某些情况下需要插入记录后立即获取id如何解决

最后发布时间 : 2026-02-26 10:17:34 浏览量 :

这个问题在生产环境非常常见,而且不同数据库写法完全不同

我给你一个按数据库划分的“生产级正确姿势”。


一、核心思路

让数据库在 INSERT 时直接返回主键
❌ 不要再去 select max(id)
❌ 不要手动查


二、PostgreSQL(推荐方式)

如果你用的是 PostgreSQL(你之前用 $1 占位符,大概率是 PG)

直接用:

INSERT INTO users (nickname)
VALUES ($1)
RETURNING id;

Go 示例:

var id int64
err := db.QueryRowContext(ctx,
    "INSERT INTO users (nickname) VALUES ($1) RETURNING id",
    nickname,
).Scan(&id)

✅ 插入
✅ 立即得到 id
✅ 并发安全

这是 PG 的标准做法。


三、MySQL(常见做法)

MySQL 没有 RETURNING(8.0.21+ 才开始支持部分 returning)

传统方式:

res, err := db.ExecContext(ctx,
    "INSERT INTO users (nickname) VALUES (?)",
    nickname,
)

id, err := res.LastInsertId()

注意:

  • 必须是自增主键
  • 不能跨连接
  • 必须同一个 session

四、为什么不能这样做?

SELECT MAX(id) FROM users;

高并发下会:

  • 读到别人的 id
  • 完全错误

这是新手常犯错误。


五、如果你用的是雪花算法

那更简单:

在应用层生成 ID:

id := snowflake.Generate()

然后:

INSERT INTO users (id, nickname)
VALUES (?, ?)

优点:

  • 不需要等待数据库生成
  • 不需要 RETURNING
  • 分布式安全
  • 性能更好

大厂基本都这么做。

例如:

  • Twitter 的 Snowflake
  • ByteDance 也类似

六、UUID 情况

如果你用 uuid 作为主键:

直接应用层生成:

id := uuid.New()

然后插入即可。

不需要再查。


七、事务中的正确写法

如果插入后马上要插入子表:

tx, _ := db.BeginTx(ctx, nil)

var userID int64
err := tx.QueryRowContext(ctx,
    "INSERT INTO users (nickname) VALUES ($1) RETURNING id",
    nickname,
).Scan(&userID)

_, err = tx.ExecContext(ctx,
    "INSERT INTO profiles (user_id) VALUES ($1)",
    userID,
)

tx.Commit()

保证一致性。


八、生产环境推荐方案总结

场景推荐方式
PostgreSQLRETURNING id
MySQLLastInsertId()
分布式应用层雪花ID
UUID主键应用层生成

九、结合你现在的 Go 高并发项目

如果你准备做:

  • 高并发
  • 微服务
  • 分布式部署

我建议:

应用层生成 int64 雪花ID

这样:

  • 不依赖数据库自增
  • 不会出现热点主键
  • 不用关心 RETURNING
  • 扩展性强

如果你愿意,我可以:

  • 给你写一个 Go 雪花算法完整实现
  • 或者给你一个 Postgres + pgxpool 生产级插入模板
  • 或者分析你当前架构应该用哪种

你现在用的是 PostgreSQL 还是 MySQL?