添加主键前须确保字段非空且无重复值,复合主键按高频查询顺序排列,自增列需显式设为主键,新表主键优先选BIGINT但需权衡存储开销。

ALTER TABLE 添加主键前必须确认字段非空且无重复值
MySQL 不允许对包含 NULL 值或重复值的列直接添加主键。执行 ALTER TABLE t ADD PRIMARY KEY (id) 时若 id 有 NULL 或重复,会报错 ERROR 1062 (23000): Duplicate entry '' for key 'PRIMARY' 或 ERROR 1171 (HY000): All parts of a PRIMARY KEY must be NOT NULL。
实操建议:
- 先查空值:
SELECT COUNT(*) FROM t WHERE id IS NULL; - 再查重复:
SELECT id, COUNT(*) FROM t GROUP BY id HAVING COUNT(*) > 1; - 修复数据后,再加
NOT NULL约束(如果还没加):ALTER TABLE t MODIFY id BIGINT NOT NULL; - 最后执行主键添加
复合主键要按查询高频顺序排列字段
主键即聚簇索引,字段顺序直接影响范围查询、排序和覆盖索引效果。比如经常用 WHERE user_id = ? AND created_at > ?,那主键应定义为 (user_id, created_at),反过来则无法高效走索引。
常见错误是把时间字段放前面,结果 WHERE created_at BETWEEN ? AND ? 能用上,但 WHERE user_id = ? 就只能用最左前缀,实际变成全索引扫描。
实操建议:
- 分析慢查询日志里最常出现的
WHERE条件组合 - 把等值查询字段放最左,范围/排序字段靠右
- 避免在主键里包含更新频繁的字段(如
status),会导致大量页分裂
已有自增列但没设主键时,别直接用 ALTER TABLE ... AUTO_INCREMENT
AUTO_INCREMENT 本身不等于主键。如果表已有 id INT AUTO_INCREMENT 但没设主键,执行 ALTER TABLE t MODIFY id INT AUTO_INCREMENT 不会自动创建主键,也不会报错,只是让该列具备自增属性——但依然无法作为主键使用,插入时可能报 ERROR 1364 (HY000): Field 'id' doesn't have a default value。
实操建议:
- 确认是否已有主键:
SHOW CREATE TABLE t;看输出里有没有PRIMARY KEY - 若只有自增列,需显式指定主键:
ALTER TABLE t ADD PRIMARY KEY (id); - 若想让自增生效,必须先有主键(或至少一个唯一非空索引),否则 MySQL 5.7+ 会拒绝插入
主键类型选 BIGINT 而不是 INT 的真实代价
INT UNSIGNED 最大值约 42 亿,对中高活跃度业务(如订单、消息、日志)2–3 年就可能耗尽。改用 BIGINT 看似简单,但每行多占 4 字节,在聚簇索引下会放大:二级索引的叶子节点存的是主键值,所以所有二级索引也会跟着变宽。
性能影响明显:1000 万行表,主键从 INT 换成 BIGINT,单个二级索引可能多占 40MB 内存,缓冲池命中率下降,随机点查延迟上升。
实操建议:
- 新表起步就用
BIGINT,尤其涉及分库分表或长期运行场景 - 存量表升级前,用
SELECT MAX(id) FROM t;评估剩余空间,再决定是否迁移 - 不要为了“以后可能不够”而滥用 UUID 或字符串主键——它们写入随机、碎片高、二级索引更大
主键不是加完就完的事。字段选择、顺序、类型、是否允许变更,每个点都卡在查询路径和存储结构的交界处。漏掉任意一环,后面优化索引、调缓冲池、拆分表都得回头补课。











