MySQL 事务并发问题(详解)

目录

  • 1-并发事务导致的问题
      • 1.1读问题
      • 1.2更新问题
  • 2-事务的隔离级别



1-并发事务导致的问题

假设现在有A,B两个事务,这两个事务都可以对数据库进行读取和修改。那么,排列组合后可以分为三种情况(都是以操作同一资源为前提):

情况 操作 结果
1 A,B都采取读操作 不会出现任何问题
2 其中一个事务进行修改,另一个则进行读取 执行修改的那个事务不会出错,执行读操作的事务读取的数据会出现问题(称为读问题,下面会详细介绍)
3 A,B都对数据库进行修改 在对同一数据进行修改时,先修改的数据会被后修改的数据覆盖(称为更新问题,下面会详细介绍)



1.1读问题

读问题分为三类(我们假定执行读操作的事务为A,执行修改的事务为B):

问题名称 解释
脏读 即A在B还未提交数据时读取数据。因为你不知道B事物之后执行的是回滚还是提交,所以你不能保证此数据的可靠性。
不可重复读 A在B提交数据前读取一次,然后在B提交数据后再读取一次。造成A事务内部多次读取的结果不一样。
幻读 A 在B插入数据前读取一次,然后在B插入数据后再读取一次。造成A事务内部前后两次查询结果不一致

1.2更新问题

以修改数据库中表的数据为例。A修改好表中的某一数据准备提交时,B立即修改此表中的其它项数据。A正常提交数据,然后B也正常提交数据。

时间点 事务A 事务B
1 开启事务
2 开启事务
3 update t_sutdent ts set ts.sname=“张三” where ts.sid=“10”
4 update t_sutdent t set t.sname=“李四”,t.age=18 where ts.sid=“10”
5 提交
6 提交
问题 事务A认为数据库中的数据已成功改为:sid=10的学生的姓名为“张三”。(实际上是“李四”) 事务B认为数据库已修改成功。(确实修改成功,但却覆盖了A的数据,且A并不知晓

解决方法:
  • 使用排它锁(首句添加“for update”): 把写写并行改为写写串行(如下表)。简单地说就是A,B在执行数据修改时要排队。而且当某一进程正在修改时,其它进程不能查询。

    时间点 事务A 事务B
    1 开启事务
    2 修改数据 不可开启事务
    3 提交数据 不可开启事务
    4 开启事务
    5 不可开启事务 修改数据
    6 不可开启事务 提交数据
  • 使用乐观锁(添加version或时间戳): 在写写并行发生冲突时报异常1,交给程序员处理。




2-事务的隔离级别

事务的四种隔离级别:

种类 处理的问题 解释
串行化 三个读问题都能处理 顾名思义,此方法下,对同一数据的访问是串行的。没有并发,就不会出现任何并发问题。此级别基本不使用,一是效率底;二是容易出现死锁。
可重复读 不能处理幻读 mysql有两种机制能达到此级别的隔离效果。一、加读锁(读读共享) + 加写锁(读写串行):实现简单,但因为读写无法并行,所以效率低;二、读不加锁 + 加写锁 + MVCC(有兴趣的可以自行百度):实现复杂,但读写可以并行2,效率高。
读已提交数据 只能处理脏读 操作:写时加排它锁,读时不加操作。 引用大佬的话(原文找不到了):在mysql的MVCC机制和该隔离级别的共同作用下,每次select的时候数据库会新生成一个版本号,所以每次select的时候读的不是一个副本 而是不同的副本。每次select之间有其他事务更新了,而我们读取数据并提交了,那就出现了不可重复读。所以虽然此级别效率高,但是mysql不能使用。
读未提交数据 即,不做任何处理。





  1. 假如我们规定,数据库原始的version为0,在某个事务对其进行修改后要把version + 1后才能存入数据库。那么,只要 存入的version < 当前数据库的version,就把它判定为丢失更新。通常遇到这种问题时,更新数据库版本就行(需要程序员自己实现)。 ↩︎

  2. 当原数据被写锁占用时,读请求可以去读取数据库的副本,又因为并行的写操作加了写锁(写的这段时间数据库不能给出副本)所以得到的副本只有两种情况:一、改写之前的副本;二、改写后的副本。这样便解决了更新问题。 ↩︎

你可能感兴趣的:(mysql)