根本问题是自增主键导致所有INSERT集中写入聚簇索引最右页,形成物理热点;innodb_autoinc_lock_mode=2仅缓解ID分配争抢,无法解决页级写入竞争。

根本问题不是锁没配好,而是所有 INSERT 都往聚簇索引最右页挤——自增主键天然把写压力钉死在物理热点上。调 innodb_autoinc_lock_mode 只能缓解分配 ID 时的争抢,但解决不了“新数据全堆在同一个 B+ 树叶子页”这个底层事实。
为什么 innodb_autoinc_lock_mode = 2 有时仍卡住
它只对 INSERT INTO t VALUES () 这类简单插入跳过 AUTO_INC 锁,但以下情况依然会退化为语句级锁:
-
INSERT INTO t SELECT ...、REPLACE INTO t SELECT ...、LOAD DATA等批量操作,mode=2 下照样锁表直到执行完 -
INSERT ... ON DUPLICATE KEY UPDATE+ 自增字段作为唯一判断依据时,预分配 ID 可能冲突,触发内部重试,反而更慢 - 没确认
binlog_format = ROW,设了 mode=2 会导致从库复制失败(SELECT @@binlog_format必须返回ROW) - 业务逻辑硬依赖 ID 连续性(比如用
id + 1推算下一条),mode=2 下事务回滚、部分插入失败都会留下空洞
避开单点热点:用 (product_id, shard_id) 替代自增主键
这不是加个联合索引,而是重定义聚簇索引结构本身——让相同业务键的数据物理分散。
- 执行
DROP INDEX idx_product_id ON product_stock_shard:只要存在KEY idx_product_id (product_id),所有product_id = X的更新仍会命中同一二级索引页,锁冲突不降反升 - 建表时用
PRIMARY KEY(product_id, shard_id),shard_id TINYINT范围取 0–9 或 0–15,足够打散又不增加存储 - 扣减时应用层随机选分片:
UPDATE product_stock_shard SET stock = stock - 1 WHERE product_id = 1001 AND shard_id = FLOOR(RAND() * 10) - 查总库存必须聚合:
SELECT SUM(stock) FROM product_stock_shard WHERE product_id = 1001,不能依赖单行值
哪些场景该彻底弃用自增主键
当写入模式本身无法拆分(如日志、消息、埋点表),或已出现严重页分裂、redo 写放大时,自增主键就该被替换。
- 用
UUID_SHORT()或雪花 ID 做逻辑主键:有序、紧凑、无锁,避免 UUID() 的无序插入和页分裂 - 关掉自增:
ALTER TABLE log_table MODIFY id BIGINT NOT NULL,应用层生成 ID 后显式插入 - 配合
innodb_change_buffering = inserts:对二级索引的写入延迟合并,降低 IO 压力 - 订单类表别用全局自增 ID 当业务标识,改用
order_no CHAR(32)(雪花 ID 或UUID_SHORT()),主键仍保留自增仅作内部引用
最容易被忽略的陷阱是:表没显式定义主键时,InnoDB 会悄悄用第一个 NOT NULL UNIQUE 列,或建隐藏 6 字节 ROW_ID——这种隐式主键同样会形成物理热点,且更难排查。


















