什么是Cursor Lost Update?如何在MySQL中复现该问题?
什么是Cursor Lost Update?
Cursor Lost Update是一种基于数据库游标操作的特殊丢失更新问题:当事务通过游标读取某行数据的快照后,另一个事务修改并提交了该行数据,而第一个事务后续基于游标读取的旧值(快照)对该行进行更新时,会直接覆盖掉第二个事务的修改,且这种情况无法被ANSI SQL定义的REPEATABLE READ隔离级别阻止。
它和普通丢失更新的核心区别在于:触发场景是游标读取与后续更新的组合——游标提供的快照读保证了事务内查询的一致性,但后续基于游标定位的更新操作,并不会检测其他事务对该行的已提交修改,最终导致更新丢失。
如何在MySQL中复现Cursor Lost Update?
准备测试环境
首先创建测试表并插入初始数据:
CREATE TABLE account ( id INT PRIMARY KEY, balance INT NOT NULL ); INSERT INTO account VALUES (1, 100);
分两个事务执行操作
打开两个MySQL客户端窗口,分别模拟事务A和事务B:
事务A(游标操作方)
- 启动事务并设置默认隔离级别(REPEATABLE READ):
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION;
- 声明可更新游标并读取目标行数据:
-- MySQL中满足单表、无聚合/分组的SELECT可创建可更新游标 DECLARE cur CURSOR FOR SELECT balance FROM account WHERE id = 1; OPEN cur; FETCH cur INTO @current_balance; -- 此时@current_balance的值为100
事务B(并发修改方)
- 启动事务、修改数据并立即提交:
START TRANSACTION; UPDATE account SET balance = 200 WHERE id = 1; -- 将余额改为200 COMMIT;
回到事务A
- 基于游标读取的旧值更新目标行:
-- 通过游标定位行并更新,使用读取到的旧值计算新余额 UPDATE account SET balance = @current_balance + 50 WHERE CURRENT OF cur; COMMIT;
- 验证最终结果:
SELECT balance FROM account WHERE id = 1; -- 结果为150,事务B的200被覆盖,更新丢失
关键说明
MySQL的REPEATABLE READ隔离级别通过快照读保证了事务内查询的一致性,但游标读取的快照与后续WHERE CURRENT OF的更新操作之间没有冲突检测机制——即使其他事务已修改并提交该行,事务A仍会基于游标快照的旧值完成更新,从而触发Cursor Lost Update。
内容的提问来源于stack exchange,提问作者Super Kai - Kazuya Ito
相关产品推荐
相关产品推荐

