事务未生效主因是autocommit未关闭或连接被重置;select … for update锁不住行多因where未走索引导致表锁;死锁需通过show engine innodb status分析锁持有与等待关系。

事务没生效,COMMIT 后数据还是回滚了?
根本原因往往是自动提交(autocommit)没关,或者连接被框架/驱动悄悄重置了。mysql 默认开启 autocommit,执行单条 INSERT/UPDATE 会立刻落盘,BEGIN 和 COMMIT 形同虚设。
实操建议:
- 显式关闭 autocommit:
SET autocommit = 0;(MySQL),或在连接字符串里加?autocommit=false - 确认事务边界是否被 ORM 干预:django 的
transaction.atomic()、spring 的@Transactional都可能覆盖手动控制 - 检查客户端是否异常断连——未收到
COMMIT确认就断开,服务端通常会回滚活跃事务 - 用
SELECT @@autocommit;和SELECT TRX_ID, TRX_STATE FROM INFORMATION_SCHEMA.INNODB_TRX;实时验证状态
SELECT ... FOR UPDATE 锁不住行?
常见于 WHERE 条件没走索引,导致升级为表锁,或者锁范围超出预期(比如范围查询锁住间隙)。InnoDB 的行锁依赖索引,没索引=全表扫描=锁表。
实操建议:
- 先
EXPLAIN SELECT ...确认是否命中索引;主键、唯一索引最安全,普通索引需注意type是ref而非index或ALL - 避免在
FOR UPDATE查询中用函数或表达式过滤,如WHERE YEAR(create_time) = 2024会失效索引 - 注意隔离级别影响:READ COMMITTED 下,非唯一条件的
FOR UPDATE可能只锁查到的行;REPEATABLE READ 下还会锁间隙(防止幻读) - 测试时用两个会话:一个执行
SELECT ... FOR UPDATE不提交,另一个尝试更新同一行,看是否阻塞
死锁日志里出现 WAITING FOR this LOCK TO BE GRANTED 怎么定位?
MySQL 的 SHOW ENGINE INNODB STATUSG 输出里,死锁段落会明确列出两个事务各自持有什么锁、等待什么锁。关键不是看谁“先”执行,而是看加锁顺序是否构成环路。
实操建议:
- 重点看
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:和*** (2) HOLDS THE LOCK(S):两段,对照 SQL 判断访问顺序 - 典型模式:事务 A 先锁 ID=1 再锁 ID=2,事务 B 反过来——只要两个事务并发执行,就必然死锁
- 统一 DML 顺序是根治法:比如所有业务都按
ORDER BY id ASC更新一批记录 - 避免在事务里做 rpc、文件读写等长耗时操作,拉长持有锁的时间窗口
高并发下 INSERT ... ON DUPLICATE KEY UPDATE 为什么还报唯一键冲突?
这不是事务问题,而是 INSERT 操作本身在重复键时触发了唯一约束检查,而该检查发生在语句执行期,和事务隔离级别无关。即使你包在 BEGIN/COMMIT 里,也拦不住这个校验。
实操建议:
- 确认
ON DUPLICATE KEY UPDATE的“key”确实是唯一索引或主键——普通索引不触发此逻辑 - 如果想避免报错,必须用
INSERT IGNORE或捕获1062 Duplicate entry错误码后重试 - 注意:该语句在 REPEATABLE READ 下可能引发间隙锁竞争,尤其在插入不存在的范围时,容易卡住其他插入
- 替代方案:先
SELECT判断是否存在,再决定INSERT或UPDATE——但要加FOR UPDATE,否则并发下仍有竞态
锁的粒度、时机、释放点,全都藏在存储引擎实现和 SQL 执行计划里。调参改配置不如先 EXPLAIN 一眼看清实际走了哪条路。