结论是:别用单列 auto_increment 表模拟 sequence,真正可用方案是「命名序列表 + last_insert_id(expr) 原子更新」,因其原子、无锁、可回滚、支持步长且不撑爆表;而 insert into seq() values() + last_insert_id() 会导致空洞、死锁、主从延迟与清理困难。

直接说结论:别用单列 AUTO_INCREMENT 表模拟 Sequence,它会撑爆表、无法回滚、不支持步长和 CURRVAL,高并发还容易死锁。真正可用的方案是「命名序列表 + LAST_INSERT_ID(expr) 原子更新」。
为什么不能用 INSERT INTO seq() VALUES() + LAST_INSERT_ID()
这是最常见也最危险的“伪方案”:
- 每次调用都写一行,
seq表几小时就上万行,清理难、备份慢、主从延迟加剧 - 事务失败后,
INSERT已分配的 ID 不可回收,产生空洞(比如你期望 1→2→3,实际可能是 1→3→4) -
SELECT LAST_INSERT_ID()在跨连接时不可靠——如果中间有其他INSERT,值就被覆盖了 - 并发执行
INSERT ... SELECT类操作,可能触发间隙锁冲突,导致死锁ERROR 1213 (HY000): Deadlock found when trying to get lock
推荐做法:用 UPDATE ... SET current_val = LAST_INSERT_ID(current_val + increment_val)
核心是利用 MySQL 的会话级 LAST_INSERT_ID() 函数特性——它只影响当前连接,且赋值与 UPDATE 绑定,原子、无锁、可回滚。
建表语句(带步长和命名支持):
CREATE TABLE `sequence` ( `seq_name` VARCHAR(50) NOT NULL PRIMARY KEY, `current_val` BIGINT NOT NULL DEFAULT 1, `increment_val` INT NOT NULL DEFAULT 1 ) ENGINE=InnoDB;
获取下一个值(等效 Oracle NEXTVAL):
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
UPDATE `sequence` SET `current_val` = LAST_INSERT_ID(`current_val` + `increment_val`) WHERE `seq_name` = 'order_seq'; SELECT LAST_INSERT_ID();
- 同一连接内,后续多次调用
LAST_INSERT_ID()都返回同一个值,不用再查表 - 不同连接并发执行该
UPDATE,互不影响,无锁竞争 - 改
increment_val字段就能动态调步长,比如UPDATE sequence SET increment_val = 10 WHERE seq_name = 'batch_seq' - 事务中执行该逻辑,若事务
ROLLBACK,current_val不变,也不会消耗 ID
需要格式化输出(如 'ORD-000123')时必须加锁
一旦涉及前缀、补零、长度控制,就必须读取当前值再计算,这时 LAST_INSERT_ID() 就不够用了——因为读和写分离,中间可能被其他连接修改。
此时必须用 SELECT ... FOR UPDATE 显式加行锁:
BEGIN;
SELECT `current_val`, `increment_val` FROM `sequence`
WHERE `seq_name` = 'order_seq' FOR UPDATE;
-- 计算新值、拼接字符串,例如 CONCAT('ORD-', LPAD(@next, 6, '0'))
UPDATE `sequence` SET `current_val` = @next WHERE `seq_name` = 'order_seq';
COMMIT;
- 没加
FOR UPDATE就直接SELECT再UPDATE,高并发下大概率生成重复流水号 - 锁粒度是行级,只要
seq_name是主键,不会锁整张表 - 务必在事务内完成读-算-写,否则锁释放后仍可能被覆盖
不要依赖函数封装(如 nextval('xxx'))
MySQL 函数里不能包含 UPDATE + SELECT 混合逻辑(尤其涉及 FOR UPDATE),而且函数调用隐式开启只读事务,容易踩坑:
-
CREATE FUNCTION nextval(...)中用SELECT ... FOR UPDATE会报错Function 'nextval' is not allowed to modify tables - 即使绕过限制(如用存储过程),函数调用栈深、调试困难,错误堆栈指向函数内部而非业务 SQL
- MyBatis 等框架对函数返回值处理不稳定,尤其
mode=OUT在某些驱动版本下失效
更稳的做法是把序列逻辑写进业务 SQL 或 DAO 层,明确控制事务边界和锁行为。
真正麻烦的不是怎么生成数字,而是什么时候要锁、锁多细、格式化逻辑放哪——这些细节一漏,线上就出重号。










