1:引言

两个事务针对同一数据都发生修改操作时,会存在丢失更新问题。

1:转账实例

时间

取款事务A

转账事务B

T1

开始事务


T2


开始事务

T3

查询账户余额为1000元


T4


查询账户余额为1000元

T5


汇入100元把余额改为1100元

T6


提交事务

T7

取出100元把余额改为900元


T8

撤销事务


T9

余额恢复为1000元(丢失更新)


时间

取款事务A

转账事务B

T1


开始事务

T2

开始事务


T3


查询账户余额为1000元

T4

查询账户余额为1000元


T5


取出100元把余额改为900元

T6


提交事务

T7

汇入100元


T8

提交事务


T9

把余额改为1100元(丢失更新)


2:统计存款总额实例

mysql并发修改 事务 死锁 mysql 事务 高并发_mysql并发修改 事务 死锁

2:解决方案1:LBCC

使用LBCC(LBCC,基于锁的并发控制,英文全称Lock Based Concurrency Control)可以解决上述的问题。查询总额事务会对读取的行加锁,等到操作结束后再释放所有行上的锁。因为用户A的存款被锁,导致转账操作被阻塞,直到查询总额事务提交并将所有锁都释放。

使用锁机制:

mysql并发修改 事务 死锁 mysql 事务 高并发_mysql并发修改 事务 死锁_02

这种方案比较简单粗暴,就是一个事务去读取一条数据的时候,就上锁,不允许其他事务来操作。MySQL加锁之后就是当前读。假如当前事务只是加共享锁,那么其他事务就不能有排他锁,也就是不能修改数据;而假如当前事务需要加排他锁,那么其他事务就不能持有任何锁。

总而言之,能加锁成功,就确保了除了当前事务之外,其他事务不会对当前数据产生影响,所以自然而然的,当前事务读取到的数据就只能是最新的,而不会是快照数据(后文MVCC会解释快照读概念)。 

3:解决方案2:MVCC

当然使用MVCC(MVCC,多版本的并发控制,英文全称:Multi Version Concurrency Control)机制可以解决这个问题。查询总额事务先读取了用户A的账户存款,然后转账事务会修改用户A和用户B账户存款,查询总额事务读取用户B存款时不会读取转账事务修改后的数据,而是读取本事务开始时的数据副本(在REPEATABLE READ隔离等级下)。

mysql并发修改 事务 死锁 mysql 事务 高并发_database_03

 

MVCC使得数据库读不会对数据加锁,普通的SELECT请求不会加锁,提高了数据库的并发处理能力。借助MVCC,数据库可以实现READ COMMITTED,REPEATABLE READ等隔离级别,用户可以查看当前数据的前一个或者前几个历史版本,保证了ACID中的I特性(隔离性)。