订单表按 user_id 建了普通索引,两个事务同时插同一用户的记录,后一个 insert 卡住 50 秒后报 1213,重试还是同一条被回滚。乍看像缓存或者权限的锅,追下去往往不是。

SHOW VARIABLES LIKE 'transaction_isolation';
SELECT ENGINE_TRANSACTION_ID, OBJECT_NAME, LOCK_MODE, LOCK_DATA
FROM performance_schema.data_locks WHERE LOCK_MODE LIKE '%GAP%';

追下去是这么回事:可重复读下,普通索引的等值范围会加 next-key 锁,锁住记录之间的空隙。两个事务各持一个间隙锁再申请对方的,就形成了环。唯一索引上的等值插入才会退化成记录锁,普通索引不会。

「间隙锁插入等待」这个问题文档写得含糊,实际行为以当前版本为准,别照旧版本的结论套。一次只改一处,改完立刻验证,几个改动叠在一起,出问题就说不清是谁导致的。

具体做法是,把插入条件改成命中唯一索引 让等值插入只加记录锁;确认隔离级别是不是 RR 业务允许就降到 RC;把同一个用户的写入收拢到一个队列 串行化冲突;用 data_locks 看 LOCK_GAP 标记 确认锁的是哪段空隙。

验收就看一点,并发插同一条记录不再出现 1213 两个事务一前一后成功 data_locks 里没有 LOCK_GAP 行。

作者 9823189