一、为什么需要从SERIAL迁移到IDENTITY

在PostgreSQL的早期版本中,我们习惯使用SERIAL类型来创建自增列。它本质上是一个语法糖,底层通过序列(sequence)和默认值来实现自增功能。但随着PostgreSQL 10的发布,引入了标准SQL定义的IDENTITY列,这成为了更现代、更规范的自增列实现方式。

举个例子,我们来看一个典型的SERIAL用法:

-- 使用SERIAL创建自增列(PostgreSQL传统方式)
CREATE TABLE users (
    id SERIAL PRIMARY KEY,  -- SERIAL会自动创建序列并绑定
    name VARCHAR(100)
);

虽然SERIAL用起来简单,但它有几个潜在问题:

  1. 不够直观SERIAL是PostgreSQL特有的语法,不是SQL标准的一部分,对于从其他数据库迁移过来的开发者可能不太友好。
  2. 隐藏细节:它隐式创建序列,如果开发者在不知情的情况下直接操作序列,可能导致意外行为。
  3. 权限问题:自动创建的序列可能需要额外权限管理。

IDENTITY列则更符合SQL标准,行为更明确:

-- 使用IDENTITY创建自增列(PostgreSQL 10+推荐方式)
CREATE TABLE products (
    id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,  -- 标准语法,行为更清晰
    product_name VARCHAR(100)
);

二、IDENTITY列的核心特性

IDENTITY列有两种生成策略:

  • GENERATED ALWAYS AS IDENTITY:强制使用序列,手动插入值会报错(除非用OVERRIDING SYSTEM VALUE)。
  • GENERATED BY DEFAULT AS IDENTITY:允许手动插入值,仅在未指定值时使用序列。

来看一个更完整的例子:

-- 创建带有IDENTITY列的表
CREATE TABLE orders (
    order_id INT GENERATED ALWAYS AS IDENTITY,  -- 必须由系统生成
    order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    amount DECIMAL(10,2),
    PRIMARY KEY (order_id)
);

-- 尝试手动插入值会报错
INSERT INTO orders (order_id, amount) VALUES (100, 99.99);  -- 错误!

-- 必须用OVERRIDING SYSTEM VALUE才能插入
INSERT INTO orders (order_id, amount) 
OVERRIDING SYSTEM VALUE VALUES (100, 99.99);  -- 显式覆盖系统生成

-- 正常插入(由系统生成ID)
INSERT INTO orders (amount) VALUES (200.00);  -- 成功,自动分配ID

相比之下,GENERATED BY DEFAULT更宽松:

CREATE TABLE inventory (
    item_id INT GENERATED BY DEFAULT AS IDENTITY,  -- 允许手动指定
    item_name VARCHAR(100)
);

-- 两种插入方式都合法
INSERT INTO inventory (item_id, item_name) VALUES (10, 'Laptop');  -- 手动指定
INSERT INTO inventory (item_name) VALUES ('Phone');  -- 自动生成

三、从SERIAL迁移到IDENTITY的实践步骤

假设我们有一个现有的使用SERIAL的表,如何平滑迁移到IDENTITY?以下是具体步骤:

步骤1:检查现有表的SERIAL列

-- 查看当前表的定义
\d old_serial_table

-- 确认序列名称(SERIAL隐式创建的序列通常是"表名_列名_seq")
SELECT sequence_name FROM information_schema.sequences;

步骤2:创建新表(使用IDENTITY)

-- 创建结构相同但使用IDENTITY的新表
CREATE TABLE new_identity_table (
    id INT GENERATED BY DEFAULT AS IDENTITY,  -- 注意选择ALWAYS或BY DEFAULT
    data VARCHAR(100)
);

-- 或者直接修改现有表(PostgreSQL 10+)
ALTER TABLE old_serial_table 
ALTER COLUMN id SET DATA TYPE INT,  -- 确保类型兼容
ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY;  -- 转换为IDENTITY

步骤3:迁移数据

-- 复制数据(假设我们选择创建新表)
INSERT INTO new_identity_table (id, data)
SELECT id, data FROM old_serial_table;

-- 如果序列需要保留原值,重置序列
ALTER SEQUENCE new_identity_table_id_seq 
RESTART WITH (SELECT MAX(id)+1 FROM new_identity_table);

步骤4:测试验证

-- 验证自动递增功能
INSERT INTO new_identity_table (data) VALUES ('Test1');
INSERT INTO new_identity_table (data) VALUES ('Test2');

-- 检查最新ID是否连续
SELECT * FROM new_identity_table ORDER BY id DESC LIMIT 5;

四、技术对比与注意事项

4.1 性能差异

在实际测试中,IDENTITYSERIAL的性能几乎相同,因为底层都使用序列。但IDENTITY有这些优势:

  • 更清晰的意图表达
  • 更好的与其他数据库兼容
  • 更细粒度的控制(通过OVERRIDING SYSTEM VALUE

4.2 注意事项

  1. 版本兼容性IDENTITY需要PostgreSQL 10+,旧版本只能使用SERIAL
  2. 迁移时机:建议在低流量时段执行迁移,避免锁表问题。
  3. 外键约束:如果其他表引用了自增列,需要同步更新外键关系。
  4. 应用代码:检查应用代码是否依赖SERIAL的特定行为,如直接操作序列。

4.3 应用场景推荐

  • 新项目:直接使用IDENTITY,无需考虑SERIAL
  • 老项目迁移:如果表结构简单,可以原地修改;复杂系统建议逐步迁移。
  • 跨数据库兼容:如果计划支持多种数据库,IDENTITY是更好选择。

五、总结

SERIALIDENTITY的迁移代表了PostgreSQL向SQL标准靠拢的趋势。虽然改动看似微小,但它带来了更规范的语法、更明确的行为和更好的兼容性。对于新项目,毫无疑问应该选择IDENTITY;对于现有系统,可以根据实际情况制定渐进式迁移计划。

记住关键点:

  • GENERATED ALWAYS更严格,适合需要强制自增的场景
  • GENERATED BY DEFAULT更灵活,兼容现有数据
  • 迁移时注意序列值的连续性

通过合理规划,这次技术升级可以做到业务无感知,为未来的数据库维护打下更好基础。