高性能MySQL(3th)(第一章 MySQL概述 ) —— 02 MySQL隔离级别
一 数据库隔离级别标准
抛开MySQL,一般性的数据库隔离级别有4种:
而不同的隔离级别可以避免的并发不一致问题也是不一样的:
关于三种问题的描述,可以详见:
这里谈谈自己对不同隔离级别为什么会产生相应的并发一致性问题的理解:
1)未提交读产生脏读
这里其实最好理解,未提交读可以认为未实现事务的“隔离性”,导致其他事务可以“看见”本事务的数据,就如并发一致性问题中举的例子,事务T1本来打算修改数据(update),但是临时决定回滚(可能因为事务中间发生错误),而事务T2刚好读到了中间的结果,这个结果就是脏数据。
2)提交读解决了脏读,但会产生不可重复读
因为提交读相当于给每个事务进行了“包裹”,其他事务不能看见本事务的中间过程,事务T1对于事务T2而言,要么执行了,要么未执行,没有中间状态(从数据来看),即实现了事务的隔离性。这就解决了脏读问题。
那么为什么没有解决不可重复读的问题?
可以这么理解:提交读将事务封装成了一个个“原子”,无法从中间状态侵入,但是没有解决事务之间的并发问题,借用并发一致性问题中不可重复读的例子:
相对于T2,事务T1(update操作)的执行时间很短,在事务T2两次select操作中间完成了。这样就导致T2出现了不可重复读的问题。其实这个就是多线程编程中要解决的基本问题:并发安全问题。而解决的方案很简单:加锁 —— 乐观锁 or 悲观锁。
MySQL采用了CAS乐观锁 —— 多版本并发控制MVCC(Multi - Version Concurency Control),即使用版本快照的方式实现乐观锁。(还有一个常见的方式:时间戳)。
3)可重复读解决了不可重复读问题,但会产生幻读
可重复读使用了MVCC机制实现了可重复读,即如果一个事务发现在自己事务执行时间内其他事务修改了数据,则进行事务回滚,重新尝试执行事务(CAS)。
那为什么还会导致幻读问题?
首先,幻读是什么? —— 其实就是“幻行”(Phantom Row)的问题,MVCC只是在每一行后面添加两个隐藏的列:创建版本号和删除版本号(MVCC后面具体再谈),但是对于行的数目(表)没法控制,它能够保证对每一行数据进行加锁(其实CAS不是锁),保证并发的安全性,但是对于整行的insert或者delete是没法控制的。
举个例子,Student表中一共就两行数据:
id name age
1 Tom 18
2 Jerry 17
可能重复读能够保证多个事务 “1 Tom 18”的记录的操作是多线程安全的,但是多个事务同时进行insert或者delete操作时,COUNT()的结果将是不可预测的,因为事务并没有对表进行加锁。
3)串行化解决了幻读问题
很好理解,串行化人如其名,将所有事务串行化,相当于每个时刻只有一个事务在执行,当然不会有幻读问题。
二 MySQL的Repeatable Read级别
虽然数据库的隔离标准中,RR级别不能避免幻读问题,但是MySQL的RR实现却解决了幻读问题,《高性能MySQL》原话:
使用MVCC后能解决可重复读问题,不能解决幻读的原因在上面已经说明,结合上面讲的,思考一下MVCC下,再加上面限制条件可以解决幻读问题?
答案是:Next-Key Locks
在讨论这个next-key之前先要认识Record lock和Gap lock:
(参考:Next-Key Locks)
注意:Record lock(记录锁)锁索引本身不锁间隙(若干点),Gap lock(间隙锁)锁间隙不锁索引本身(若干开区间)。这也就有了Next-Key Locks,如果Gap lock能锁索引本身(若干闭区间)那么就不用引入Next-Key Locks了。
简单说下MVCC下加Next-Key Locks的原则:
1)如果事务内where是唯一条件,如 id = 99,则只需要加记录锁;达到事务期间其他不能delete,update。
2)如果事务内where是范围条件,则要同时加记录锁和间隙锁即加Next-Key Locks,达到其他事务不仅不能delete,update边界,也不能对范围内的数据进行插入或者删除。