自增id提升innodb插入性能与索引紧凑性,因其递增特性避免页分裂和随机i/o,减少写放大与提升缓存命中;主键小(如int仅4字节)、范围查询高效;但不保证连续,可能因冲突、回滚、批量插入或重启而断号;分布式场景下易主键冲突,宜用snowflake等方案;uuid适用于写多读少且无范围查询场景,需转为binary(16)存储。

为什么自增ID能让InnoDB插入快、索引紧
因为InnoDB的聚簇索引把数据行和主键B+树绑在一起,而自增ID天然递增——新记录总追加到叶子节点末尾,不插在中间,避免页分裂(Page Split)和随机I/O。这不只是“快一点”,而是直接影响磁盘写放大和缓存命中率。
-
AUTO_INCREMENT值每次只申请一个,用完即放,锁粒度小(innodb_autoinc_lock_mode=1是默认且推荐的) - 主键是
INT时仅占4字节;若用VARCHAR(32)或UUID,二级索引叶子节点也得存它,空间直接翻3–9倍 - 范围查询如
WHERE id BETWEEN 1000 AND 2000能走索引连续扫描,I/O次数少;换成无序UUID就得跳着读页
什么时候自增ID会“断号”甚至“跳得离谱”
自增ID不保证连续,这是常态,不是bug。只要发生唯一键冲突或事务回滚,就可能空缺。更隐蔽的是:批量插入(INSERT ... SELECT、REPLACE INTO)在高并发下也可能预分配多段值但只用一部分。
- 执行
INSERT INTO t(c) VALUES (1)报Duplicate key error后,AUTO_INCREMENT仍会从2→3,下次插入就是id=3 - MySQL 5.7及以前重启后靠
SELECT MAX(id)恢复自增值,如果表有大删操作,可能比实际最大值还小 - MySQL 8.0起通过redo log持久化自增值,但跨实例复制时仍无法解决主从延迟导致的“从库跳号”
分库分表或微服务下,别硬扛自增ID
单机MySQL里自增ID很稳,但一上分布式,问题立刻暴露:多个实例各自从1开始,合并数据时主键冲突、外键失效、历史订单查不到关联记录。
- 不要用
auto_increment_offset+auto_increment_increment手动分段(比如A库步长2起始1,B库起始2),运维成本高、扩缩容反人类 - 业务层生成ID时,优先考虑
Snowflake或Leaf类方案,而非拼接时间戳+机器码——后者在毫秒级并发下易重复 - 如果必须保留自增语义(如分页依赖
ORDER BY id),可在应用层加一层“逻辑ID映射表”,但需接受额外JOIN开销
该不该用UUID替代自增ID?看这三点再决定
UUID不是银弹,它解决的是全局唯一性,代价是性能和空间。别只听“分布式必须UUID”,先看你的查询模式和数据量。
- 写多读少、且几乎不用范围查询(比如日志表、事件溯源),
UUID或ULID更合适;但记得建表时用UUID_TO_BIN()存为BINARY(16),别用字符串存36字节 - 有高频分页(
LIMIT 10000,20)、按ID区间导数、或关联大量二级索引的场景,自增ID仍是首选 - 混合方案可行:用自增ID作物理主键(
PRIMARY KEY),另加一个external_id CHAR(36)作业务ID对外暴露,既保性能又防爬虫猜总量
真正容易被忽略的,是自增ID在备份恢复、GTID复制、以及跨云迁移时引发的隐性冲突——这些往往要等到上线后压测才暴露,而不是建表那一刻就能想到的。











