您的位置:首页 > 博客中心 > 数据库 >

理解MySql事务隔离机制、锁以及各种锁协议

时间:2022-03-13 22:56

    一直以来对数据库的事务隔离机制的理解总是停留在表面,其内容也是看一遍忘一边。这两天决定从原理上理解它,整理成自己的知识。查阅资料的过程中发现好多零碎的概念如果串起来足够写一本书,所以在这里给自己梳理一个脉络,具体的内容参考引文或在网上搜一下。由于平时接触最多的是MySQL,所以文章中某些部分是MySQL特有的特性,请读者注意。


数据库并发操作会引发的问题:

  脏读 不可重复读 幻读 Read uncommitted √ √ √ Read committed × √ √ Repeatable read × × √ Serializable × × ×

    四种级别对并发问题的解决由弱到强,相应的系统性能由强到弱,MySQL的默认级别是Repeatable Read。

Read Uncommitted

    在Read Uncommitted策略下,数据库遵循一级封锁协议,只对修改数据的并发操作做限制。一个事务不能修改其他事务正在修改的数据,但可以读取到其他事务中尚未提交的修改,这些修改如果未被提交,将会成为脏数据。

Read committed

    在Read committed策略下,数据库遵循二级封锁协议,只允许读取已经被提交的数据,反过来讲,如果一个事务修改了某行数据且尚未提交,而第二个事务要读取这行数据的话,那么是不允许的。在MySql的InnoDB下,虽然这种操作不被允许,但MySQL不会阻塞住数据的查询操作,而是会查询出数据被修改之前的备份,返回给客户端。MySQL的这种机制称为MVCC(多版本并发控制),就是说数据库在事务并发的过程中对数据维护多个版本,使得不同的事务对不同的数据版本进行读写(MVCC的实现参见引用中的文章)。这样的机制反映在应用中就是,在任何时候对数据库查询总是可以得到数据库中最近提交的数据。为被提交的脏数据被隔离起来,无法被查询到,即防止脏读发生。

Repeat Read

    Repeat Read又比Read Committed更加严格一点,但仍然是在二级封锁协议的范畴,只是读取过程受到更多MVCC的影响。在Read Committed下,允许一个事务中多次相同查询得到不同的结果,就是所谓的不可重复读问题。这在一些应用中是允许的,所以oracle、SQL server上默认这一隔离级别,但MySQL没有,它默认Repeat Read级别。在这一级别下,有赖于MVCC,同一个事务中的查询只能查到版本号不高于当前事务版本的数据,即事务只能看到该事务开始前或者被该事物影响的数据。反过来说,这一级别下,不允许事务读取在该事务开始后新提交的数据。即防止了不可重复读的发生。

    依靠上面的机制,已经做到了在事务内数据内容的不变,但是不能保证多次查询得到的数据数量一致。因为在一个事务执行的过程中别的事务完全可以执行数据插入,当插入了刚好符合查询条件的数据时,就会引发数据查询结果集增加,引发幻读。还有一种情况就是,如果一个事务想插入一条数据,而另一个事务已经插入了含有相同主键的数据,那么当前事务也会被阻塞,并最终执行失败,虽然当前事务根本无法查询到这一条数据,这也是一种幻读。InnoDB提供的间隙锁机制可以在一定程度上防止幻读的发生,具体介绍见最后一篇引文。

Serializable

    最后,最强事务隔离机制Serializable,它遵循三级封锁协议,使得所有的事务必须串行化执行,只要有事务在对表进行查询,那么在此事务提交前,任何其他事务的修改都会被阻塞。这解决了一切并发问题,但会造成大量的等待、阻塞甚至死锁,使系统性能降低。


    要注意,在任何一种隔离机制下,都是不允许一个事务删除或修改另一个事务影响过而未提交的数据的。因为事务增、删、改数据以后,会在该行加上排它锁,排它锁会阻塞其他事务再次对该行数据操作。也正是由于排它锁的存在,这四种隔离机制都不会出现任何一种更新丢失的现象,因为一条信息根本不允许第二个事务进行修改。

两段锁协议

    数据库在调度并发事务时遵循“两段锁”协议,“两段锁”协议是指所有事务必须分两个阶段对数据项进行加锁和解锁

  1. 扩展阶段:在对任何数据项的读、写之前,要申请并获得该数据项的封锁。
  2. 收缩阶段:每个事务中,所有的封锁请求必须先于解锁请求。

    在数学上可以证明,遵循两段锁的调度可以保证调度结果与串行化调度相同。这样的机制保证了数据库并发调度与串行调度的等价。


*注:

参考资料:






热门排行

今日推荐

热门手游