能否通过MySQL Workbench复现丢失更新?代码问题排查
嘿,我来帮你搞定这个丢失更新的复现问题~ 答案是完全可以用MySQL Workbench复现,但你的当前语句逻辑有几个关键问题,导致没法触发典型的丢失更新场景,我来一步步拆解:
你的代码核心问题在哪?
1. 原子UPDATE不会触发丢失更新
你原代码里用的是UPDATE table SET Number = Number + 10这种写法——这是MySQL的原子性行更新,它会直接基于当前磁盘上的最新行版本来计算,不管你之前有没有执行过SELECT。哪怕事务B在sleep期间完成了更新提交,事务A的UPDATE也会用事务B修改后的最新值来计算,自然不会出现“丢失更新”的情况。
丢失更新的本质是:应用层读取了旧数据,基于旧值计算后再去更新,此时数据已经被其他事务修改了,而不是让数据库直接做字段的原子增减。
2. SELECT ... FOR UPDATE阻塞是正常行为
这个语句会给查询到的行加排他锁,事务B的UPDATE需要获取同一行的排他锁,必然会被阻塞,这种锁机制直接阻止了并发修改,当然没法复现丢失更新了。
正确的复现步骤(用MySQL Workbench)
我们需要模拟“应用层读旧值→基于旧值计算→更新”的场景,具体操作如下:
第一步:准备测试表
先在任意一个查询标签页执行,创建测试数据:
CREATE TABLE test_user ( FirstName VARCHAR(50) PRIMARY KEY, Number INT NOT NULL DEFAULT 0 ); INSERT INTO test_user (FirstName, Number) VALUES ('Name1', 100);
第二步:打开两个独立的查询标签页(对应事务A和事务B)
事务A的操作步骤:
- 设置隔离级并启动事务:
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; START TRANSACTION; - 读取
Number的值,手动记下这个值(比如这里是100):SELECT Number FROM test_user WHERE FirstName = 'Name1'; - 执行sleep,留出10秒给事务B操作:
SELECT SLEEP(10); - 基于刚才记下的旧值(100)计算后更新(注意不要用
Number = Number +10,直接写计算结果):UPDATE test_user SET Number = 110 WHERE FirstName = 'Name1'; COMMIT;
事务B的操作步骤(必须在事务A执行SELECT SLEEP(10)的10秒内完成):
- 同样设置隔离级并启动事务:
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; START TRANSACTION; - 读取同一行的
Number值(此时读到的是初始值100):SELECT Number FROM test_user WHERE FirstName = 'Name1'; - 基于读到的100计算后更新:
UPDATE test_user SET Number = 95 WHERE FirstName = 'Name1'; COMMIT;
第三步:验证结果
等两个事务都提交后,查询test_user表:
SELECT * FROM test_user WHERE FirstName = 'Name1';
你会发现最终Number的值要么是110,要么是95——这就意味着其中一个事务的修改被另一个覆盖了,也就是丢失更新现象成功复现。
补充小知识点
- 其实不需要刻意用
READ UNCOMMITTED隔离级,在MySQL默认的READ COMMITTED隔离级下,同样能复现这个场景,因为事务B读到的也是事务A修改前的初始值,一样会基于旧值更新导致丢失。 - 要避免丢失更新,常见的方案有:用
SELECT ... FOR UPDATE加锁(就是你之前试过的阻塞方案)、用乐观锁(比如加个版本号字段)、或者用数据库的原子更新语句(也就是你原代码里的写法)。
内容的提问来源于stack exchange,提问作者tk5
相关产品推荐
相关产品推荐

