MySQL无原生Sequence,需用持久化序列表+事务安全自定义函数模拟;建表含name主键及next_val等字段,函数通过UPDATE+SELECT原子递增并返回值,须显式事务、READS SQL DATA声明,避免多次调用或缓存导致跳号。

MySQL里没有原生Sequence,但可以用自定义函数+表模拟
MySQL 8.0 之前确实不支持 CREATE SEQUENCE,哪怕 8.0+ 加了,语法和行为也和 Oracle 差很多(比如不支持 NOCACHE、ORDER,也不保证严格递增)。真要兼容 Oracle 的 Sequence 用法(尤其是多会话并发取号、不跳号、可指定起始值),最稳妥的方式是自己建表 + 自定义函数封装。
必须建一张序列控制表,且字段设计要带事务安全意识
别用单行全局变量或临时表——并发下极易出错。正确做法是建一张带主键的持久表,每条记录对应一个逻辑 sequence 名:
CREATE TABLE mysql_sequence ( name VARCHAR(64) PRIMARY KEY, next_val BIGINT NOT NULL DEFAULT 1, increment_by INT NOT NULL DEFAULT 1, min_value BIGINT NOT NULL DEFAULT 1, max_value BIGINT NOT NULL DEFAULT 9223372036854775807 );
关键点:
-
name是唯一标识,比如'order_seq',后续函数靠它查/更新 -
next_val必须用UPDATE ... WHERE name = ?+SELECT LAST_INSERT_ID()方式读取并原子递增,不能先SELECT再UPDATE(竞态) -
increment_by和min_value/max_value是为了后期扩展,Oracle Sequence 的核心参数都得覆盖到
自定义函数 nextval() 必须用 READ COMMITTED 隔离级 + 显式事务
MySQL 函数内不能直接执行 UPDATE,所以得用存储过程配合函数调用,或者更常用的是:用函数包装一个「带事务的 SELECT」。实际推荐写法是:
DELIMITER $$
CREATE FUNCTION nextval(seq_name VARCHAR(64)) RETURNS BIGINT
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE cur_val BIGINT DEFAULT 0;
START TRANSACTION;
UPDATE mysql_sequence
SET next_val = next_val + increment_by
WHERE name = seq_name;
SELECT next_val INTO cur_val FROM mysql_sequence WHERE name = seq_name;
COMMIT;
RETURN cur_val;
END$$
DELIMITER ;
注意几个坑:
- 函数声明里必须写
READS SQL DATA,否则报错 “Function does not satisfy requirements” - 不能省略
START TRANSACTION—— 单条UPDATE在默认隔离级别下不保证跨会话可见性,尤其高并发时可能读到旧值 - 别用
LAST_INSERT_ID():它只对INSERT生效,UPDATE不触发 - 如果想支持
CACHE行为(减少 IO),就得在函数里缓存一批号,但会牺牲严格连续性,Oracle 默认也是NOCACHE才绝对不跳号
初始化和使用时,nextval('xxx') 要配 INSERT ... VALUES (nextval('xxx'), ...)
初始化新 sequence:
INSERT INTO mysql_sequence (name, next_val, increment_by)
VALUES ('user_id_seq', 1000, 1);
在 INSERT 中直接调用:
INSERT INTO users(id, name) VALUES (nextval('user_id_seq'), 'Alice');
常见错误:
- 在
WHERE或JOIN条件里多次调用nextval()—— 每次都触发递增,导致“取号但不用”,号就丢了 - 把
nextval()放在视图或子查询里,MySQL 可能优化重排执行顺序,引发不可预测递增 - 没提前插入记录就调用,函数会返回
NULL或报错,建议函数内加IF NOT EXISTS判断并自动初始化(需额外逻辑)
真正难的不是写这个函数,而是确保所有业务代码都遵守“一次调用、一次使用”的约定。一旦有人在应用层缓存了 nextval() 结果又重复插入,序列就乱了。


















