简单聊聊mysql的脏读、不可重复读
最近,在一次 mysql 死锁的生产事故中,我发现,关于 mysql 的锁、事务等等,我所知道的东西太碎了,所以,我试着用几个例子将它们串起来。具体做法就是通过不断地问问题、回答问题,再加上“适当”的比喻,来逐步构建脑子里的“知识树”。
需要提醒一下,这篇博客并不适合小白,因为你需要先了解排它锁、共享锁、事务,最重要的是你需要知道事务中的锁是什么时候加上、什么时候打开的。而这篇博客更多的是希望把这些碎片化的知识给连接起来。
项目环境
mysql 版本:5.7.28-winx64
OS:win 10
数据库脚本:
DROP TABLE IF EXISTS `demo_user`;
CREATE TABLE `demo_user` (
`id` varchar(32) NOT NULL COMMENT '用户id',
`name` varchar(16) NOT NULL COMMENT '用户名',
`gender` tinyint(1) DEFAULT '0' COMMENT '性别',
`age` int(3) unsigned DEFAULT NULL COMMENT '用户年龄',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录创建时间',
`gmt_modified` timestamp NULL DEFAULT NULL COMMENT '记录最近修改时间',
`deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除',
`phone` varchar(11) NOT NULL COMMENT '电话号码',
PRIMARY KEY (`id`),
KEY `idx_phone` (`phone`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
insert into `demo_user`(`id`,`name`,`gender`,`age`,`gmt_create`,`gmt_modified`,`deleted`,`phone`) values ('222','zzs001',0,18,'2021-12-13 15:11:03','2021-12-13 09:59:12',0,'188******26');
insert into `demo_user`(`id`,`name`,`gender`,`age`,`gmt_create`,`gmt_modified`,`deleted`,`phone`) values ('111','zzf001'0,18,'2001-08-27 11:00:11','2001-08-27 11:00:13',0,'188******22');
脏读
准备工作
在讲脏读之前,我们先开启两个会话,并把事务隔离级别更改为读未提交(read uncommitted)。这时,id 为 222 的用户初始年龄为 18。
万事俱备,我们开始吧。
什么是脏读
脏读,就是读到了其他会话还没有提交的修改。下面用例子说明:
可以看到,会话 2 修改了 id 为 222 的用户,在还没提交或回滚事务之前,会话 1 就读到了这些改动。
脏读的本质就是,还没结束的写操作被读操作分割了。所以,为了解决脏读,就必须让写操作不可被读操作分割(当然,也不能被其他写操作分割),即保证所谓的原子性。
如何解决脏读
那么,应该如何实现呢?这里给出两种方案。
第一种,给读增加锁。为了保证写操作的原子性,从更新操作开始到事务结束(注意,不是事务开始到事务结束),会话 2 都应该锁着 id 为 222 的记录,会话 1 的读操作要等会话 2 的事务结束后才能执行。上面的例子中,我们理所当然地会认为是会话 2 的写操作没有加排它锁导致的脏读,然而并非如此,通过SELECT * FROM information_schema.INNODB_TRX;
可以发现,会话 2 已经锁住了 id 为 222 的记录,但会话 1 的读操作并没有等待,为什么呢?根本原因在于会话 1 的读是无锁读,在读未提交的事务隔离级别中,无锁读不需要等待写操作。所以,我们需要给读加上锁(共享锁和排它锁均可,但为了并发读,建议用共享锁),如下:
可以看到,因为会话 2 的更新操作还没结束,所以,会话 1 需要一直等待,直到会话 2 的事务结束,这就避免了脏读的问题。你可能会觉得奇怪,实际项目好像不是这样的吧?没错,因为我们用的更多的是第二种方案。
第二种方案,将事务隔离级别更改为读已提交(read committed)。第一种方案中,读写是串行的,然而,我们既要读写并行,又不想出现脏读。需求刁钻但合理,于是,就有了第二种方案。如下:
可以看到,会话 2 的更新操作还没结束,会话 1 就读到了同一条记录,结果却没有产生脏读。如何实现的呢?
这里我说说自己的理解,可能并不严谨。逻辑上有点像 java 中的CopyOnWriteArrayList
,当事务隔离级别为已提交时,不会在实际记录上进行写操作,而是将需要修改的记录缓存一份进行更改,事务提交时才把这部分缓存刷入实际记录,而这个过程,其他会话可以正常读实际记录,而不会读到修改中的数据。
不可重复读
准备工作
在讲不可重复读之前,我们可以把事务隔离级别设置为读未提交(read uncommitted),也可以设置为读已提交(read committed)。
什么是不可重复读
不可重复读,就是在同一个事务中,多次读相同的记录但读到了不同的结果。下面用例子说明:
可以看到,会话 1 第一次读 id 为 222 的用户年龄为 18,在事务还没结束之前,会话 2 将他的年龄更改为 19,会话 1 再次读就会出现前后不一致的情况。
不可重复读的本质就是,还没结束的读操作被写操作分割了。所以,为了解决不可重复读,就必须让读操作不可被写操作分割,即保证所谓的原子性。
如何解决不可重复读
那么,应该如何实现呢?和解决脏数据一样,这里也给出两种方案。
第一种方案,给读增加锁来。为了保证读操作的原子性,从读操作开始到事务结束(注意,不是事务开始到事务结束),会话 1 都应该锁着 id 为 222 的记录,会话 2 的写操作要等会话 1 的事务结束后才能执行。所以,我们需要给读加上锁(共享锁和排它锁均可,但为了并发读,建议用共享锁),如下:
可以看到,会话 2 的写操作需要等待会话 1 的事务结束才能执行,在事务结束之前,会话 1 读几次数据都不会出现不可重复读。
第二种方案,将事务隔离级别更改为可重复读(repeatable read)。第一种方案中,读写是串行的,然而,我们既要读写并行,又不想出现不可重复读。于是,就有了第二种方案。如下:
可以看到,会话 1 的读操作并没有加锁,会话 2 的写操作也不需要等待,最终却没有产生不可重复读。如何实现的呢?
这里我还是说说自己不严谨的理解。当第一次读到 id 为 222 的记录时,mysql 会把这条记录放在当前事务的缓存区里,下次读这条数据的时候直接从缓存拿就好,不需要去读实际记录,所以,其他会话的写操作并不需要等待。这种读被称为快照读,如果读已提交中的写是 copy on write,那可重复读的读就是 copy on read。
幻读
未完待续。
结语
以上只是自己对 mysql 锁、事务的一点点思考。因为我并没有看过底层的逻辑,所以都是一些抽象层面的解读。如有错误,欢迎指正。
最后,感谢阅读。
参考资料
本文为原创文章,转载请附上原文出处链接:https://www.cnblogs.com/ZhangZiSheng001/p/15552479.html
简单聊聊mysql的脏读、不可重复读的更多相关文章
- 数据库事务隔离级别 - 分析脏读 & 不可重复读 & 幻读
一 数据库事务的隔离级别 数据库事务的隔离级别有4个,由低到高依次为Read uncommitted .Read committed .Repeatable read .Serializable ,这 ...
- Java -- JDBC 事务处理, 事务的隔离级别 脏读 不可重复读 等...
1. 事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功. 数据库开启事务命令 •start transaction 开启事务 •Rollback 回滚事务 •Commit ...
- spring事务隔离级别以及脏读 不可重复读 幻影读
隔离级别 声明式事务的第二个方面是隔离级别.隔离级别定义一个事务可能受其他并发事务活动活动影响的程度.另一种考虑一个事务的隔离级别的方式,是把它想象为那个事务对于事物处理数据的自私程度. 在一个典型的 ...
- MySQL事务(脏读、不可重复读、幻读)
1. 什么是事务? 是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作:这些操作作为一个整体一起向系统提交,要么都执行.要么都不执行:事务是一组不可再分割的操作集合(工作逻辑单元): ...
- Mysql事务,并发问题,锁机制-- 幻读、不可重复读--专题
1.什么是事务 事务是一条或多条数据库操作语句的组合,具备ACID,4个特点. 原子性:要不全部成功,要不全部撤销 隔离性:事务之间相互独立,互不干扰 一致性:数据库正确地改变状态后,数据库的一致性约 ...
- MySQL使用可重复读作为默认隔离级别的原因
一般的DBMS系统,默认都会使用读提交(Read-Comitted,RC)作为默认隔离级别,如Oracle.SQL Server等,而MySQL却使用可重复读(Read-Repeatable,RR). ...
- MySQL选用可重复读之前一定要想到的事情
原文地址:http://blog.itpub.net/29254281/viewspace-1398273/ MySQL选用可重复读隔离级别之前一定要想到的事情.间隙锁 MySQL在使用之前有三个务必 ...
- 聊聊mysql的事务
今天来聊聊事务的四大特性以及其实现原理,需结合之前写的mysql是如何实现mvcc的来理解,因为大多数的实现都是基于mvcc的,理论介绍完后会通过实例来演示mvcc又是如何实现这些隔离级别的 事务的四 ...
- mysql系列:加深对脏读、脏写、可重复读、幻读的理解
关于相关术语的专业解释,请自行百度了解,本文皆本人自己结合参考书和自己的理解所做的阐述,如有不严谨之处,还请多多指教. 事务有四种基本特性,叫ACID,它们分别是: Atomicity-原子性,Con ...
随机推荐
- Codeforces 1304F1/F2 Animal Observation(单调队列优化 dp)
easy 题目链接 & hard 题目链接 给出一张 \(n \times m\) 的矩阵,每个格子上面有一个数,你要在每行选出一个点 \((i,t)\),并覆盖左上角为 \((i,t)\), ...
- FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅲ
第三波,走起~~ FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅰ FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅱ 单位根反演 今天打多校时 1002 被卡科技了 ...
- Codeforces 633F - The Chocolate Spree(树形 dp)
Codeforces 题目传送门 & 洛谷题目传送门 看来我这个蒟蒻现在也只配刷刷 *2600 左右的题了/dk 这里提供一个奇奇怪怪的大常数做法. 首先还是考虑分析"两条不相交路径 ...
- 毕业设计之zabbix 之mysql主从状态的监控
建立监控脚本在自定义的位置 /usr/local/zabbix/script/ [root@mysql.quan.bbs script]$pwd /usr/local/zabbix/script [r ...
- 55. Binary Tree Preorder Traversal
Binary Tree Preorder Traversal My Submissions QuestionEditorial Solution Total Accepted: 119655 Tota ...
- 基于 芯片 nordic 52832 rtt 调试(Mac 电脑)
代码配置 // <e> NRF_LOG_BACKEND_SERIAL_USES_UART - If enabled data is printed over UART //======== ...
- Zookeeper【概述、安装、原理、使用】
目录 第1章 Zookeeper入门 1.1 概述 1.2 特点 1.3 数据结构 1.4应用场景 第2章 Zookeep安装 2.1 下载地址 2.2 本地模式安装 1. 安装前准备 2. 配置修改 ...
- java代码定时备份mysql数据库及注意事项——基于 springboot
源码地址: https://gitee.com/kevin9401/BackUpDataBase git 拉取: https://gitee.com/kevin9401/BackUpDataBase. ...
- linux ln用法
这是linux中一个非常重要命令,请大家一定要熟悉.它的功能是为某一个文件在另外一个位置建立一个同不的链接,这个命令最常用的参数是-s,具体用法是:ln -s 源文件 目标文件 这是linux中一个非 ...
- Equinox OSGi服务器应用程序的配置步骤 (支持JSP页面)
本文介绍在Eclipse里如何配置一个简单的基于Eclipse Equinox OSGi实现的Web应用程序,在它的基础上可以构造更加复杂的应用,本文使用的是Eclipse 3.3.1版本,如果你的E ...