SQL 事务与锁机制教程

1次阅读

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

SQL 事务与锁机制教程

事务没生效,COMMIT 后数据还是回滚了?

根本原因往往是自动提交(autocommit)没关,或者连接被框架/驱动悄悄重置了。mysql 默认开启 autocommit,执行单条 INSERT/UPDATE 会立刻落盘,BEGINCOMMIT 形同虚设。

实操建议:

  • 显式关闭 autocommit:SET autocommit = 0;(MySQL),或在连接字符串里加 ?autocommit=false
  • 确认事务边界是否被 ORM 干预:djangotransaction.atomic()spring@Transactional 都可能覆盖手动控制
  • 检查客户端是否异常断连——未收到 COMMIT 确认就断开,服务端通常会回滚活跃事务
  • SELECT @@autocommit;SELECT TRX_ID, TRX_STATE FROM INFORMATION_SCHEMA.INNODB_TRX; 实时验证状态

SELECT ... FOR UPDATE 锁不住行?

常见于 WHERE 条件没走索引,导致升级为表锁,或者锁范围超出预期(比如范围查询锁住间隙)。InnoDB 的行锁依赖索引,没索引=全表扫描=锁表。

实操建议:

  • EXPLAIN SELECT ... 确认是否命中索引;主键、唯一索引最安全,普通索引需注意 typeref 而非 indexALL
  • 避免在 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 判断是否存在,再决定 INSERTUPDATE ——但要加 FOR UPDATE,否则并发下仍有竞态

锁的粒度、时机、释放点,全都藏在存储引擎实现和 SQL 执行计划里。调参改配置不如先 EXPLAIN 一眼看清实际走了哪条路。

text=ZqhQzanResources