事务隔离级别
1.read uncommitted(读未提交)
在Read Uncommitted级别,事务中的修改,即使没有提交,对其他的事务也是可见的.
事务可以读取未提交的数据,这也被称为脏读(Dirty Read). 这个级别会导致很多的问题,从性能上来说,read uncommitted不会比其他的级别好很多,但缺乏其他级别的好处,除非真的非常必要的理由,在实际应用中一般很少使用.
2.read committed (读已提交)
大多数数据库的默认隔离级别都是read committed(但MySQL不是). read committed满足前面提到的隔离性的简单定义:一个事务开始时,只能"看见"已经提交的事务所作的修改.换句话,一个事务从开始到提交之前,所作的任何修改对其他事务都是不可见的.这个级别有时候也叫做不可重复读(nonrepeatable read),因为2次执行同样的查询,可能得到不同的结果.
读已提交的不可重复读再现和疑问
时间 | 事务A | 事务B |
---|---|---|
设置隔离级别 | set session transaction isolation level repeatable read; | set session transaction isolation level repeatable read; |
T1 | 开始事务(start transaction;) | |
T2 | 开始事务(start transaction;) | |
T3 | 查询账户余额100元 | |
T4 | 查询账户余额100元 | |
T5 | 取出10元,将余额改为90元 | |
T6 | 提交事务 | |
T7 | 查询账户余额90元(和T4读取的不一致) |
如果需要手动实现一遍的话,可以参考这篇博客
突然在想"读到 90 不是挺好的么,实际就是 90 了啊…"
然后我百度了一下, 在v2ex里有个老哥发了一类似问题的帖子,觉得里边有几个人的回答可以解决我的疑问.
比如在执行, 所以还是需要考虑解决不可重复读的问题.
BEGIN TRANSACTION
UPDATE 表 1 SELECT FROM ....
// 不可重复读
UPDATE 表 2 SELECT FROM ...
COMMIT
表 1 和 表 2 不一致了
3.repeatable read (可重复读)
repeatable read解决了脏读的问题.该级别保证了在同一个事务***多次***读取同样的记录的结果是一致的. 但是理论上,可重复读隔离级别还是无法解决里另一个幻读(Phantom read)的问题,
所谓幻读,指的是在当前事务在读取某个范围内的记录时,另一个事务又在该范围内查询一条新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom row)InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency control)
解决了幻读的问题
可重复读 如果按照上面的表格再执行一遍会怎样?
时间 | 事务A | 事务B |
---|---|---|
设置隔离级别 | set session transaction isolation level repeatable read; | set session transaction isolation level repeatable read; |
T1 | 开始事务(start transaction;) | |
T2 | 开始事务(start transaction;) | |
T3 | 查询账户余额100元 | |
T4 | 查询账户余额100元 | |
T5 | 取出10元,将余额改为90元 | |
T6 | 提交事务 | |
T7 | 查询账户余额还是100元 | |
T8 | 如果此时事务A执行修改操作却能正确修改数据库,比如update X表 set money=money-10 where id=X; 此时的余额会变成80元,好神奇哦 |
卧槽,居然还是100元,虽然解决了多次读取到的数据不一致的问题, 但是你这读取到的是旧的值啊. 如果用来
BEGIN TRANSACTION
UPDATE 表 1 SELECT FROM ....
// 不可重复读
UPDATE 表 2 SELECT FROM ...
COMMIT
表 1 和 表 2 不一致了
岂不是翻车了??? 所以有时候感觉如果不是 同 一 个 事 务 \color{red}{同一个事务} 同一个事务多次读取的话,用read committed隔离级别就够了,反正这个级别也能实现行级锁,Oracle就是默认read committed隔离级别
4.serializable (可串行化)
serializable是最高的隔离级别.它通过强制事务串行化执行,避免了前面说的幻读的问题,简单来说,serializable会在读取的每一行
数据上都加锁,所以可能导致大的超时和锁的争用的问题.实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别.
隔离级别 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 | 加锁读 |
---|---|---|---|---|
read uncommitted | yes | yes | yes | no |
read committed | no | yes | yes | no |
repeatable read | no | no | yes | no |
serializable | no | no | no | yes |
从<<高性能MySQL>>抄写的补充知识点
隐式和显式锁定
InnoDB采用的是两阶段锁定协议(two-phase locking protocol). 在事务执行过程中,随时都可以执行锁定,锁只有在commit和rollback的时候才会释放,并且所有的锁是同一时刻被释放.前面描述的锁定是隐式锁定,InnoDB会根据隔离级别在需要的时候进行加锁.
另外,InnoDB也支持通过特定的语句进行显式锁定,这些语句是不属于sql规范的
- select …lock in share mode
- select …for update
mysqlye 也支持lock tables和unlock tables语句,这些是在服务器层
实现的,和存储引擎无关.他们有自己的用途,但并不能替代事务处理.如果应用需要用到的事务,还是应该选择事务性存储引擎.
经常可以发现,应用已经将表从Myisam转换到InnoDB,但还是显式地使用lock tables语句.但不但没有必要,还会影响性能,实际上InnoDB的行级锁工作得更好.