上周三晚上十一点,我正准备关电脑睡觉,手机突然震个不停。群里炸了锅,说是线上数据库报了个诡异错误,某个核心业务接口直接挂了。运维小哥疯狂刷屏:“ERROR 1062,Duplicate entry ‘xxx’ for key ‘PRIMARY’。”这个错误太眼熟了,就是主键重复。但诡异的是,这个表根本没设过主键,业务跑了三年都没出过问题。开发小哥一脸懵,说这表只是临时存日志的,平时靠自增 ID 字段来区分数据,根本没建唯一索引。可是数据库死活不让写数据,提示主键重复。我一边翻日志一边想,这到底是谁在搞鬼?

先说说这个错误本身。MySQL 的 1062 错误,全称是 “Duplicate entry for key”,翻译成人话就是你往数据库里插入一条数据,但这条数据的主键值或唯一索引值已经存在。数据库讲究数据唯一性,就像身份证号不能重复一样,主键和唯一索引就是数据的身份证。但问题来了,如果根本没设主键,也没有唯一索引,为什么还会报这个错?这就要说到 MySQL 内部那些你可能从未注意过的潜规则。很多开发觉得,表没有显式定义 PRIMARY KEY,就是无主键的表。实际上,InnoDB 引擎在没有显式主键时,会选第一个非空的唯一索引作为主键;如果连唯一索引都没有,它会自动生成一个 6 字节的隐藏主键。这个隐藏主键对用户是透明的,理论上不会触发 1062 错误。那么这次报错该怎么解释?
我翻开表结构一看,发现问题出在 “自增 ID” 上。很多开发习惯给表加个 id 字段,设成 INT AUTOINCREMENT,觉得这样就能保证数据唯一性。但自增 ID 不等于主键,也不等于唯一索引。你只是告诉 MySQL “这个字段每次插入时自动加 1”,但没告诉它 “这个字段的值不能重复”。所以理论上,你可以手动插入一个与自增 ID 冲突的值,例如表里已经有 id=1 的数据,你再 INSERT 一条 id=1,MySQL 不会拦你,除非给这个字段加了 UNIQUE 或 PRIMARY KEY 约束。可是这次报错说主键重复,我顺着这个思路查下去,发现表里确实没有显式主键,却有一个隐藏的主键——那个自增 ID 字段。为什么会这样?因为 MySQL 的默认行为是:如果使用 InnoDB 引擎且没有指定主键,它会尝试把第一个非空的自增列当作主键。于是这个自增 ID 虽然不是显式主键,却被内部当成了主键来管理。
这下就清晰了。原来表的设计者当初图省事,只写了 “id INT AUTOINCREMENT”,没写 “PRIMARY KEY (id)”。但 InnoDB 自动把这个自增 ID 当成了隐藏主键。那为什么三年都没事?因为业务插入数据时从不指定 id 值,都是让自增机制自动生成。上周的 bug 是因为某个开发在修复另一个问题时,手贱写了个 “INSERT INTO table (id, …) VALUES (1, …)”,而 id=1 的记录早已存在。MySQL 一看,你往隐藏主键里插入重复值,立刻报 1062。这个锅该谁背?开发说是数据库设计问题,没设主键导致隐藏规则;DBA 说是开发乱写 SQL,不该手动指定自增 ID 的值。两边都有道理,根本原因是“隐含假设”太多。
这类问题在生产环境里很常见。我见过更离谱的情况,有人建了个表,里头全是 NULL 值,却报 1062。原因是 MySQL 的唯一索引允许 NULL 值重复,但主键不允许 NULL。如果给字段设了主键且允许 NULL,插入 NULL 时会报错。而唯一索引则可以接受多个 NULL。还有一次,某团队用 UUID 做主键,插入时没注意大小写,MySQL 默认大小写不敏感,导致 “ABC123” 与 “abc123” 被视为重复。这些坑其实都是对数据库底层规则认知的盲区。
回到这次事故的教训。第一,建表时一定要显式指定主键,别依赖 MySQL 的“好心”。你永远不知道它在哪个版本里会改规则。第二,自增 ID 字段要么设为主键,要么就别指望它保证唯一性。第三,生产环境的 SQL,尤其是 INSERT 和 UPDATE,一定要经过代码审查,手动指定 ID 的操作必须走审批流程。第四,日志系统要能记录具体是哪条 SQL 引发的错误,方便快速定位。如果不是运维小哥翻出了慢查询日志,我们可能还在纠结那张表到底有没有主键。
说个更扎心的事实。很多公司所谓的“数据库规范”,其实只是一张 Excel 表,列了几条 “不要用 SELECT *”“不要用 JOIN 太多表” 之类的原则,却没有写明 “主键必须显式定义”“自增字段的处理规则”。结果,同一个团队里,有人用自增 ID 当主键,有人用 UUID,有人用业务流水号,还有人干脆不设主键。每个开发都觉得自己是对的,直到数据库报错才暴露问题。所以,别信 “数据库会自动帮你处理好一切” 这种鬼话。你偷的每一个懒,迟早都会变成线上故障,半夜打电话叫你起来修。


