不能。表级锁+insert...select无法保证sequence唯一;必须用单行记录+select...for update+update原子操作,确保并发下“读-算-写”不被干扰。

MySQL 表级锁 + INSERT ... SELECT 能否保证 sequence 唯一?
不能直接依赖。单纯用 INSERT INTO seq_table VALUES () 配合 AUTO_INCREMENT 是安全的,但若想自定义步长、重置、或跨表复用逻辑(比如订单号前缀+递增数),就必须手动维护一个 sequence 记录。此时并发写入极易导致重复值——哪怕加了 SELECT ... FOR UPDATE,如果没锁住同一行或事务隔离级别不足,仍可能读到旧值后写入相同新值。
最简可靠的方案:单行记录 + SELECT ... FOR UPDATE + UPDATE
核心是让所有线程竞争同一行,并在事务内完成“读-算-写”原子操作。建一张只有一行的表:
CREATE TABLE `sequence` ( `id` TINYINT PRIMARY KEY DEFAULT 1, `value` BIGINT NOT NULL DEFAULT 0 ) ENGINE=InnoDB;
每次获取下一个值时执行:
BEGIN; SELECT `value` FROM `sequence` WHERE `id` = 1 FOR UPDATE; -- 应用层计算 next = value + 1 UPDATE `sequence` SET `value` = next WHERE `id` = 1; COMMIT;
关键点:
-
FOR UPDATE在REPEATABLE READ或READ COMMITTED下都有效,但必须确保 WHERE 条件命中索引(id是主键,没问题) - 不能省略
BEGIN和COMMIT,否则锁在语句结束就释放,失去保护意义 - 应用层必须检查
UPDATE影响行数是否为 1,否则说明中间有异常(如连接断开、超时),需重试
为什么不用 REPLACE INTO 或 INSERT ... ON DUPLICATE KEY UPDATE?
这类语句看似能“自动处理冲突”,但它们不适用于纯递增场景:
-
REPLACE INTO先删后插,会改变AUTO_INCREMENT值,且无法控制递增值 -
INSERT ... ON DUPLICATE KEY UPDATE value = value + 1看似可行,但存在竞态:两个线程同时执行该语句,可能都读到旧值 A,然后都更新成 A+1,最终只+1而非+2 - 更隐蔽的问题是:如果事务中先做了其他 DML,再执行这个语句,MySQL 可能对同一行加多次锁,引发死锁风险
性能瓶颈在哪?如何缓解?
单行锁天然成为热点,QPS 上千后延迟明显上升。缓解方式有限但实用:
- 批量预取:应用层一次取 100 个值(
UPDATE ... SET value = value + 100),缓存在内存里逐个分配,降低数据库往返次数 - 分段 sequence:按业务维度拆(如
order_2024,refund_2024),每类独立一行,分散锁竞争 - 避免在大事务中调用 sequence:它本应是轻量原子操作,嵌套在长事务里会延长锁持有时间,拖累整体并发
真正难处理的是锁等待超时和连接中断后的状态恢复——你得知道上次到底有没有成功写入,否则可能跳号或重号。这需要配合日志或幂等标记,不是单靠 SQL 能解决的。











