
在 Go 应用中通过 pq 驱动操作 PostgreSQL 时,若表主键为 UUID 等非自增类型,无法使用 LastInsertId();应改用 RETURNING 子句配合 QueryRow().Scan() 安全获取插入后生成的 ID。
在 go 应用中通过 pq 驱动操作 postgresql 时,若表主键为 uuid 等非自增类型,无法使用 `lastinsertid()`;应改用 `returning` 子句配合 `queryrow().scan()` 安全获取插入后生成的 id。
PostgreSQL 的 RETURNING 子句是获取插入(或更新、删除)后行数据的原生、高效且原子的方式。当你的模型使用 UUID 作为主键(例如通过 gen_random_uuid() 或应用层生成),Exec() 方法无法直接返回该值——因为 sql.Result.LastInsertId() 仅对数据库自增序列(如 SERIAL)有效,而 pq 驱动明确不支持该方法对 UUID 的实现。
✅ 正确做法是:使用 db.QueryRow() 执行带 RETURNING 的 INSERT 语句,并通过 .Scan() 提取结果:
import (
"database/sql"
"github.com/lib/pq"
)
// 假设 todo_id 是 UUID 类型(TEXT 或 UUID 列)
var id string
err := db.QueryRow(
`INSERT INTO todos (text, list_id)
VALUES ($1, $2)
RETURNING todo_id`,
text, listId,
).Scan(&id)
if err != nil {
// 处理错误:如唯一约束冲突、空值、类型不匹配等
log.Fatal("insert failed:", err)
}
// 此时 id 已成功赋值为新记录的 UUID⚠️ 注意事项:
- 必须使用
QueryRow()(而非Exec()),因为只有QueryRow支持扫描返回结果集; -
RETURNING后可指定多个字段(如RETURNING todo_id, created_at, updated_at),对应Scan()中传入多个变量地址; - 若
todo_id在数据库中定义为UUID类型(非TEXT),建议 Go 中使用pgtype.UUID或github.com/google/uuid.UUID类型接收,并确保驱动支持(推荐升级至pgx或使用pq+sql.Scanner实现自定义扫描); - 不要忽略错误:
QueryRow().Scan()在无返回行(如触发器阻止插入)、类型不匹配或空值时会返回具体错误,需显式处理。
? 小结:RETURNING 是 PostgreSQL 的核心特性,在 Go 中结合 QueryRow().Scan() 是获取非自增主键(如 UUID)的标准、安全、推荐方案。避免手动拼接 UUID 或二次查询,既保证原子性,又提升性能与可靠性。

















