MySQL中UUID()不适合主键,因引发页分裂和索引低效;可靠ID生成应采用UPDATE+SELECT原子操作配合id_seq表、READ COMMITTED隔离级别及批量预占step,避免存储过程瓶颈。

直接用 UUID() 不行,但别急着写存储过程
多数人一上来就想用存储过程“自己造ID”,结果发现并发一高就锁表、慢、甚至重复。其实 UUID() 是MySQL内置最轻量的唯一ID生成方式,但它不适合当主键——InnoDB 写入时页分裂严重,VARCHAR(36) 索引效率低,JOIN也拖慢。它只适合做 trace_id 或临时凭证。真要主键级ID,得从可控性、有序性和并发安全三方面下手。
UPDATE ... SELECT 原子操作是唯一可靠路径
MySQL 8.0 不支持序列对象(SEQUENCE),也没原生 GET_NEXT_VALUE(),所以必须靠单条 UPDATE 更新计数器 + 紧接着 SELECT 读出新值,靠事务隔离级别兜底。关键点不在“写得多”,而在“更新和读必须在同一个事务里原子完成”。
- 建一张专用表:
CREATE TABLE id_seq (name VARCHAR(32) PRIMARY KEY, next_val BIGINT NOT NULL DEFAULT 1, step INT NOT NULL DEFAULT 1) - 插入初始化记录:
INSERT INTO id_seq (name, next_val) VALUES ('order_id', 100000) - 存储过程里不能先
SELECT再UPDATE——这中间可能被其他会话插队;必须用UPDATE id_seq SET next_val = next_val + step WHERE name = 'order_id',然后立即SELECT next_val INTO @new_id FROM id_seq WHERE name = 'order_id' - 字段
next_val必须设为BIGINT,避免溢出;step可配成 1000 之类批量预占,减少锁竞争
READ COMMITTED 隔离级别比 REPEATABLE READ 更合适
默认的 REPEATABLE READ 在高并发下容易触发间隙锁(gap lock),导致多个线程争抢同一行时阻塞加剧。而 READ COMMITTED 下,UPDATE 只锁匹配行,不锁范围,吞吐明显提升。
- 执行前加一句:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED - 务必显式开启事务:
START TRANSACTION,否则UPDATE后SELECT可能读到旧值 - 不要依赖
AUTOCOMMIT=1——单条UPDATE虽自动提交,但无法保证“更新后立刻读到自己刚写的值” - 如果业务允许 ID 略微跳跃(比如一次预占 1000 个),把
step设大,能显著降低锁频率
并发超 500 QPS 时,存储过程本身会成瓶颈
MySQL 存储过程不是“无成本”的——每次调用都要解析、压栈、上下文切换。实测在 8 核 32G 的 MySQL 8.0 实例上,纯存储过程生成 ID 的极限约 800–1000 QPS,再往上延迟陡增。这不是逻辑问题,是执行引擎开销。
- 别让应用每次插入都调一次存储过程;改用批量预取:一次取 1000 个 ID 缓存在应用内存里
- 避免在存储过程中做字符串拼接、日期格式化等额外计算——ID 就是数字,其余逻辑放应用层
- 如果已用分库分表,优先考虑
AUTO_INCREMENT+auto_increment_offset+auto_increment_increment组合,比存储过程更稳更快 - 真正高并发(>5k QPS)场景,该上
Redis INCR或Snowflake就上,别硬扛
存储过程生成 ID 的本质是用数据库锁换一致性,它不解决性能问题,只解决“怎么不出错”。一旦并发压力上来,最先卡住的永远是那行 UPDATE,而不是你的业务逻辑。


















