普通INSERT语句的自增锁在语句执行完成、自增值分配并写入行记录后立即释放,而非等到事务提交;INSERT...SELECT等批量语句则需等整条语句执行完毕才释放,以保障主从一致性。

普通 INSERT 语句的自增锁释放时机
自增锁(AUTO_INC 锁)在普通 INSERT 语句中,**不是等到事务提交才释放,而是在语句执行完成、自增值分配并写入行记录后立即释放**。这意味着:即使事务 A 还没 COMMIT,事务 B 也能马上获取下一个自增 ID 并开始自己的 INSERT。
这种设计是为了提升并发性能——锁粒度最小化,不阻塞其他事务对同一张表的插入操作。
- 典型普通 INSERT:如
INSERT INTO t (c, d) VALUES (10, 20),或带NULL/0的INSERT INTO t VALUES (NULL, 10, 20) - 释放点非常明确:只要 MySQL Server 层拿到
innodb_next_autoinc值、InnoDB 完成该行的内存构造与日志写入(mtr提交),锁就解除了 - 注意:这里“语句完成”指 SQL 执行引擎层面结束,不是存储引擎内部所有刷盘动作完成
INSERT ... SELECT 为何要等语句结束才释放自增锁
INSERT ... SELECT 是典型的“批量插入语句”,在 innodb_autoinc_lock_mode = 1(默认)下,它的自增锁会**持续到整条语句执行完毕才释放**,而非逐行释放。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
这么做不是为了性能,而是为了主从一致性:当 binlog_format = STATEMENT 时,备库重放 SQL 必须保证插入结果和主库完全一致。如果提前释放锁,多个并发 INSERT ... SELECT 可能交错分配 ID,导致主备记录顺序或内容错位。
- 例如:
INSERT INTO t2 SELECT * FROM t,哪怕t只有 1 行,也会全程持有AUTO_INC锁 - 若想规避长锁等待,可改用
ROW格式 binlog,或拆成小批量INSERT ... VALUES循环 - 设置
innodb_autoinc_lock_mode = 2能让这类语句也立即释放锁,但仅限于所有节点都使用ROW格式且无级联复制场景
自增锁释放与事务回滚的关系
自增锁释放和事务是否回滚**完全无关**:锁早在语句执行阶段就已释放,而回滚发生在事务生命周期后期(ROLLBACK 或崩溃恢复时)。这意味着:
- 事务 A 插入了
id=100后未提交,事务 B 拿到了id=101并成功提交 → 即使 A 最终ROLLBACK,100也不会“回收”,ID 就跳了 - 自增值本身不会因回滚而倒退;MySQL 不会把已分配出去的 ID 收回来重用
- 这也是为什么生产环境常见“自增不连续”——不是 bug,是锁行为 + 回滚 + 崩溃重启共同导致的正常现象
容易被忽略的细节:重启与自增值持久化
MySQL 8.0 之前,自增值只存在内存里;实例重启后,会通过 SELECT MAX(id) + 1 重新推算。这就带来一个隐蔽陷阱:
- 假设表当前最大
id = 99,内存中AUTO_INCREMENT = 105(因为之前插入过 5 行又回滚了) - 此时重启:新值变成
100,后续插入可能复用已被逻辑删除的 ID 区间 - MySQL 8.0+ 通过 redolog 持久化自增值变更,但仅保证“重启后不降”,仍不保证连续
- 如果你依赖自增 ID 做分页、范围查询或下游消费顺序,必须意识到:ID 仅唯一,不保序、不保密、不保连续

















