select buy_num from order_test where order_num=105; 返回橙子购买数量为11!!!对于A事务来说,相当于15+1=11!!
由于普通查询语句未加任何锁,事务A未完成时,其他事务仍可对其所查询的语句进行修改操作,mysql实现的可重复读并不绝对可靠。
测试3(丢失更新):
事务A
事务B
select buy_num from order_test where order_num=105; 返回橙子购买数量为15
update order_test set buy_num= 16 where order_num=105; commit;
将橙子购买数量+1,并提交,此时数据库中橙子数量为16
Update order_test set buy_num=16 where order_num=105;
根据第一步查询出来的橙子数量,业务代码中加1之后,再更新至数据库
两个独立的事务分别对橙子数量+1之后,数据库中的橙子数量只是从15增加到了16!造成了丢失更新的问题。
针对测试2、3的问题,可在第一条查询语句后面加上lock in share mode或者 for update
X(写锁)
S(共享锁)
X
冲突
冲突
S
冲突
兼容
其中lock in share mode将给数据添加共享锁,容易造成死锁,不推荐用于查询出来之后需要在事务内进行更新的场合;for update将给数据添加写锁,推荐使用。下面具体看看为什么添加共享锁容易造成死锁。
测试4(死锁测试):
事务A
事务B
select buy_num from order_test where order_num=105 lock in share mode; 加共享锁
Update order_test set buy_num=1 where order_num=105;加共享锁
Update order_test set buy_num= 1 where order_num=105; 由于事务B对该条记录加了共享锁,所以只能等待事务B提交
Update order_test set buy_num=1 where order_num=105; 由于事务A对该条记录也加了共享锁,所以只能等待事务A提交
两个事务都不能往下执行,互相等待对方释放锁,造成死锁。
如果将lock in share mode 换成 for update(写锁),则不会出现这个问题。
在查询语句后加for update,适用于先查询数据,再根据查询的结果,做业务处理计算出新值后,直接更新前面数据的场合,可以有效防止丢失更新问题,很重要
测试5(间隙锁):
事务A
事务B
select * from order_test where order_num >105 and order_num <124 for update;
update order_test set buy_num=10 where order_num=111; 数据被锁住了,无法更新
insert into order_test values(107,309,'茄子',8,now()); 等待执行,范围内的值也无法插入
update order_test set buy_num=10 where order_num=105; 更新成功
insert into order_test values(90,309,'茄子',8,now()); 插入成功,范围外的写数据不受影响