事务粒度应最小化,仅包含必要dml操作;避免在事务中执行查询、日志、http调用等非原子性操作;合理设置隔离级别(优先repeatable read);确保锁操作命中索引;批量操作需分批提交。

事务粒度越小越好,别把查询也塞进事务里
高并发下事务长时间持有锁是性能杀手。常见错误是把 SELECT 查询、日志记录、HTTP 调用等非必要操作包进 BEGIN ... COMMIT 块里,导致行锁/间隙锁持有时间远超实际写入所需。
实操建议:
- 只把真正需要原子性保证的 DML(
INSERT/UPDATE/DELETE)放在事务内 - 读多写少场景优先用
SELECT ... FOR UPDATE替代先查后更,但必须加索引覆盖,否则会升级为表锁 - 避免在事务中调用外部服务或执行耗时计算,拆到事务外做
隔离级别别盲目设成 SERIALIZABLE
MySQL 默认 REPEATABLE READ 已能解决大部分幻读问题,而 SERIALIZABLE 会让所有 SELECT 隐式加读锁,极大加剧锁冲突,QPS 直接腰斩。
判断依据:
- 业务真需要强一致性且能接受吞吐下降?→ 才考虑
SERIALIZABLE - 多数支付、库存扣减用
REPEATABLE READ+ 合理索引 +SELECT ... FOR UPDATE就够 -
READ COMMITTED在某些日志类、统计类事务中更轻量,但注意它不支持间隙锁,可能引发幻读
索引没建对,事务就是自缚手脚
事务中 UPDATE 或 SELECT ... FOR UPDATE 如果走不到索引,InnoDB 会锁整张表或全范围间隙,高并发下直接堵死。
检查要点:
- 用
EXPLAIN确认事务内所有带锁语句是否命中索引,尤其注意隐式类型转换(如字符串字段传数字)导致索引失效 - 复合索引要遵循最左前缀,
WHERE a = ? AND b = ?需要(a,b)而非单独a索引 - 避免在事务中使用
LIKE '%xxx'或函数索引(如WHERE DATE(create_time) = '2024-01-01'),它们无法利用索引下推
批量操作别用循环单条事务
100 条记录逐条 INSERT 开 100 次事务,网络往返 + 日志刷盘开销爆炸。但全塞进一个事务又可能触发 innodb_log_file_size 限制或长事务阻塞 MVCC 清理。
折中方案:
- 分批提交:每 100–500 条用一个事务,具体看单条数据大小和
innodb_log_buffer_size - 用
INSERT ... VALUES (...), (...), (...)替代多条单值INSERT - 确认
autocommit=0时手动控制,别依赖框架默认行为——有些 ORM 默认每条 SQL 自动提交
事务优化最易被忽略的一点:锁等待超时不是调大 innodb_lock_wait_timeout 就能解决的,得先知道谁在等、等什么锁。上线前务必用 SELECT * FROM performance_schema.data_lock_waits 或 SHOW ENGINE INNODB STATUS 抓真实锁链。











