last_insert_id()是mysql中唯一可靠获取刚插入自增主键的方式,它返回当前会话最后一次insert生成的第一个自增id,不跨会话、不依赖表名、不受并发干扰,必须在同一连接中紧接insert后调用。

MySQL 中用 LAST_INSERT_ID() 拿刚插入的自增 ID
执行 INSERT 后立刻获取 ID,最稳妥的方式是调用 LAST_INSERT_ID() ——它返回当前会话中最后一次 INSERT 生成的自增值,**不依赖表名、不受其他会话干扰**。
常见错误是误用 SELECT MAX(id) 或 SELECT id FROM table ORDER BY id DESC LIMIT 1,这在并发写入时大概率拿错 ID。
- 必须在同一次数据库连接(session)中调用,跨连接或事务提交后失效
- 即使
INSERT带有IGNORE或失败,LAST_INSERT_ID()也不会更新(这点和 PostgreSQL 的RETURNING不同) - 如果
INSERT ... SELECT插入多行,它只返回第一行生成的 ID
示例:
INSERT INTO users (name, email) VALUES ('Alice', 'a@example.com');
SELECT LAST_INSERT_ID();
PostgreSQL 用 RETURNING 一行搞定
PostgreSQL 原生支持在 INSERT 语句末尾加 RETURNING 子句,直接返回指定字段(包括自增列),无需额外查询。
这是最简洁、线程安全、且能返回多列的方式。注意:不是所有客户端都默认解析多结果集,有些 ORM 或驱动需显式启用 RETURNING 支持。
-
RETURNING *可返回整行,但要注意大字段或 JSON 列可能影响性能 - 若表使用
serial,返回的是id;若用IDENTITY列,行为一致 - 不能在批量插入(
INSERT ... VALUES (...), (...))中只取某一行的 ID —— 它会返回所有新行的对应值
示例:
INSERT INTO users (name, email) VALUES ('Bob', 'b@example.com') RETURNING id;
SQL Server 的 SCOPE_IDENTITY() 是更安全的选择
避免用 @@IDENTITY —— 它会受触发器里隐式插入的自增影响而返回错误 ID。SCOPE_IDENTITY() 限定在当前作用域(当前批处理或存储过程),更可靠。
另一个选项是 OUTPUT 子句(类似 PostgreSQL 的 RETURNING),但它要求语句必须是独立批处理,不能跟在 DECLARE 后面而不加分号,否则语法报错。
-
SCOPE_IDENTITY()必须在INSERT后立即执行,中间不能有其他语句(哪怕只是SELECT 1) -
OUTPUT支持返回多列甚至表达式,比如OUTPUT INSERTED.id, GETDATE() - 如果用 Entity Framework,它默认用
SCOPE_IDENTITY(),但手动写 SQL 时容易忽略这个细节
示例:
INSERT INTO users (name, email) VALUES ('Charlie', 'c@example.com');
SELECT SCOPE_IDENTITY() AS id;
ORM 层怎么写才不踩坑
多数主流 ORM(如 Django ORM、SQLAlchemy、MyBatis、Entity Framework)封装了上述逻辑,但行为差异很大:
- Django 的
model.save()默认返回带id的实例,但前提是模型没禁用auto_created = True的主键 - SQLAlchemy 的
session.flush()后可读obj.id,但未commit()前该 ID 在数据库中尚未最终确认(极端情况下回滚会失效) - MyBatis 的
useGeneratedKeys="true"配合keyProperty才生效,漏配会导致id仍是 null - 直连 JDBC/ODBC 时,务必检查是否启用了
RETURN_GENERATED_KEYS标志位,否则getGeneratedKeys()返回空结果集
最易被忽略的一点:某些数据库连接池(如 HikariCP)默认关闭了 allowMultiQueries 或 rewriteBatchedStatements,这会导致多语句模式下 LAST_INSERT_ID() 失效 —— 看似代码没错,实则是驱动配置卡住了。










