数据库隔离机制的实现


数据库隔离机制

  1. 串行化
  2. 可重复读
  3. 读已提交
  4. 读未提交

数据库事务可能导致的问题

  1. 脏读
  2. 不可重复读
  3. 幻读
脏读

​ 事务A,先执行,处于未提交的状态:

​ insert into table values(1, xx);

​ 事务B,后执行,也未提交:

​ select * from table ;

​ 如果事务B能够读取到(1, xx)这条记录,事务A就对事务B产生了影响,这个影响叫做“脏读”,读到了未提交事务操作的记录。

不可重复读

​ 事务A,先执行:

​ select * from table where id=1;

​ 结果集为:

​ 1, xx

​ 事务B,后执行,并且提交:

​ update table set name=yy where id=1;

​ commit;

​ 事务A,再次执行相同的查询:

​ select * from table where id=1;

​ 结果集为:

​ 1, yy

​ 这次是已提交事务B对事务A产生的影响,这个影响叫做“不可重复读”,一个事务内相同的查询,得到了不同的结果。

​ 和脏读区别在于,脏读在于读事务B还没有提交的数据,而不可重读是读取到事务B已经提交的数据

幻读

​ 事务A,先执行:

​ update table set name=“hh” where id>3;

​ 结果为:

​ OK row xx 表名成功影响多少行数据

​ 事务B,后执行,并且提交:

​ insert into table values(11, uu);

​ commit;

​ 事务A,然后再select一下:

​ select * from table where id>3

​ 结果集为:

​ …

​ 11,uu

​ …

​ 事务A懵了,我特么不是id>3全部更新了吗

​ 这次是已提交事务B对事务A产生的影响,这个影响叫做“幻读”。


数据库隔离机制的实现

一、读未提交

​ 这个玩意应该没人使用,完全性的不加锁,select不加锁。最爽,最快,可惜数据脏读

二、串行化

​ 这个最安全,select都是加锁,所有select语句都会被隐式的转化为in share mode. 这就意味着,如果前面有事务在做修改操作,那么就会阻塞。完全性的把读和写串行化了。但是这个并发性不高

三、读已提交

​ 最使用最广的隔离机制

​ 普通读是快照读,MVVC每次读取都是读取当前快照最新版本,会自动刷新,所以读取其他事务已经提交了的

​ 加锁的select, update, delete等语句,除了在外键约束检查以及重复键检查时会封锁区间,其他时刻都只使用记录锁;

​ 此时,其他事务的插入依然可以执行,就可能导致,读取到幻影记录。也就是说一般都是记录锁,锁单记录,上面那个幻读例子中,id>3的有5条记录,只会单独锁5条,插入11的时候还是不会锁住。

四、可重复读

​ 这是InnoDB默认的隔离级别

​ 普通的select使用快照读,MVVC读取创建版本小于或等于当前事务版本号,并且删除版本为空或大于当前事务版本号的记录。这样可以保证在读取之前记录是存在的。解决幻读和不可重复读

​ 加锁的select(S或者X), update, delete等语句,它们的锁,依赖于它们是否在唯一索引上使用了唯一的查询条件,或者范围查询条件:

  • 在唯一索引上使用唯一的查询条件,会使用记录锁
  • 范围查询条件,会使用间隙锁与临键锁,锁住索引记录之间的范围,避免范围间插入记录,以避免产生幻影行记录,以及避免不可重复的读

总结

  • 读未提交没有锁
  • 读已提交,普通读都是快照读,加锁读和更新操作一般都是记录锁,除非外键和重复键检查 解决脏读问题
  • 可重复读,普通读是快照,加锁读和更新操作如果是唯一索引上的唯一条件则记录,范围条件就是间隙锁和临键锁 解决幻读,不可重读的问题
  • 串行化,读写串行化

纠正个问题

​ 大多数资料上说幻读只能串行化解决,而可重复读只能解决不可重复读,解决不了幻读。
​ 在《Mysql技术内幕,InnoDB存储引擎》 事务解释,innodb默认的可重复读的事务隔离机制,采用的是next-key lock(临键锁) 锁定范围,因此避免了幻读

资料

http://note.youdao.com/noteshare?id=a1de9654d9cc56dde20f218357fda548