空表并发insert必死锁,因innodb在repeatable read下所有事务争抢(-∞, +∞)间隙的insert intention lock;应改用insert ... on duplicate key update、批量插入、auto_increment生成订单号,并确保唯一索引字段顺序与查询条件一致。

空表并发INSERT必死锁,不是配置能调的
空表或极低数据量时,INSERT在REPEATABLE READ隔离级别下必然死锁——这不是运气差,而是InnoDB的Next-Key Lock机制决定的。所有事务都在(-∞, +∞)这个唯一间隙争抢INSERT INTENTION LOCK,形成确定性循环等待。调大innodb_lock_wait_timeout没用,重试只是掩盖问题。
用INSERT ... ON DUPLICATE KEY UPDATE替代查再插
业务层常见的SELECT FOR UPDATE → 判断 → INSERT三步法,极易引发锁链环路。换成原子操作能直接绕过间隙锁冲突:
- 必须确保字段有
UNIQUE KEY或PRIMARY KEY(比如order_no建唯一索引) - 示例:
INSERT INTO orders (order_no, user_id) VALUES ('ORDER123', 1001) ON DUPLICATE KEY UPDATE user_id = VALUES(user_id) - 它仍会申请插入意向锁,但不触发额外的SELECT锁,大幅降低死锁概率
- 注意:若业务需要严格控制“只插入不更新”,则该语法不适用
批量写入+事务拆分,避免单事务锁太久
高频单条INSERT不仅放大死锁风险,还拖慢吞吐。批量写入是硬性优化点:
- 用
INSERT INTO ... VALUES (), (), ()一次插50–500行,具体值需结合单行大小压测 - 每批提交后立刻开启新事务,避免长事务持有锁、膨胀undo log
- 拆分依据建议按业务维度(如
user_id % 100)而非简单行数,减少跨批依赖 - 禁用
FOREIGN_KEY_CHECKS和UNIQUE_CHECKS仅限离线导入,线上慎用
订单号生成别碰SELECT MAX(),交给AUTO_INCREMENT
用SELECT MAX(order_no) FROM orders再加1生成订单号,在并发下必定竞态。正确做法是:
- 让
order_id主键用AUTO_INCREMENT,保证唯一递增 - 订单号逻辑拆解为前缀(如
'ORD2026')+序列(LAST_INSERT_ID()或应用层缓存递增值) - 避免在触发器里做
SELECT ... ORDER BY ... LIMIT 1,同样面临并发读一致性问题 - 如果必须全局有序且带业务含义,考虑Snowflake或数据库号段服务,而非DB层自增模拟











