原文链接:https://www.leahy.club/archives/mysqlisolations

事务并发会造成的三个隔离性问题:脏读、不可重复读、幻读。


脏读、不可重复读和幻读

首先先举例说明这三种问题。

  1. 脏读:脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中(数据还在内存中),这时,另外一个事务也访问这个数据(内存中数据),然后使用了这个数据。

    栗子:

    • Mary的原工资为1000, 财务人员将Mary的工资改为了8000(但未提交事务)
    • Mary读取自己的工资 ,发现自己的工资变为了8000,欢天喜地!
    • 而财务发现操作有误,回滚了事务,Mary的工资又变为了1000

    像这样,Mary记取的工资数8000是一个脏数据。

  2. 不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

    栗子:

    • 在事务1中,Mary 读取了自己的工资为1000,操作并没有完成
    • 在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务.
    • 在事务1中,Mary 再次读取自己的工资时,工资变为了2000

    对于某些 事务,比如定时刷新显示工资,这种操作是没有问题的。除非对于前后读取到的数据要进行什么特别的操作,而这种操作是要求工作是不可变的。

  3. 幻读:是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

    栗子:

    目前工资为1000的员工有10人。

    • 事务1,读取所有工资为1000的员工。
    • 这时事务2向employee表插入了一条员工记录,工资也为1000
    • 事务1再次读取所有工资为1000的员工 共读取到了11条记录

    可以看出:不可重复读的重点是修改,同样的数据值,前后读取值不同;而幻读重点是新增或者删除,同样的条件,前后读取出的记录行数是不一致的。


事务隔离的级别

由于上述的并发导致的事务隔离性问题,需要引入事务隔离的级别机制。针对不同的事务需求,一般来说总共五种级别。

  • TRANSACTION_NONE 不使用事务。
  • TRANSACTION_READ_UNCOMMITTED 允许脏读。
  • TRANSACTION_READ_COMMITTED 防止脏读,最常用的隔离级别,并且是大多数数据库的默认隔离级别
  • TRANSACTION_REPEATABLE_READ 可以防止脏读和不可重复读。
  • TRANSACTION_SERIALIZABLE 可以防止脏读,不可重复读取和幻读,(事务串行化)会降低数据库的效率。

锁机制

根据上述事务隔离的级别,在SQL中定义了各种锁。锁定的单元越小,就越能提高并发处理能力,但是管理锁的开销越大。所以需要在性能和并发性之间找到平衡点。

  • 共享锁:用于只读操作(SELECT),锁定共享的资源。共享锁不会阻止其他用户读,但是阻止其他的用户写和修改。
  • 更新锁:更新锁是一种意图锁,当一个事务已经请求共享琐后并试图请求一个独占锁的时候发生更新琐。例如当两个事务在几行数据行上都使用了共享锁,并同时试图获取独占锁以执行更新操作时,就发生了死锁:都在等待对方释放共享锁而实现独占锁。更新锁的目的是只让一个事务获得更新锁,防止这种情况的发生。就是只让一个事务有更新的念头,打消其他事物的念头。
  • 独占锁:一次只能有一个独占锁用在一个资源上,并且阻止其他所有的锁包括共享锁。写操作是独占锁,可以有效的防止’脏读’。
  • 意图锁:在使用共享锁和独占锁之前,使用意图锁。从表的层次上查看意图锁,以判断事务能否获得共享锁和独占锁,提高了系统的性能,不需从页或者行上检查。
  • 计划锁:Sch-M,Sch-S。对数据库结构改变时用Sch-M,对查询进行编译时用Sch-S。这两种锁不会阻塞任何事务锁,包括独占锁。
    读是共享锁,写是排他锁,先读后更新的操作是更新锁,更新锁成功并且改变了数据时更新锁升级到排他锁。

对于不同的事务隔离级别,也需要使用不同的锁定范围:

  • TRANSACTION_READ_COMMITTED:防止脏读,锁定正在读取的行。这个级别的事务,在修改时使用独占锁,直到事务提交之后才释放;读取使用共享锁,防止其他事物的修改,但是可以读取。

    <mark>假设事务1读取数据时,其他事务也读取了数据,之后事务1修改了数据,那么其他数据再来读取时就发生了不可重复读问题。</mark>

  • TRANSACTION_REPEATABLE_READ:防止脏读和不可重复读,锁定所读取的所有行。这个级别的事务,在修改时使用独占锁,读取时使用共享锁,事务提交之后才释放共享锁。与上面的不同之处在于,这里共享锁在事务提交之后才释放,上面的是读取完之后就释放了共享锁。

    <mark>假设事务1读取数据时,其他事物插入了新的数据行,注意是插入而不是修改原理的数据行,事务1再次读取时就会发生幻读。</mark>

  • TRANSACTION_SERIALIZABLE:防止脏读,不可重复读和幻读,锁表。锁的机制与上述一致,只不过锁的范围从锁行变成了锁表。


参考链接

[1]https://my.oschina.net/jimyao/blog/632458

[2]https://blog.csdn.net/xiaolinyouni/article/details/6997392