插入数据(INSERT)
INSERT 是 DML 的入口——所有数据最终都靠它写进表里。本节讲清 INSERT 的全部形态:单行 / 多行 VALUES、INSERT ... SELECT、DEFAULT VALUES 与显式 DEFAULT,RETURNING 的取回能力,以及"批量插入到底应该用多行 VALUES 还是 COPY"这个绕不开的性能问题。
前置知识
阅读本节前,建议先了解:创建表(CREATE TABLE)
基础概念
INSERT 的作用是向目标表追加新行。它不会修改已有行,也不会删除任何数据;如果插入的数据违反约束(主键重复、非空、检查约束),整条语句会失败并回滚——一条 INSERT 是原子的,要么所有行都进去,要么一行都不进。
| 写法 | 语法形态 | 典型用途 | 能否一次插多行 |
|---|---|---|---|
单行 VALUES | INSERT INTO t (a) VALUES (1) | 应用层单条写入 | 否 |
多行 VALUES | INSERT INTO t (a) VALUES (1), (2), (3) | 中小批量写入 | 是 |
INSERT ... SELECT | INSERT INTO t (a) SELECT a FROM s | 表间搬运 / ETL | 是(任意行数) |
INSERT ... DEFAULT VALUES | INSERT INTO t DEFAULT VALUES | 全默认值插一行 | 否 |
INSERT ... RETURNING | 上述任意形式 + RETURNING ... | 写回同时取回结果 | 是 |
几个关键术语:
- 目标列列表(Column List):
INSERT INTO t (col1, col2)中括号里的部分。省略列列表时,值的顺序必须与表的物理列顺序完全一致,这是最常见的事故来源之一。 DEFAULT:在VALUES中写DEFAULT,表示"该列交给它的默认值决定"(可能是DEFAULT表达式,也可能是GENERATED生成列 /IDENTITY序列)。它和"不写这一列"的效果相同,但允许在不省略列列表的前提下跳过某列。DEFAULT VALUES:整行都用默认值,只能插一行。RETURNING:PostgreSQL 扩展。让INSERT在写入的同时返回被插入行的列值,包括数据库生成的列(如IDENTITY生成的主键、DEFAULT now()得到的时间戳)。- 生成列(
GENERATED ALWAYS AS IDENTITY/GENERATED ALWAYS AS (...) STORED):前者由序列产生,禁止显式写值(除非OVERRIDING SYSTEM VALUE);后者由表达式算出,压根不能出现在INSERT的列列表里。 ON CONFLICT:冲突处理子句(即 UPSERT)。本节只讲DO NOTHING的初步用法,完整用法见下一节。OVERRIDING SYSTEM VALUE:当表使用GENERATED ALWAYS AS IDENTITY而又必须手工指定主键时(如数据迁移),用它显式覆盖。
语法与用法
完整语法骨架如下(方括号为可选部分):
[ WITH [ RECURSIVE ] with_query [, ...] ]
INSERT INTO table_name [ AS alias ] [ ( column_name [, ...] ) ]
[ OVERRIDING { SYSTEM | USER } VALUE ]
{ DEFAULT VALUES | VALUES ( { expression | DEFAULT } [, ...] ) [, ...] | query }
[ ON CONFLICT [ conflict_target ] conflict_action ]
[ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ]五个最常用的形态:
-- 1. 单行插入
INSERT INTO users (email, name) VALUES ('a@example.com', 'Alice');
-- 2. 多行插入(推荐,一次往返写多行)
INSERT INTO users (email, name) VALUES
('b@example.com', 'Bob'),
('c@example.com', 'Carol');
-- 3. 从查询结果插入
INSERT INTO users_archive (email, name)
SELECT email, name FROM users WHERE created_at < now() - interval '1 year';
-- 4. 全默认值插入一行
INSERT INTO users DEFAULT VALUES;
-- 5. 跳过某一列(用 DEFAULT 占位)
INSERT INTO users (email, name, status) VALUES ('d@example.com', 'Dave', DEFAULT);取值来源的几种写法:
| 值的形式 | 说明 | 示例 |
|---|---|---|
| 字面量 | 字符串、数字、布尔 | VALUES ('a', 1, true) |
| 表达式 | 任意可求值的 SQL 表达式 | VALUES (now(), upper('ab'), 1 + 2) |
DEFAULT | 使用该列的默认值 | VALUES ('a', DEFAULT) |
| 类型转换 | 显式 CAST 或 :: | VALUES ('2026-09-24'::date) |
| 数组 / JSON | 用字面量语法 | VALUES ('{a,b}', '{"k":"v"}') |
| 子查询 | 标量子查询(只能返回一行一列) | VALUES ((SELECT max(id) + 1 FROM t)) |
下面用一张贯穿本节的示例表:
-- 示例:用户订单表
CREATE TABLE orders (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
order_no text NOT NULL UNIQUE,
user_email text NOT NULL,
amount numeric(12,2) NOT NULL CHECK (amount >= 0),
status text NOT NULL DEFAULT 'pending',
tags text[] NOT NULL DEFAULT '{}',
created_at timestamptz NOT NULL DEFAULT now()
);详细说明
单行与多行 VALUES
多行 VALUES 是 PostgreSQL 最实用的语法之一,它在一次网络往返里完成多行写入,且整体作为一个原子语句执行。
-- 单行:一次往返写一行
INSERT INTO orders (order_no, user_email, amount)
VALUES ('ORD-1001', 'alice@example.com', 199.00);
-- 多行:一次往返写三行
INSERT INTO orders (order_no, user_email, amount, status) VALUES
('ORD-1002', 'bob@example.com', 88.50, 'paid'),
('ORD-1003', 'carol@example.com', 320.00, 'pending'),
('ORD-1004', 'dave@example.com', 15.90, 'paid');多行 VALUES 的执行代价大致线性于行数,但固定开销(解析、计划、往返)只付一次。示意性实测对比(同样插入 10000 行,本地回环连接):
-- 以下为示意数据,用于说明数量级差异
方式 耗时 相对倍数
单行 INSERT × 10000 次 4820 ms 1.00
多行 VALUES(每批 100 行) 186 ms 0.04
多行 VALUES(每批 1000 行) 92 ms 0.02
COPY(单次) 41 ms 0.009注意:多行 VALUES 也不是越大越好。单条语句过大(例如几十万行一次)会显著增加内存占用、延长事务持锁时间,并让失败时的重试代价变高。
INSERT ... SELECT
当数据来源是另一张表时,用 INSERT ... SELECT 而不是"先查出来、再拼 SQL 插回去"。数据全程在数据库内部流动,不经过应用层,既快又不会因类型往返转换丢精度。
-- 把过期订单归档
INSERT INTO orders_archive (id, order_no, user_email, amount, status, created_at)
SELECT id, order_no, user_email, amount, status, created_at
FROM orders
WHERE created_at < now() - interval '1 year';列顺序按位置对应,不按名字匹配。务必显式写出两侧列名,避免两边列顺序不一致导致数据错位:
-- 危险!两边列顺序不同会静默写错数据
INSERT INTO orders_archive
SELECT * FROM orders;
-- 安全:显式列出两侧列名
INSERT INTO orders_archive (order_no, user_email, amount)
SELECT order_no, user_email, amount FROM orders WHERE amount > 1000;INSERT ... SELECT 也支持 ORDER BY、LIMIT、CTE 与窗口函数,可以把复杂的取数逻辑直接内联:
-- 每个用户取最近一笔订单,写入对账表
WITH ranked AS (
SELECT *,
row_number() OVER (PARTITION BY user_email ORDER BY created_at DESC) AS rn
FROM orders
)
INSERT INTO reconciliation (order_no, user_email, amount)
SELECT order_no, user_email, amount
FROM ranked
WHERE rn = 1;注意
INSERT ... SELECT ... ORDER BY中的ORDER BY不保证写入物理顺序,它只影响SELECT的输出顺序(例如配合LIMIT时才有意义)。
DEFAULT VALUES 与显式 DEFAULT
如果表的所有列都有默认值或允许 NULL,可以用 DEFAULT VALUES 插入一行"全默认"记录。它常用于"先占一行拿主键"的场景:
-- 插入一行全默认值,并直接拿到生成的主键
INSERT INTO orders (order_no, user_email, amount)
VALUES ('ORD-9999', 'temp@example.com', 0);
-- 若表允许,也可整行默认
INSERT INTO settings DEFAULT VALUES;
-- 显式 DEFAULT:只跳过部分列
INSERT INTO orders (order_no, user_email, amount, status, created_at)
VALUES ('ORD-1005', 'eve@example.com', 66.00, DEFAULT, DEFAULT);DEFAULT 的语义要点:
DEFAULT不等于NULL。若列定义了DEFAULT 0,写DEFAULT得到0,写NULL得到NULL(并可能违反NOT NULL)。GENERATED ALWAYS AS (...) STORED的生成列不能出现在列列表中,写DEFAULT也不行,必须整个省略。- 若列没有
DEFAULT且允许NULL,写DEFAULT等价于写NULL。
-- 反例:生成列不能出现在 INSERT 的列列表中
-- 假设 orders 有列 total numeric GENERATED ALWAYS AS (amount * 1.13) STORED
INSERT INTO orders (order_no, user_email, amount, total)
VALUES ('ORD-1006', 'frank@example.com', 100.00, 113.00);
-- ERROR: cannot insert a non-DEFAULT value into column "total"
-- DETAIL: Column "total" is a generated column.省略列列表的风险
INSERT INTO t VALUES (...)(不写列列表)能跑,但极其脆弱:
-- 建表
CREATE TABLE t_demo (a int, b text, c date);
-- 省略列列表:值按物理列顺序 a, b, c 对应
INSERT INTO t_demo VALUES (1, 'x', '2026-01-01');
-- 现在有人加了一列到中间(老版本 PostgreSQL 允许指定位置)
ALTER TABLE t_demo ADD COLUMN d boolean NOT NULL DEFAULT false;
-- 旧语句依然能执行,但语义已经变了:
-- 值仍按 a, b, c 匹配,d 走默认值。若新列是新增在最前,则会直接类型不匹配报错。正确的做法是永远显式写出列列表:
-- 推荐:列名与值一一对应,加列、改列顺序都不受影响
INSERT INTO t_demo (a, b, c) VALUES (1, 'x', '2026-01-01');同理,INSERT INTO t SELECT * FROM s 也应该写成 INSERT INTO t (a, b, c) SELECT a, b, c FROM s。
显式主键与 IDENTITY / serial 的冲突
GENERATED ALWAYS AS IDENTITY 与 serial 在"能不能手工指定值"上行为完全不同:
| 列定义方式 | 手工指定主键 | 需要什么开关 | 说明 |
|---|---|---|---|
serial / bigserial | 允许 | 无需开关 | 本质是 DEFAULT nextval(...),显式值直接覆盖 |
GENERATED BY DEFAULT AS IDENTITY | 允许 | 无需开关 | 手工值优先,序列不推进 |
GENERATED ALWAYS AS IDENTITY | 默认禁止 | 必须加 OVERRIDING SYSTEM VALUE | 防止误覆盖 |
-- 表使用 GENERATED ALWAYS AS IDENTITY
INSERT INTO orders (id, order_no, user_email, amount)
VALUES (1001, 'ORD-2001', 'a@example.com', 10.00);
-- ERROR: cannot insert a non-DEFAULT value into column "id"
-- DETAIL: Column "id" is an identity column defined as GENERATED ALWAYS.
-- HINT: Use OVERRIDING SYSTEM VALUE to override.
-- 数据迁移场景:显式覆盖
INSERT INTO orders (id, order_no, user_email, amount)
OVERRIDING SYSTEM VALUE
VALUES (1001, 'ORD-2001', 'a@example.com', 10.00);最关键的一点:无论哪种方式,显式插入主键都不会推进序列。 迁移完数据后必须手动把序列推到最大值,否则后续自动生成的主键会和历史数据撞车:
-- 迁移后修复序列水位(IDENTITY 列同样适用)
SELECT setval(
pg_get_serial_sequence('orders', 'id'),
(SELECT max(id) FROM orders)
);
-- 更稳的写法:加 1 并把 is_called 设为 false,让下一次恰好取到 max+1
SELECT setval(
pg_get_serial_sequence('orders', 'id'),
(SELECT max(id) FROM orders),
true
);RETURNING:写回同时取回
RETURNING 解决了"插入后还要再查一次"的往返问题,尤其是需要拿数据库生成的值的场景(自增主键、默认时间戳、触发器改写后的值)。
-- 拿到生成的主键与默认时间戳
INSERT INTO orders (order_no, user_email, amount)
VALUES ('ORD-3001', 'gina@example.com', 520.00)
RETURNING id, order_no, status, created_at; id | order_no | status | created_at
-----+-----------+---------+-------------------------------
12 | ORD-3001 | pending | 2026-09-24 15:03:21.774+08
(1 row)
INSERT 0 1RETURNING 里可以写表达式、函数、甚至基于插入值的计算:
-- 返回计算结果与常量标记
INSERT INTO orders (order_no, user_email, amount)
VALUES ('ORD-3002', 'hugo@example.com', 200.00)
RETURNING id, amount, amount * 1.13 AS amount_with_tax, now() AS returned_at;
-- 多行插入时,RETURNING 会为每一行各返回一行
INSERT INTO orders (order_no, user_email, amount) VALUES
('ORD-3003', 'i@example.com', 1.00),
('ORD-3004', 'j@example.com', 2.00)
RETURNING id, order_no;INSERT ... SELECT ... RETURNING 还能作为 CTE 的一部分,把结果继续喂给后续语句:
-- 插入并立即用返回的行做后续统计
WITH ins AS (
INSERT INTO orders (order_no, user_email, amount)
SELECT 'ORD-BATCH-' || g, 'batch@example.com', g * 10
FROM generate_series(1, 5) AS g
RETURNING id, amount
)
SELECT count(*) AS inserted, sum(amount) AS total FROM ins;ON CONFLICT DO NOTHING 初步
当插入的数据可能撞上唯一约束时,默认行为是整条语句报错回滚。如果想"撞了就跳过、不报错",用 ON CONFLICT DO NOTHING:
-- 撞上 order_no 的唯一约束就跳过该行,其余行照常插入
INSERT INTO orders (order_no, user_email, amount) VALUES
('ORD-1002', 'dup@example.com', 1.00), -- 已存在,跳过
('ORD-4001', 'new@example.com', 2.00) -- 新行,插入
ON CONFLICT (order_no) DO NOTHING;INSERT 0 1
-- 注意:影响行数是 1,而不是 2;被跳过的行不计入ON CONFLICT 的括号里必须是已经存在唯一索引或唯一约束的列/表达式,否则报错:
-- 错误!user_email 上没有唯一索引
INSERT INTO orders (order_no, user_email, amount)
VALUES ('ORD-4002', 'k@example.com', 3.00)
ON CONFLICT (user_email) DO NOTHING;
-- ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification还要注意:ON CONFLICT DO NOTHING 不返回冲突行,只返回真正插入的行。这在"需要知道哪些行已存在"的业务里会不够用——这时应改用 DO UPDATE(下一节详解),并用 xmax = 0 判断本次是新插还是更新。
批量写入:多行 VALUES vs COPY
三种批量写入方式的取舍:
| 方式 | 适用规模 | 相对速度 | 是否走 WAL 常规路径 | 是否支持 RETURNING |
|---|---|---|---|---|
单行 INSERT 循环 | < 100 行 | 最慢 | 是 | 是 |
多行 VALUES | 100 ~ 10 万行 | 快 | 是 | 是 |
INSERT ... SELECT | 任意规模(源在库内) | 很快 | 是 | 是 |
COPY | 10 万行以上 | 最快 | 是(除非表为 UNLOGGED) | 否 |
COPY 快的核心原因是绕过了 SQL 解析、计划与逐行执行器,直接把文本/二进制流交给存储层。代价是不支持 RETURNING,也不支持 ON CONFLICT(除非先导入临时表再 INSERT ... SELECT)。
-- 多行 VALUES:适合应用层批量提交,可分片控制大小
INSERT INTO orders (order_no, user_email, amount)
SELECT 'ORD-GEN-' || g, 'gen@example.com', (random() * 1000)::numeric(12,2)
FROM generate_series(1, 1000) AS g;# COPY:适合文件导入,psql 客户端
# 注意:服务端 COPY 需要超级用户或 pg_read_server_files 角色
psql -d demo -c "\copy orders (order_no, user_email, amount) FROM 'orders.csv' WITH (FORMAT csv, HEADER true)"一个实用折中方案:先 COPY 到临时表,再用 INSERT ... SELECT ... ON CONFLICT 合并进目标表,既拿到 COPY 的速度,又有冲突处理与 RETURNING:
-- 1) 建临时表并 COPY 导入原始数据(见 COPY 章节)
CREATE TEMP TABLE orders_stage (LIKE orders INCLUDING DEFAULTS);
-- 2) 合并进正式表,处理冲突并保留审计
INSERT INTO orders (order_no, user_email, amount)
SELECT order_no, user_email, amount FROM orders_stage
ON CONFLICT (order_no) DO UPDATE
SET amount = EXCLUDED.amount
RETURNING order_no;实战示例
下面用一段完整流程演示"电商订单写入"的典型链路:建表 → 单行写入 → 批量写入 → 幂等重放 → 归档搬运 → 校验。
-- 步骤 1:准备示例表
DROP TABLE IF EXISTS orders_demo;
CREATE TABLE orders_demo (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
order_no text NOT NULL UNIQUE,
user_email text NOT NULL,
amount numeric(12,2) NOT NULL CHECK (amount >= 0),
status text NOT NULL DEFAULT 'pending',
tags text[] NOT NULL DEFAULT '{}',
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE orders_demo_archive (LIKE orders_demo INCLUDING ALL);DROP TABLE
CREATE TABLE
CREATE TABLE-- 步骤 2:单行下单,并立刻拿到数据库生成的主键与时间戳
INSERT INTO orders_demo (order_no, user_email, amount)
VALUES ('ORD-5001', 'alice@example.com', 199.00)
RETURNING id, order_no, status, created_at; id | order_no | status | created_at
----+----------+---------+-------------------------------
1 | ORD-5001 | pending | 2026-09-24 15:20:11.318+08
(1 row)
INSERT 0 1-- 步骤 3:批量写入 3 笔订单(一次往返)
INSERT INTO orders_demo (order_no, user_email, amount, status, tags) VALUES
('ORD-5002', 'bob@example.com', 88.50, 'paid', '{promo}'),
('ORD-5003', 'carol@example.com', 320.00, 'pending', '{}'),
('ORD-5004', 'dave@example.com', 15.90, 'paid', '{gift}')
RETURNING id, order_no, amount; id | order_no | amount
----+----------+--------
2 | ORD-5002 | 88.50
3 | ORD-5003 | 320.00
4 | ORD-5004 | 15.90
(3 rows)
INSERT 0 3-- 步骤 4:消息重投场景,同一批订单重复写入,要求幂等不报错
INSERT INTO orders_demo (order_no, user_email, amount, status) VALUES
('ORD-5002', 'bob@example.com', 88.50, 'paid'),
('ORD-5005', 'erin@example.com', 45.00, 'pending')
ON CONFLICT (order_no) DO NOTHING
RETURNING id, order_no; id | order_no
----+----------
5 | ORD-5005
(1 row)
INSERT 0 1
-- 注意:ORD-5002 已存在被跳过,因此 RETURNING 只返回 1 行,
-- 业务层若需要感知"被跳过",可改用 DO UPDATE ... RETURNING (xmax = 0)-- 步骤 5:从查询结果批量写入(模拟数据回流)
INSERT INTO orders_demo (order_no, user_email, amount, status)
SELECT 'ORD-RB-' || id, user_email, amount, 'refunded'
FROM orders_demo
WHERE status = 'paid' AND amount < 100
RETURNING order_no, status; order_no | status
-----------+----------
ORD-RB-2 | refunded
ORD-RB-4 | refunded
(2 rows)
INSERT 0 2-- 步骤 6:把已退款订单搬到归档表(INSERT ... SELECT)
INSERT INTO orders_demo_archive (order_no, user_email, amount, status, tags, created_at)
SELECT order_no, user_email, amount, status, tags, created_at
FROM orders_demo
WHERE status = 'refunded';INSERT 0 2-- 步骤 7:核对两边数据量
SELECT
(SELECT count(*) FROM orders_demo) AS main_rows,
(SELECT count(*) FROM orders_demo_archive) AS archive_rows,
(SELECT sum(amount) FROM orders_demo) AS main_amount; main_rows | archive_rows | main_amount
-----------+--------------+-------------
7 | 2 | 713.40
(1 row)上述流程中,只有步骤 4 体现了幂等:同样的 SQL 重跑一次,ON CONFLICT DO NOTHING 会跳过所有已存在行,最终数据条数不变。这正是把"重试"和"重复消费"变安全的标准手段。
注意事项
INSERT失败会回滚整条语句。 多行VALUES中只要有一行违反约束,整个语句都不生效。如果业务上希望"能插的插进去",需要拆成多批或改用冲突处理。
-- 整条语句都会失败,前两行也不会插入
INSERT INTO orders_demo (order_no, user_email, amount) VALUES
('ORD-6001', 'ok1@example.com', 10.00),
('ORD-5001', 'dup@example.com', 20.00), -- 主键/唯一冲突
('ORD-6002', 'ok2@example.com', 30.00);
-- ERROR: duplicate key value violates unique constraint "orders_demo_order_no_key"- 同时指定主键和序列时会撞车。 手工插入了
id = 100却不推进序列,之后自动插入很可能又取到100,直接报唯一冲突。迁移后必须setval修复水位。 - 省略列列表是定时炸弹。 表结构一变,老语句可能从"报错"变成"静默写错列"。永远显式列出列名。
ON CONFLICT DO NOTHING的影响行数小于提交行数。INSERT 0 1可能表示你提交了 100 行、99 行被跳过。不要用它判断"业务是否执行成功"。INSERT ... RETURNING的顺序不保证与VALUES顺序一致。 尤其是并行INSERT ... SELECT时,返回值顺序是不确定的。需要顺序时,在返回结果上加ORDER BY(外层查询)。COPY不走 SQL 解析,因此不会触发INSERT规则(RULE),但会触发BEFORE/AFTER INSERT行级触发器。 大批量导入时触发器会显著拖慢速度,必要时先禁用。- 大事务会放大锁与 WAL 压力。 一次插几百万行会让事务长时间持有锁、阻塞 autovacuum、造成主从复制延迟。生产上要分批提交。
- 生成的列不能显式赋值。
GENERATED ALWAYS AS (...) STORED的列出现在列列表中即报错,写DEFAULT也不行。
最佳实践
- 永远显式写出列列表。 无论是
VALUES还是INSERT ... SELECT,把列名写全。这是抵抗表结构演进的最低成本手段。
-- 推荐
INSERT INTO orders_demo (order_no, user_email, amount) VALUES ('ORD-7001', 'a@example.com', 10.00);
-- 不推荐:表加列后语义可能悄悄改变
INSERT INTO orders_demo VALUES (DEFAULT, 'ORD-7001', 'a@example.com', 10.00, DEFAULT, DEFAULT, DEFAULT);- 需要数据库生成的值时,一律用
RETURNING。 省掉一次查询往返,也避免了"插入后按非唯一字段反查"带来的竞态。
INSERT INTO orders_demo (order_no, user_email, amount)
VALUES ('ORD-7002', 'b@example.com', 20.00)
RETURNING id, created_at;- 批量写入优先多行
VALUES,大批量导入优先COPY。 应用层批量提交时把批大小控制在 500~5000 行之间:既摊薄了固定开销,又不会让单条语句过大。
-- 批大小可控的多行写入(示例:每批 1000 行)
INSERT INTO orders_demo (order_no, user_email, amount)
SELECT 'ORD-B-' || g, 'batch@example.com', g % 100
FROM generate_series(1, 1000) AS g;- 幂等写入统一用
ON CONFLICT。 定时同步、消息重投、断点续传都应走冲突处理,而不是"先SELECT判断存在与否再决定插或改"——后者在并发下有竞态窗口。
INSERT INTO orders_demo (order_no, user_email, amount)
VALUES ('ORD-7003', 'c@example.com', 30.00)
ON CONFLICT (order_no) DO NOTHING;- 分批提交,避免超长事务。 大批量写入放在
BEGIN/COMMIT内分片执行,每片之间提交一次,控制 WAL 与锁的持有时间。
-- 伪代码示意:每 5000 行提交一次
BEGIN;
INSERT INTO orders_demo (order_no, user_email, amount)
SELECT 'ORD-P-' || g, 'p@example.com', g % 500
FROM generate_series(1, 5000) AS g;
COMMIT;
-- 重复上述块,推进 generate_series 的起点- 数据迁移后修复序列水位。 显式插入主键后,务必
setval到当前最大值,并检查pg_sequences对齐。
SELECT setval(pg_get_serial_sequence('orders_demo', 'id'),
(SELECT max(id) FROM orders_demo));
-- 核对当前水位与最大值
SELECT last_value, is_called FROM pg_sequences
WHERE schemaname = 'public' AND sequencename = 'orders_demo_id_seq';- 导入前评估触发器与索引代价。 大批量导入若表上有多个索引和行级触发器,考虑"先导入、后建索引",或临时
ALTER TABLE ... DISABLE TRIGGER(需谨慎,仅限受控窗口)。
下一节