能,而且这是 postgresql 最干净、最原子的方式;只要主键是 serial 或 identity 列,执行 insert ... returning id 就会同步返回刚生成的值,无需额外查询,且并发安全。

RETURNING 能直接拿到刚插入的 ID 吗?
能,而且这是 PostgreSQL 最干净、最原子的方式。只要表的主键是 SERIAL 或 IDENTITY 列(比如 id SERIAL PRIMARY KEY),执行 INSERT ... RETURNING id 就会立刻返回刚生成的值,不需要额外查一次数据库。
注意:这不是“事后补查”,而是 INSERT 语句执行完成时同步返回结果集——哪怕并发插入,也绝不会拿错 ID。
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
INSERT ... RETURNING 的基本写法和常见错误
核心就是把想取的字段写在 RETURNING 后面,和 SELECT 类似,但只能作用于当前 INSERT 影响的行。
- 正确写法:
INSERT INTO users (name, email) VALUES ('Alice', 'a@example.com') RETURNING id;
- 想取多个字段:
RETURNING id, created_at, name(字段必须存在于目标表)
- 别名可用但非必需:
RETURNING id AS inserted_id
- 错误:写成
RETURNING users.id —— 表名前缀在 RETURNING 中不被允许,会报 ERROR: invalid reference to FROM-clause entry
- 错误:在没有主键/唯一约束的表上用 RETURNING 取非主键字段,可能返回多行,但逻辑上你只插了一行——得确认是否真只影响一行
和 LASTVAL() / CURRVAL() 对比有什么区别?
LASTVAL() 和 CURRVAL('seq_name') 是序列函数,依赖底层序列对象,有明显风险:
-
LASTVAL() 返回当前会话中最近一次调用 NEXTVAL() 的值,但如果你中间执行了其他 INSERT(比如触发器里悄悄用了序列),它就不可靠
-
CURRVAL() 要求你提前知道序列名(如 users_id_seq),而序列名可能因迁移或手动建表不一致
- 两者都不保证与本次 INSERT 绑定——如果 INSERT 失败回滚,它们仍可能返回已被丢弃的 ID
-
RETURNING 没这些问题:它只在 INSERT 成功提交后才返回值,且严格对应本条语句
在应用代码里怎么安全读取 RETURNING 结果?
几乎所有 PostgreSQL 驱动都支持直接获取 RETURNING 的结果集(不是单个标量),行为类似执行一条 SELECT:
- Python + psycopg2:
cur.execute("INSERT ... RETURNING id", (name, email)); row = cur.fetchone(); inserted_id = row[0]
- Node.js + pg:
const res = await client.query("INSERT ... RETURNING id", [name, email]); const id = res.rows[0].id;
- Go + pgx:
err := conn.QueryRow(ctx, "INSERT ... RETURNING id", name, email).Scan(&id)
- 关键点:不要用
lastInsertId() 这类 MySQL 风格接口——PostgreSQL 没这个机制,硬用会返回 0 或报错
RETURNING 看似简单,但容易被当成“语法糖”忽略它的事务边界意义:它返回的是已持久化、已提交(或至少已锁定)的值。如果你在事务中 INSERT 后又 ROLLBACK,RETURNING 的结果依然有效——但它代表的是那个被撤销的快照。这点在调试或日志记录时容易误判。
INSERT INTO users (name, email) VALUES ('Alice', 'a@example.com') RETURNING id;
RETURNING id, created_at, name(字段必须存在于目标表)RETURNING id AS inserted_id
RETURNING users.id —— 表名前缀在 RETURNING 中不被允许,会报 ERROR: invalid reference to FROM-clause entry
LASTVAL() 和 CURRVAL('seq_name') 是序列函数,依赖底层序列对象,有明显风险:
-
LASTVAL()返回当前会话中最近一次调用NEXTVAL()的值,但如果你中间执行了其他 INSERT(比如触发器里悄悄用了序列),它就不可靠 -
CURRVAL()要求你提前知道序列名(如users_id_seq),而序列名可能因迁移或手动建表不一致 - 两者都不保证与本次 INSERT 绑定——如果 INSERT 失败回滚,它们仍可能返回已被丢弃的 ID
-
RETURNING没这些问题:它只在 INSERT 成功提交后才返回值,且严格对应本条语句
在应用代码里怎么安全读取 RETURNING 结果?
几乎所有 PostgreSQL 驱动都支持直接获取 RETURNING 的结果集(不是单个标量),行为类似执行一条 SELECT:
- Python + psycopg2:
cur.execute("INSERT ... RETURNING id", (name, email)); row = cur.fetchone(); inserted_id = row[0]
- Node.js + pg:
const res = await client.query("INSERT ... RETURNING id", [name, email]); const id = res.rows[0].id;
- Go + pgx:
err := conn.QueryRow(ctx, "INSERT ... RETURNING id", name, email).Scan(&id)
- 关键点:不要用
lastInsertId() 这类 MySQL 风格接口——PostgreSQL 没这个机制,硬用会返回 0 或报错
RETURNING 看似简单,但容易被当成“语法糖”忽略它的事务边界意义:它返回的是已持久化、已提交(或至少已锁定)的值。如果你在事务中 INSERT 后又 ROLLBACK,RETURNING 的结果依然有效——但它代表的是那个被撤销的快照。这点在调试或日志记录时容易误判。
cur.execute("INSERT ... RETURNING id", (name, email)); row = cur.fetchone(); inserted_id = row[0]
const res = await client.query("INSERT ... RETURNING id", [name, email]); const id = res.rows[0].id;
err := conn.QueryRow(ctx, "INSERT ... RETURNING id", name, email).Scan(&id)
lastInsertId() 这类 MySQL 风格接口——PostgreSQL 没这个机制,硬用会返回 0 或报错









