mysql用auto_increment、postgresql用identity可安全生成自增id,但需主键约束且避免手动插入;分布式场景应改用uuid或snowflake等方案,而非改造自增机制。

MySQL 的 AUTO_INCREMENT 是最直接的解法
只要表结构支持,AUTO_INCREMENT 能在 INSERT 时自动填入递增整数,无需应用层干预。它底层由存储引擎(如 InnoDB)维护,保证并发安全。
常见错误是建表时漏掉 PRIMARY KEY 或没设 NOT NULL —— AUTO_INCREMENT 字段必须是索引的一部分且不能为 NULL:
CREATE TABLE users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) );
- 必须用
PRIMARY KEY或UNIQUE KEY约束该字段 - 类型推荐
INT或BIGINT,避免用CHAR或UUID混用 - 插入时不传
id值:INSERT INTO users (name) VALUES ('Alice'); - 如果手动插入了
id,后续自增起点会变成该值 + 1,容易引发重复或跳号
PostgreSQL 用 SERIAL 或 IDENTITY 更稳妥
SERIAL 是语法糖,本质是创建序列(SEQUENCE)并绑定默认值;IDENTITY(v10+)才是标准、更可控的方式,支持 GENERATED ALWAYS 和 BY DEFAULT 两种模式。
容易踩的坑是误以为 SERIAL 能防手动插入 —— 它其实允许显式赋值,而 IDENTITY 配合 GENERATED ALWAYS 才真正拒绝覆盖:
CREATE TABLE orders ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, title TEXT );
- 用
GENERATED ALWAYS时,INSERT INTO orders (id, title) VALUES (1, 'test')会报错:cannot insert into column "id" - 想兼容旧逻辑可选
GENERATED BY DEFAULT,此时仍允许手动指定id - 序列值不会因事务回滚而重用,可能产生空洞,但这是正常行为,不是 bug
需要全局唯一或分布式场景?别硬扛 AUTO_INCREMENT
单机自增 ID 在分库分表、多主复制、跨服务合并数据时会冲突。这时候强行用 AUTO_INCREMENT 步长偏移(如奇偶分片)只是权宜之计,维护成本高且难扩展。
更实际的做法是换生成策略,而非改造数据库自增机制:
-
UUID(如 PostgreSQL 的gen_random_uuid()或 MySQL 8.0+ 的UUID_TO_BIN(UUID()))适合不依赖顺序的场景,但写放大、索引碎片多 - 雪花算法(Snowflake)类 ID:需外部服务或应用层生成,注意时钟回拨和机器 ID 冲突
- 数据库代理层(如 Vitess、TiDB)内置的
auto_inc分布式方案,对应用透明但强依赖基础设施
如果已有业务重度依赖自增 ID 的连续性或排序语义,贸然切换 UUID 可能影响分页、缓存键设计甚至前端展示逻辑。
INSERT ... SELECT 或批量导入时 ID 生成容易失效
当用 INSERT INTO t1 SELECT ... FROM t2 插入数据时,目标表的 AUTO_INCREMENT 或 IDENTITY 不会触发(除非显式省略该列),但某些 ORM 或工具会自动补全字段列表,导致意外覆盖或报错。
关键点在于明确列名列表,尤其在目标表有自增字段时:
INSERT INTO users (name, email) SELECT name, email FROM legacy_users;
- 上面语句安全,因为没提
id列,数据库自动填充 - 如果写成
INSERT INTO users SELECT * FROM legacy_users,且两表字段顺序/数量不一致,轻则插入失败,重则把name当作id插入 - PostgreSQL 中若目标列为
IDENTITY且设为GENERATED ALWAYS,即使你没写它,SELECT *也会因列数不匹配报错
批量导入(如 LOAD DATA INFILE 或 COPY)同理:必须确认是否跳过自增列,否则可能触发主键冲突或被拒绝。











