mysql并发插入时auto_increment主键绝对唯一靠自增锁(表级排他锁)、事务隔离与内存缓存协同保障;锁在insert语句结束即释放,非事务结束;innodb_autoinc_lock_mode=1默认启用预分配,提升性能但会导致跳号;单实例内唯一,多实例需分段或全局id服务。

MySQL 在并发插入时能保证 AUTO_INCREMENT 主键绝对唯一,靠的不是“运气”,而是 InnoDB 存储引擎内置的自增锁(auto-inc lock)+ 事务隔离 + 内存缓存协同控制。它不依赖应用层重试或业务逻辑兜底。
自增锁是表级排他锁,不是行锁
InnoDB 的自增锁本质是轻量级的表级锁(LOCK_AUTO_INC),在执行 INSERT 语句时加锁,直到语句结束才释放——注意,不是事务结束才释放。这意味着:
- 两个并发
INSERT会串行获取自增值,后一个必须等前一个完成赋值、写入、释放锁后才能开始 - 即使事务 A 插入后未提交,事务 B 也能拿到下一个 ID(因为锁只管“生成”,不管“提交”)
- 如果表没有主键或
AUTO_INCREMENT列未建索引(或不是索引首列),MySQL 会直接报错ERROR 1075
自增值缓存让性能不崩,但会跳号
InnoDB 默认启用 innodb_autoinc_lock_mode = 1(连续模式),此时会预分配一批自增值到内存中(由 innodb_autoinc_increment 控制步长,默认为 1)。这带来两个实际影响:
- 高并发下避免频繁加锁,吞吐明显提升
- 若事务回滚,已分配但未使用的 ID 就永久丢失,造成“跳号”(比如插入失败、
ROLLBACK、服务器崩溃重启) - 主从复制中若用 STATEMENT 格式,且从库
auto_inc缓存配置不同,可能引发主从 ID 不一致(建议统一设为2,即交错模式)
分布式多实例下,单靠 AUTO_INCREMENT 必然冲突
如果你有多个 MySQL 实例(如分库分表、读写分离、多活部署),每个实例都用默认 AUTO_INCREMENT,那主键重复是确定性事件,不是概率问题。必须打破“单点生成”依赖:
- 方案一:用
auto_increment_increment和auto_increment_offset手动分段(例如 5 台库设步长 5,offset 分别为 1~5),但扩容/缩容极难维护 - 方案二:改用全局 ID 服务(如 Snowflake、Leaf)、Redis
INCR或 UUID,把 ID 生成彻底移出数据库 - 方案三:业务层生成带时间戳+机器码的有序字符串 ID(如
20260608180200001),但需确保无时钟回拨风险
真正容易被忽略的点是:自增唯一性只在单实例内成立,且它的“连续性”和“可预测性”本身就是副作用,不是设计目标。一旦你开始考虑分库、扩容、灾备,就得立刻放弃对 AUTO_INCREMENT 的信任,而不是试图给它打补丁。











