ⓂMySQLMySQL三种并发问题以及四种隔离级别
Posted 百里浅暮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ⓂMySQLMySQL三种并发问题以及四种隔离级别相关的知识,希望对你有一定的参考价值。
并发读:脏读、不可重复读、幻读
如果不对事务进行隔离性的保障,则在多个事务并发进行时,可能出现:脏读、不可重复读、幻读。
脏读
一个事务处理过程中,读取了另一个事务还未提交的数据。
举例:
- money = 0.
- 事务A、B开启。
- 事务A将money修改为100,但还未提交。
- 事务B执行money = money + 100,将money修改为200。
- 事务A回滚,事务B提交。
- 异常:经过一次自增100的操作,money却增加了200。
重点:针对还未提交的数据。
不可重复读
一个事务处理过程中,对同一数据的多次查询得到不同的结果,这是由于另一个事务在多次查询间隔修改了这个数据。
举例:
- money = 0。
- 事务A、B开启。
- 事务B第一次读取money = 0。
- 事务A修改money为100,然后提交。
- 事务B第二次读取money = 100。
- 异常:在同一个事务中,对money的两次读取,结果不同。
重点:针对被修改且已提交的数据。
幻读
一个事务处理过程中,读取了另一个事务新增加或删除的数据。
举例:
- 表中共计10条记录。
- 事务A、B开启。
- 事务B第一次count,获悉count(id) = 10。
- 事务A新增了2条记录,删除了一条记录,然后提交。
- 事务B第二次count,获悉count(id) = 11。
- 异常:在同一个事务中,对表中数据总条数的两次统计,结果不同。
重点:针对新增删且已提交数据。
四种隔离级别
MySQL提供了四种事务隔离级别,从低到高依次是:读未提交、读已提交、可重复读、串行化。
读未提交Read Uncommitted
简单理解:一个事务可以读取Read到其他事务未提交Uncommitted的数据。
解决的并发事务问题:最低级别,无法解决任何问题。
未解决的并发事务问题:脏读、不可重复读、幻读。
读已提交 Read Committed
简单理解:一个事务需要读取Read到其他事务已经提交Committed的数据。
解决的并发事务问题:因为已提交的保证,能够避免脏读的出现。
未解决的并发事务问题:不可重复读、幻读。
可重复读Repeatable Read
简单理解:一个事务能够对同一记录进行重复性的Repeatable读取Read,多次读取结果一致。
解决的并发事务问题:不可重复读、脏读。
未解决的并发事务问题:幻读
特别:MySQL的默认事务隔离级别。
串行化Serializable
简单理解:多个事务以串行化Serializable的方式运行,自然不会产生并发事务问题。
解决的并发事务问题:幻读、不可重复读、脏读。
特别:效率特别低。
三种并发读与四种事务隔离级别的总结
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | 未解决 | 未解决 | 未解决 |
读已提交 | 解决 | 未解决 | 未解决 |
可重复读 | 解决 | 解决 | 未解决 |
串行化 | 解决 | 解决 | 解决 |
隔离级别的查看与设置
-- 查看
select @@tx_isolation;
-- 设置
set tx_isolation=\'read-committed\';
MySQL隔离级别的实现
虽然平时已经很少使用MySQL了,但是数据库作为基本技能仍然不能忘,最近在学习数据库隔离级别,在此写下个人理解以备复习。
大家都知道数据库事务ACID(原子性、一致性、隔离性和持久性)的四个特征,也知道数据库存在三种并发问题(脏读、不可重复读、幻读),以及针对性的四种隔离级别(读未提交、读已提交、可重复读、序列化)。
解决与否 | 脏读 | 不可重复读 | 幻读 |
读未提交 | Yes | Yes | Yes |
读已提交 | No | Yes | Yes |
可重复读 | No | No | Yes |
串行化 | No | No | No |
特别提醒一句,隔离级别作用在连接(或会话)级别。客户端每次连接数据库的时候,都要根据自己对一致性的需求程度合理设置自己的事务隔离级别。
那么问题来了,MySQL底层是采用何种技术来实现这四种隔离级别的呢?
读未提交
MySQL全表的数据存储在以主键为排序值的B+树索引中,叶子节点存储了相应主键的整行记录。需要指出的是,叶子节点的数据都是最新的数据,可能是事务提交后的一致状态,也可能是事务执行中的中间值(可能被回滚)。
当隔离级别设置为RU时:
- 所有的读不加锁,读到的都是叶子节点上最新的值,性能最好。
- 所有的写(更新、插入、删除)加行级排斥锁,不存在脏写的问题,写完就释放锁。
读已提交
当隔离级别是RC和RR时,就要谈到大名鼎鼎的MVCC(多版本控制) 技术。通过在每行加入若干隐藏的字段,它实现了不加锁的读操作,性能较好。
- 先说RC级别的写操作,MySQL依然加行级排斥锁。事务开始时会往UNDO日志中写入当前的有效记录值,B+树叶子节点的隐藏列DATA_ROLL_PTR会存储指向该UNDO记录的指针。顺着行的DATA_ROLL_PTR的指针形成一个链表,记录该行数据的有效的历史记录。
- 再说不加锁的读操作,如果叶子节点正被其他事务锁定,那么MySQL顺着叶子节点的DATA_ROLL_PTR指针找到上一个有效的历史记录即可。
可重复读
在事务开始的时候,除了正常往UNDO日志中写回滚的数据外,会创建一个ReadView,记录了当前活跃的其他事务的ID,其中最小值为Tmin,最大值为Tmax。
当执行SELECT操作时,MySQL顺着行记录的DATA_ROLL_PTR指针查找符合条件的历史版本。这里就用到了另一个隐藏列DATA_TRX_ID,其中存储的是更新该记录的事务ID(事务ID是全局递增且唯一的)。如果DELETE_BIT为1,则代表ID为DATA_TRX_ID的事务对当前行执行了删除。
扫描历史版本串成的链表的过滤条件是:
- 如果当前记录的DATA_TRX_ID小于Tmin(之前存在的数据),那么由DELETE_BIT决定是否可见;否则,转2。
- 如果当前记录的DATA_TRX_ID小于Tmax,且不在活跃的事务ID集合中,那么由DELETE_BIT决定是否可见视为可见;否则,转3。
- 否则视为不可见,顺着DATA_ROLL_PTR进入上一个历史版本,或因为到头而结束回溯。
因为插入的数据版本号要么在活跃事务ID集合内、要么大于当前事务ID,所以MVCC机制同时解决了幻读问题。
需要指出的是,以上三个隔离级别中的读均为普通的SELECT。如果用的是SELECT ... LOCK IN SHARE MODE或SELCT ... FOR UPDATE,均属于当前读。即加写锁,读叶子节点最新值,如果更早的事务改了行值,依然会存在不可重复读的情况。
在RR级别下,如果WHERE的条件列上有唯一索引,那么MySQL只加行级锁;如果是普通索引,会加间隙锁来防止幻读;如果没有索引,就会采用表锁,大大降低并发写的能力。
串行化
读写均加表级的读写锁即可,直接读主键索引B+树的叶子节点的最新数据。该级别下,数据一致性很强,但是并发写的能力非常差。
以上是关于ⓂMySQLMySQL三种并发问题以及四种隔离级别的主要内容,如果未能解决你的问题,请参考以下文章