MySQL AUTO_INCREMENT 插入 0 变成自增值的原因与解决方案
MySQL AUTO_INCREMENT 插入 0 变成自增值的原因与解决方案
前言
在 MySQL 中,AUTO_INCREMENT 常用于生成自增主键。正常情况下,我们只需要在插入数据时省略主键,或者把主键设置为 NULL,MySQL 就会自动生成下一个自增 ID。
但在一些特殊场景中,例如初始化默认分类、兼容旧系统数据、迁移历史数据时,可能需要插入一条 id = 0 的记录。这时很多人会发现:明明 SQL 里写了 0,最终落库的却是另一个自增值。
本文记录这个问题的原因、复现方式和几种处理方案。
问题现象
假设有一张配置分类表,id 是自增主键:
CREATE TABLE config_type (
id BIGINT NOT NULL AUTO_INCREMENT,
type VARCHAR(64) NOT NULL,
name VARCHAR(128) NOT NULL,
parent_id BIGINT NULL,
PRIMARY KEY (id)
) ENGINE = InnoDB;
现在希望插入一条 ID 为 0 的默认分类:
INSERT INTO config_type (id, type, name, parent_id)
VALUES (0, 'service', '默认分类', NULL);
预期结果:
id = 0
实际结果可能是:
id = 1
也就是说,0 没有作为普通值写入,而是触发了自增 ID 的生成。
原因分析:0 和 NULL 默认都会触发自增
MySQL 对 AUTO_INCREMENT 列有一个特殊规则:在默认 SQL Mode 下,向自增列插入 NULL 或 0,MySQL 都会生成下一个序列值。
也就是说,下面几种写法在默认情况下都可能触发自增:
-- 省略自增列
INSERT INTO config_type (type, name) VALUES ('service', '默认分类');
-- 显式插入 NULL
INSERT INTO config_type (id, type, name) VALUES (NULL, 'service', '默认分类');
-- 显式插入 0,默认也会触发自增
INSERT INTO config_type (id, type, name) VALUES (0, 'service', '默认分类');
如果希望 0 被当作普通数值插入,而不是触发自增,就需要启用 SQL Mode:NO_AUTO_VALUE_ON_ZERO。
启用该模式后:
- 插入
NULL:仍然生成新的自增值; - 插入
0:按普通值0写入表中。
复现实验
可以用下面的临时表复现这个行为:
DROP TABLE IF EXISTS t_auto_zero;
CREATE TABLE t_auto_zero (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(32) NOT NULL,
PRIMARY KEY (id)
) ENGINE = InnoDB;
默认模式下插入 0:
INSERT INTO t_auto_zero (id, name) VALUES (0, 'zero');
SELECT id, name FROM t_auto_zero;
可能得到:
+----+------+
| id | name |
+----+------+
| 1 | zero |
+----+------+
此时 0 被 MySQL 解释成“请生成下一个自增值”。
解决方案一:当前会话开启 NO_AUTO_VALUE_ON_ZERO
如果只是当前连接、当前脚本需要插入 0,推荐使用会话级设置:
SELECT @@SESSION.sql_mode;
SET @old_sql_mode = @@SESSION.sql_mode;
SET SESSION sql_mode = CASE
WHEN FIND_IN_SET('NO_AUTO_VALUE_ON_ZERO', @@SESSION.sql_mode) > 0
THEN @@SESSION.sql_mode
WHEN @@SESSION.sql_mode = ''
THEN 'NO_AUTO_VALUE_ON_ZERO'
ELSE CONCAT(@@SESSION.sql_mode, ',NO_AUTO_VALUE_ON_ZERO')
END;
INSERT INTO config_type (id, type, name, parent_id)
VALUES (0, 'service', '默认分类', NULL);
SET SESSION sql_mode = @old_sql_mode;
这种方式的优点是影响范围小,只对当前数据库连接生效,适合一次性修复、初始化脚本或数据导入脚本。
解决方案二:全局开启 SQL Mode
如果业务上长期需要允许 AUTO_INCREMENT 字段插入 0,也可以设置全局 SQL Mode:
SELECT @@GLOBAL.sql_mode;
SET GLOBAL sql_mode = CASE
WHEN FIND_IN_SET('NO_AUTO_VALUE_ON_ZERO', @@GLOBAL.sql_mode) > 0
THEN @@GLOBAL.sql_mode
WHEN @@GLOBAL.sql_mode = ''
THEN 'NO_AUTO_VALUE_ON_ZERO'
ELSE CONCAT(@@GLOBAL.sql_mode, ',NO_AUTO_VALUE_ON_ZERO')
END;
需要注意:
SET GLOBAL通常需要较高权限;- 它不会影响已经建立的连接,只影响之后新建的连接;
- 生产环境不要为了单次导入随意修改全局 SQL Mode;
- 修改前应先记录原始
@@GLOBAL.sql_mode,避免覆盖已有模式。
如果使用 MySQL 8.0+,也可以结合 SET PERSIST 做持久化配置,但仍然建议先评估影响范围。
解决方案三:配置文件持久化
如果确认整个实例都需要长期启用该模式,可以在 MySQL 配置文件中设置。
Linux 常见配置文件:
/etc/my.cnf
/etc/mysql/my.cnf
/etc/mysql/mysql.conf.d/mysqld.cnf
示例:
[mysqld]
sql_mode=STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION,NO_AUTO_VALUE_ON_ZERO
配置后需要重启 MySQL 才会生效。
注意不要只写:
sql_mode=NO_AUTO_VALUE_ON_ZERO
否则可能会把原有的严格模式、日期检查、引擎替代检查等 SQL Mode 覆盖掉,导致其他行为发生变化。
数据导入场景建议
如果需求来自数据迁移或初始化脚本,建议采用下面的模式:
SET @old_sql_mode = @@SESSION.sql_mode;
SET SESSION sql_mode = CASE
WHEN FIND_IN_SET('NO_AUTO_VALUE_ON_ZERO', @@SESSION.sql_mode) > 0
THEN @@SESSION.sql_mode
WHEN @@SESSION.sql_mode = ''
THEN 'NO_AUTO_VALUE_ON_ZERO'
ELSE CONCAT(@@SESSION.sql_mode, ',NO_AUTO_VALUE_ON_ZERO')
END;
-- 执行需要保留 id = 0 的导入语句
INSERT INTO config_type (id, type, name, parent_id)
VALUES (0, 'service', '默认分类', NULL);
SET SESSION sql_mode = @old_sql_mode;
这样既能保证导入时 0 被正确写入,也不会长期改变应用连接的行为。
是否推荐使用 ID = 0?
虽然可以通过 NO_AUTO_VALUE_ON_ZERO 插入 id = 0,但不建议在普通业务数据中频繁使用 0 作为主键。
更推荐的做法是:
- 如果表示“无父级”,优先使用
parent_id IS NULL; - 如果表示“默认分类”,可以使用单独的业务字段,例如
is_default; - 如果表示“系统内置数据”,可以使用独立编码字段,例如
code = 'default'; - 如果是历史系统兼容或数据迁移,则可以谨慎保留
id = 0。
换句话说:
NO_AUTO_VALUE_ON_ZERO更适合解决迁移、兼容、初始化场景,不应成为日常业务建模依赖。
常见问题
1. 为什么我执行 SET GLOBAL 后还是插入不了 0?
SET GLOBAL 不会影响当前已经存在的连接。你需要重新建立数据库连接,或者直接执行 SET SESSION sql_mode = ...。
2. 可以直接把 sql_mode 设置成 NO_AUTO_VALUE_ON_ZERO 吗?
不建议。sql_mode 往往包含多个模式,直接覆盖可能会关闭原有的严格检查。更安全的方式是在原有值基础上追加 NO_AUTO_VALUE_ON_ZERO。
3. 插入 0 会不会影响后续自增值?
通常不会让自增序列从 0 开始。后续插入 NULL 或省略自增列时,MySQL 仍然会继续生成新的自增值。
4. 所有数据库都这样处理自增列的 0 吗?
不是。这是 MySQL AUTO_INCREMENT 与 SQL Mode 组合下的行为。迁移到 PostgreSQL、Oracle、SQL Server 等数据库时,需要重新确认对应数据库的自增列、序列或 identity column 行为。
总结
MySQL 自增 ID 插入 0 后变成其他自增值,并不是插入语句失效,而是 AUTO_INCREMENT 的默认规则导致的:默认情况下,NULL 和 0 都会触发生成下一个自增值。
如果确实需要保存 id = 0,可以启用:
NO_AUTO_VALUE_ON_ZERO
实践建议是:
- 临时导入或修复数据:优先使用
SET SESSION; - 长期实例级策略:谨慎使用
SET GLOBAL或配置文件; - 普通业务建模:尽量避免依赖
0作为特殊主键值。