如何使无事务/自动提交的普通SELECT在MariaDB中受行锁限制?
问题场景
我有两个基于PHP7 / 10.4.14-MariaDB的脚本,均会更新数据库中的同一数据。Script1使用事务,Script2不使用事务,且Script1的执行时间略早于Script2。
两者的伪代码如下:
Script 1
$objDb->startTransaction(); $objDb->query("select ID,name from table1 where name='nameB' limit 1 FOR UPDATE "); if($objDb->totalRows()>0) { $objDb->get(); $objDb->query("update table1 set name ='nameBB' where ID=".$objDb->row['ID']." "); } sleep(3); $objDb->commit();
Script 2
$objDb->query("select ID,name from table1 where name='nameB' limit 1"); if($objDb->totalRows()>0) { $objDb->get(); $objDb->query("update table1 set name ='nameCC' where ID=".$objDb->row['ID']." "); }
若给Script2添加事务,最终数据库值为'nameBB',符合预期(Script2会等待Script1提交);但当前Script2无事务时,最终值为'nameCC',显然未针对table1的对应ID施加读锁。请问如何确保无事务/自动提交的普通SELECT被施加读锁?
解决方案
要让无事务(自动提交模式)下的普通SELECT被施加读锁,从而被Script1的FOR UPDATE锁阻塞,可通过以下两种方式实现:
1. 给SELECT语句添加锁关键字
直接修改Script2的SELECT语句,加上FOR UPDATE或LOCK IN SHARE MODE锁关键字:
$objDb->query("select ID,name from table1 where name='nameB' limit 1 FOR UPDATE");
FOR UPDATE会添加排他锁,与Script1的锁类型一致,此时Script2的查询会等待Script1提交事务后才能执行,后续更新也会基于Script1提交后的最新数据。LOCK IN SHARE MODE会添加共享锁,同样会被Script1的排他锁阻塞,等待锁释放后再读取数据,也能达到避免覆盖更新的效果。
2. 调整会话事务隔离级别
如果不想修改SQL语句,可将当前会话的事务隔离级别调整为SERIALIZABLE:
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
在SERIALIZABLE隔离级别下,自动提交模式的SELECT会隐式添加共享锁,等效于执行SELECT ... LOCK IN SHARE MODE,会被Script1的FOR UPDATE锁阻塞,保证读取到一致的数据,避免后续更新覆盖Script1的结果。
注意:调整隔离级别会影响整个会话的所有查询,需结合业务场景评估性能影响,避免引入不必要的锁等待。
内容的提问来源于stack exchange,提问作者Patrick
相关产品推荐
相关产品推荐

