innodb必须显式设置自增主键,因其无主键时会启用不可控、不可索引、不保证递增的6字节隐式rowid,导致高并发冲突、页分裂加剧、二级索引i/o翻倍及主从复制异常。

MySQL建议每个表都必须显式建立自增主键,不是为了“方便开发”或“省事”,而是因为InnoDB引擎在无显式主键时会退化为隐式ROWID,而这个隐式主键不可控、不可索引、不保证递增,反而放大写入和查询风险——尤其在线上高并发、大数据量场景下,它比乱设UUID更危险。
为什么没主键时InnoDB会用ROWID,但你不该依赖它
InnoDB要求每张表必须有聚簇索引。如果你没定义PRIMARY KEY,也没定义UNIQUE NOT NULL索引,它就会悄悄启用一个6字节的隐藏ROWID作为主键。这个ROWID随插入顺序增长,看似“类自增”,但它:
• 不暴露给SQL层,无法被ORDER BY、JOIN或应用逻辑引用
• 不受AUTO_INCREMENT机制保护,重启后不持久,重建表时可能重排
• 无法被任何二级索引复用(因为不是真实列),导致所有二级索引仍要回查整行数据,I/O翻倍
• 在主从复制中,ROWID不参与binlog事件标识,row格式下可能导致备库apply失败或卡住
AUTO_INCREMENT列必须搭配索引,否则建表直接报错
MySQL强制要求AUTO_INCREMENT列必须是PRIMARY KEY或UNIQUE索引的一部分,否则建表失败:
• 错误示例:CREATE TABLE t (id INT AUTO_INCREMENT) → 报错 ERROR 1075
• 正确写法必须显式声明索引:id BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT
• 常见踩坑:ALTER TABLE时只改列类型,忘了补PRIMARY KEY;或把UNIQUE建在非空字段上却漏了NOT NULL约束
• 注意:AUTO_INCREMENT只支持整数类型(TINYINT到BIGINT),VARCHAR或UUID类型加该属性会直接拒绝
自增主键能避免页分裂,但前提是“真自增”且“不手动干预”
页分裂不是理论问题,是线上可观察的性能衰减信号(如Data_free持续 > 表大小5%)。真正起作用的是插入位置的确定性:
• 有效做法:只用INSERT INTO t (col1, col2) VALUES (?, ?),让MySQL自动分配ID
• 危险操作:显式插入大ID(如INSERT INTO t (id, name) VALUES (9999999, 'x')),后续自增将从此跳号,若该ID远超当前页范围,就可能触发中间页插入
• 更隐蔽的坑:用CONCAT(DATE_FORMAT(NOW(),'%Y%m%d'), LPAD(@id,6,'0'))生成“伪有序ID”,但不同时间戳生成的ID仍会交叉写入,一样引发分裂
• 验证方式:SHOW INDEX FROM t确认主键是否为PRIMARY且Seq_in_index = 1;SELECT @@innodb_autoinc_lock_mode确认是默认值1
换主键的成本远高于初期决策,线上几乎不可行
已有千万级数据的表想从UUID切到自增ID,不是改个字段类型那么简单:
• 必须新建自增列、填充连续值、重建所有二级索引、切换应用写入路径、处理历史数据关联逻辑
• ALTER TABLE ... ADD COLUMN id BIGINT AUTO_INCREMENT PRIMARY KEY FIRST会锁表(除非用ALGORITHM=INPLACE且满足严格条件)
• 外键、视图、存储过程、应用DAO层全要同步改造,任一遗漏都会导致数据不一致
• 尤其要注意:MySQL不支持对已有主键列直接加AUTO_INCREMENT属性,必须先DROP PRIMARY KEY再重建,期间表处于无主键状态,风险极高











