事务并发

事务并发产生的四个问题

更新丢失

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题——最后的更新覆盖了由其他事务所做的更新。

clipboard.png

脏读

一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做"脏读"。

image.png

不可重复读

一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。

image.png

幻读

错误的理解:
一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。
这其实并不是幻读,这是不可重复读的一种,只会在 R-U R-C 级别下出现,而在 mysql 默认的 RR 隔离级别是不会出现的。
解决不可重复读使用的是一致性读模式,通过快照的方式,使得事务内的select只会以第一次查询为基准。
事实是,在RR级别下,如果事务A的两个select操作中间不穿插更新、删除、插入操作,那么即使别的事实B有更新、删除、插入操作,并且已经提交。事物A在事务B提交后的,第二次select操作所得到的查询结果,与第一次相同。(如果以上述错误的理解为准,那RR级别不就不存在幻读了?)

image.png

正确的理解:

重现更新丢失问题(错误的理解)

第一步:

我在数据库造了一条数据,如下图:

image.png

解释一下,

字段名 备注
id 唯一标识
num 计数器

第二步:

写一个事务

image.png

在事务中,分为三个步骤:

  1. 获取这条数据
  2. 对num加一
  3. 更新这条数据

第三步:

多线程并行执行10次事务

image.png

第四步:

多次执行,查看结果

image.png

运行三次,每次结果都不同(每次运行完都把计数器置零),但都没加到10,即存在更新丢失问题。

事务的隔离级别

MySQL/InnoDB定义的4种隔离级别:

  • Read Uncommited
    读未提交:一个事务可以读取到另一个事务未提交的修改。这会带来脏读、幻读、不可重复读问题。(基本没用)

  • Read Committed (RC)
    读已提交:一个事务只能读取另一个事务已经提交的修改。其避免了脏读,但仍然存在不可重复读和幻读问题。

  • Repeatable Read (RR)
    可重复读:同一个事务中多次读取相同的数据返回的结果是一样的。其避免了脏读和不可重复读问题,但幻读依然存在。

  • Serializable
    串行化:从MVCC并发控制退化为基于锁的并发控制。不区别快照读与当前读,所有的读操作均为当前读,读加读锁 (S锁),写加写锁 (X锁)。事务串行执行。避免了以上所有问题。
    Serializable隔离级别下,读写冲突,因此并发度急剧下降,在MySQL/InnoDB下不建议使用。

于是我尝试提高事务的隔离级别到Serializable来防止更新丢失

image.png

执行上述多线程并发加程序时,缺发生了数据库死锁问题:

image.png

究其原因,是SERIALIZABLE隔离级别读写锁竞争导致的。

在SERIALIZABLE级别下,不会使用mysql的mvcc机制,而是在每一个select请求下获得读锁,在每一个update操作下尝试获得写锁。

在我们的例子中,在level_1中,事务A获得了id = 1的读锁A。

而在同时,事务B获得id = 1的读锁B。

在事务A level_2时,事务A尝试获得id = 1的写锁,这个时候,由于id = 1处不仅有事务A的读锁,还有事务B的读锁,因此事务A的update操作获取锁被阻塞。

此时,当事务B继续执行update操作时,由于事务A又拥有id = 1的读锁A,因此进入互相等待状态,造成死锁。

解决方案:

1 将select操作改为select for update,直接加写锁。

2 在业务层将此种类事务串行化。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容