insert 后用 returning id 可安全获取生成的主键值,需确保目标列有自动生成逻辑(如 serial 或 default),显式指定列名更可靠,批量插入时 returning 返回与插入行严格对应的完整结果集。

能直接返回,且必须用 RETURNING,别碰 LASTVAL() 或 CURRVAL() —— 它们在并发或批量场景下会出错。
INSERT 后怎么写 RETURNING 才拿到 ID?
核心是:在 INSERT 语句末尾加 RETURNING id,前提是目标列确实在插入时生成了值(比如 SERIAL、GENERATED ALWAYS AS IDENTITY,或带 DEFAULT nextval(...))。
- 显式指定列名比
RETURNING *更安全,避免字段顺序或默认值干扰 - 如果插入时手动指定了
id值(如INSERT INTO t (id, name) VALUES (123, 'x')),RETURNING id返回的就是你写的123,不是“新生成”的 - 对 UUID 主键(如用
gen_random_uuid()),只要该列在VALUES或DEFAULT中被赋值,RETURNING id就能取到 - 语法错误常见于把
RETURNING写成子查询或放在WITH内部——它必须紧贴在INSERT/UPDATE/DELETE语句末尾
批量 INSERT 怎么确保每行 ID 都不丢?
多行 VALUES + RETURNING 是原子的,返回结果集行数与插入行数严格一致,顺序也完全对应。
- 客户端必须调用
fetchall()(Python psycopg2)或遍历res.rows(Node.js pg),不能只取首行 - 若其中某行违反约束(如唯一键冲突),整个语句失败,不返回任何行 —— 没有“部分成功”
- 想跳过冲突继续插?得用
ON CONFLICT DO NOTHING,此时RETURNING只返回真正插入的行 - 一次插入不宜超过 5000 行,否则单语句解析开销陡增,容易触发 PostgreSQL 的内存或超时限制
和 ON CONFLICT 一起用时 RETURNING 还有效吗?
有效,但返回内容取决于实际执行的操作类型。
-
ON CONFLICT ... DO NOTHING:只返回成功插入的行 -
ON CONFLICT ... DO UPDATE:所有匹配冲突键的行都会出现在结果中(包括未真正修改数据的行) - 可用技巧:
RETURNING id, xmax = 0 AS is_inserted,xmax = 0表示该行是新插入的,否则为更新 - 注意:
DO UPDATE SET子句里不能引用RETURNING中定义的别名,它们不在同一作用域
客户端代码里最容易漏掉哪一步?
不是写错 SQL,而是没正确消费返回结果集。
- psycopg2:必须用
cursor.execute(sql, params)+cursor.fetchone()或.fetchall();只调execute()不取值,等于没拿 - Node.js pg:必须
await client.query(...),然后从res.rows读,不是res.rowCount - Go pgx:
rows, _ := conn.Query(ctx, sql, args...)后必须循环rows.Next()并rows.Scan(),否则资源泄漏且无数据 - ORM 如 SQLAlchemy 2.0+ 默认启用
RETURNING,但若手动拼 SQL 或禁用自动返回,仍需显式加子句并处理结果
最常被当成“理所当然”的地方,其实是批量插入后只取了第一行 ID 就结束;还有就是误以为 RETURNING 能拿到触发器里改过的字段值——它拿不到,除非触发器是 AFTER 且你另起查询。










