能,而且是唯一推荐的原子方式。RETURNING子句在INSERT执行时同步返回指定列(如id),确保原子性、避免竞态,支持批量插入按VALUES顺序返回每行主键,兼容触发器修改后的最终值。

RETURNING 子句能直接拿到刚插入的主键 ID 吗?
能,而且是唯一推荐的原子方式。只要表有主键(或至少有 serial/generated always as identity 列),INSERT ... RETURNING id 就会按插入顺序返回每行对应的主键值,无需额外查询或事务控制。
注意:不能用 LASTVAL() 或 CURRVAL() 替代——它们只返回单个值,且在批量插入时无法对齐行顺序;并发写入下还可能被其他会话干扰。
批量插入多行并返回全部 ID 的标准写法
用 VALUES 列出所有行,RETURNING 指定要返回的列(通常是主键):
INSERT INTO users (name, email)
VALUES
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Charlie', 'charlie@example.com')
RETURNING id, name;
执行后立即返回三行结果,每行包含对应插入记录的 id 和 name,顺序与 VALUES 中一致。
- 必须显式列出字段名(如
users (name, email)),不能省略 -
RETURNING可返回任意表达式,比如RETURNING id, created_at AT TIME ZONE 'UTC' - 如果主键是
UUID类型且由gen_random_uuid()生成,同样适用
和 ON CONFLICT 结合时 RETURNING 还生效吗?
生效,但只返回**实际插入或更新的行**。例如:
INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@new.com'), (2, 'Bob', 'bob@example.com') ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, email = EXCLUDED.email RETURNING id, name, xmax = 0 AS is_inserted;
这里 xmax = 0 是个小技巧:若 xmax 为 0 表示该行是新插入的,否则是更新的。你可以用它区分操作类型。
-
ON CONFLICT ... DO NOTHING时,RETURNING只返回成功插入的行,冲突跳过的不出现 -
DO UPDATE时,所有命中的行都会出现在结果中(无论是否真正修改了数据) - 不能在
DO UPDATE的SET子句里引用RETURNING中的别名
用程序批量插入时要注意的边界情况
多数客户端驱动(如 Python 的 psycopg2、Go 的 pgx)都支持直接获取 RETURNING 结果集,但有几个关键点容易漏掉:
- 参数化查询必须用
%s(psycopg2)或$1, $2(pgx)占位符,不能拼字符串——否则 SQL 注入且RETURNING无法绑定 - 一次插入行数不宜超过几千,PostgreSQL 单语句解析开销随行数非线性增长;超 5000 行建议分批
- 如果主键是自增但用了
INSERT ... SELECT方式(比如从另一张表导入),RETURNING依然有效,但需确保SELECT的列顺序与目标表字段严格匹配
最常被忽略的是:当插入触发器(BEFORE INSERT)修改了 NEW.id,RETURNING id 返回的是触发器处理后的值——这本来是正确行为,但若没意识到触发器存在,会误以为 ID 被“偷偷改了”。

















