事务中先UPDATE后INSERT锁更久,因UPDATE加行锁/间隙锁,INSERT需在已持锁范围内做唯一键检查;反之INSERT先执行仅加轻量插入意向锁,UPDATE锁行更少、时间更短。

事务里先 UPDATE 再 INSERT 为什么锁更久
因为 UPDATE 会加行锁(甚至间隙锁),而后续的 INSERT 可能触发唯一键检查或二级索引维护,MySQL 得在已持有的锁范围内做判断——锁不会自动释放,直到事务结束。顺序颠倒后,INSERT 先执行,只加插入意向锁(轻量、范围窄),再 UPDATE 时锁住的行更少、时间更短。
常见错误现象:Deadlock found when trying to get lock 或事务平均耗时突然升高,尤其在高并发写入同一张表时。
- 优先把「不依赖其他操作结果」的写入放在前面,比如日志记录、状态初始化等
INSERT - 把需要查旧值、做条件更新的
UPDATE往后挪,尤其涉及SELECT ... FOR UPDATE后紧跟UPDATE的场景 - 避免在事务开头
UPDATE一张被频繁读写的热点表主键行
WHERE 条件没走索引导致锁全表
哪怕你只改一行,如果 UPDATE 或 DELETE 的 WHERE 条件没命中索引,MySQL 会降级为全表扫描 + 每行加锁,锁持有时间直接和表大小正相关。
使用场景:后台批量修正数据、定时任务里的补偿 SQL、管理后台的“按条件删除”功能。
- 执行前用
EXPLAIN看type是否为const/ref,避免ALL或index - 复合索引要注意最左匹配,
WHERE status = ? AND created_at > ?不能只给status建单列索引 - 时间范围查询慎用
datetime字段做条件——若没索引,10万行表可能锁住几秒
事务里调用存储过程或函数容易隐式延长锁期
存储过程内部如果有未显式提交的 DML、或者调用了另一个事务性操作,会导致当前事务的锁持续到过程结束。更隐蔽的是,某些内置函数如 SLEEP()、UUID() 虽然不改数据,但会拖长事务生命周期,间接拉长锁持有时间。
性能影响:一个本应 20ms 完成的事务,因嵌套了无必要延迟的函数,变成 300ms,QPS 直接跌掉 50%。
- 禁用事务内
SLEEP()做重试等待,改用应用层控制 - 避免在事务中调用含 DML 的自定义函数,除非确认它只读且无副作用
- 用
SHOW ENGINE INNODB STATUS查TRANSACTIONS部分,看trx_mysql_thread_id对应的事务是否卡在函数调用上
批量操作别在一个事务里塞 5000 条 INSERT
不是事务越大越快。InnoDB 的 undo log、锁结构、事务内存开销都随语句数线性增长;超过一定阈值(通常 500–1000 行),单事务失败回滚成本远高于拆分成多个小事务。
兼容性影响:MySQL 5.7 默认 innodb_log_file_size 较小,超大事务可能触发 log sequence number 写满告警;8.0 虽优化不少,但锁粒度仍受事务长度影响。
- 按主键/索引字段分片,每批 100–500 行,用
INSERT INTO ... VALUES (...), (...), (...)批量插入 - 避免在循环里逐条
INSERT却共用一个事务,那等于没控制锁范围 - 如果必须原子性,考虑用临时表 +
REPLACE INTO或INSERT ... ON DUPLICATE KEY UPDATE替代长事务
最容易被忽略的一点:应用层拿到数据库连接后,习惯性开启事务却忘了在最后 COMMIT 或 ROLLBACK ——这种空挂事务会让锁一直留着,连 SHOW PROCESSLIST 都看不出明显异常,只能靠 information_schema.INNODB_TRX 排查。










