MySQL默认REPEATABLE READ无法避免更新丢失,需用SELECT ... FOR UPDATE加行锁确保库存扣减等并发操作安全,且须关闭autocommit、统一加锁顺序、捕获死锁重试。

事务的隔离级别直接影响并发修改行为
MySQL 默认的 REPEATABLE READ 隔离级别不能完全避免“更新丢失”——两个事务读取同一行后各自修改并提交,后提交的会覆盖前一个的修改结果。这不是 bug,而是该级别下不加锁读(快照读)导致的逻辑冲突。
真正能阻止并发覆盖的,是让读操作带上写锁,即使用 SELECT ... FOR UPDATE 或 SELECT ... LOCK IN SHARE MODE。它们只在当前事务持有锁期间生效,且仅对索引列起作用(无索引会锁全表)。
- 必须确保
WHERE条件命中索引,否则升级为表级锁,严重拖慢并发性能 -
FOR UPDATE是排他锁,其他事务无法读或写该行;LOCK IN SHARE MODE允许其他事务加共享锁,但不允许加排他锁 - 锁会在事务结束(
COMMIT或ROLLBACK)时自动释放,不能手动解锁
典型场景:库存扣减必须用事务+行锁
电商下单扣库存是最常见的并发修改问题。如果只用 UPDATE product SET stock = stock - 1 WHERE id = 123 AND stock >= 1,看似原子,但多个请求同时执行时仍可能超卖——因为 WHERE 判断和赋值不是同一个原子锁操作。
正确做法是先显式加锁再判断再更新:
START TRANSACTION; SELECT stock FROM product WHERE id = 123 FOR UPDATE; -- 应用层检查 stock 是否足够 UPDATE product SET stock = stock - 1 WHERE id = 123; COMMIT;
注意:SELECT ... FOR UPDATE 必须在 UPDATE 前执行,且在同一事务中;如果应用层判断失败,记得 ROLLBACK 释放锁。
死锁不是配置问题,是加锁顺序不一致导致的
当事务 A 先锁行 100 再锁行 200,而事务 B 先锁行 200 再锁行 100,就可能触发 MySQL 的死锁检测并回滚其中一个事务。错误日志里会出现 Deadlock found when trying to get lock。
程序介绍:程序采用.net 2.0进行开发,全自动应用淘客api,自动采集信息,无需,手工更新,源码完全开放。(程序改进 无需填入阿里妈妈淘客API 您只要修改app_code文件下的config.cs文件中的id为你的淘客id即可)针对淘客3/300毫秒的查询限制,系统采用相应的解决方案,可以解决大部分因此限制带来的问题;程序采用全局异常,避免偶尔没考虑到的异常带来的问题;程序源码全部开放,请使
- 所有涉及多行更新的事务,必须约定统一的加锁顺序(例如按主键 ID 升序)
- 尽量减少事务内操作步骤,避免在事务中调用外部服务或做耗时计算
- 捕获
Deadlock found when trying to get lock错误,在应用层做有限重试(如 3 次),不要无限循环
autocommit 关闭是前提,但别忘了显式控制
MySQL 客户端默认开启 autocommit=1,此时每个语句都是独立事务,SELECT ... FOR UPDATE 加的锁在语句结束就释放,起不到保护作用。
必须先执行:
SET autocommit = 0; START TRANSACTION;
然后才执行带锁查询和后续更新。漏掉 START TRANSACTION 或忘记 COMMIT,会导致连接长期持有锁、阻塞其他事务,甚至引发连接池耗尽。
最容易被忽略的是:ORM 框架(如 Django、SQLAlchemy)通常封装了事务控制,但若手动拼 SQL 并用原生连接执行,必须自己管理 autocommit 和事务边界。









