last_insert_id()返回当前连接最近一次insert生成的第一个自增id,会话级、不跨连接、不依赖表名;批量插入时返回首行id,事务回滚后值仍保留但id作废。

LAST_INSERT_ID() 返回的是会话级最近一次插入的自增ID
它不依赖表名,也不跨连接生效——同一个 MySQL 连接(connection)中,只要执行过 INSERT(含批量、INSERT ... SELECT、REPLACE),调用 LAST_INSERT_ID() 就能拿到该语句生成的第一个自增值。注意:不是“最后一条记录”的 ID,而是“该 INSERT 语句生成的第一个自增 ID”。
常见错误现象:SELECT LAST_INSERT_ID() 在另一个连接里查不到值;或者在触发器里误以为能拿到主表刚插的 ID,结果返回的是触发器内部 INSERT 的 ID。
- 只对当前连接有效,不同连接互不影响
- 即使中间执行了没用到自增列的
INSERT(比如向无自增字段的表插入),也不会重置它的值 - 如果
INSERT插入多行且含自增列,LAST_INSERT_ID()返回的是第一行生成的 ID,不是最后一行 - 使用
INSERT IGNORE或ON DUPLICATE KEY UPDATE时,若发生冲突更新而非插入,LAST_INSERT_ID()不变(除非显式指定LAST_INSERT_ID(expr))
在 PHP 中用 mysqli 或 PDO 获取更安全,别手动 SELECT
很多人写 SELECT LAST_INSERT_ID() 手动查询,这多了一次网络往返,还可能被并发干扰(虽然概率低)。实际应该直接调用驱动封装好的方法:
- mysqli(面向对象):
$mysqli->insert_id—— 必须在INSERT后立刻读,不能等其他查询执行完再取 - PDO:
$pdo->lastInsertId()—— 同样要求紧接在execute()之后调用 - 如果用了预处理语句(
prepare/execute),仍可安全获取,无需担心 SQL 注入影响 ID 获取逻辑 - 注意:PDO 默认不抛异常,如果
INSERT失败但没检查execute()返回值,lastInsertId()可能返回 0 或上一次成功插入的 ID
批量插入时 LAST_INSERT_ID() 返回第一个 ID,不是最大值
执行 INSERT INTO t (name) VALUES ('a'), ('b'), ('c'),假设自增起始为 100,则三行 ID 是 100/101/102,LAST_INSERT_ID() 返回 100,不是 102。这点常被误用于“判断是否全部插入成功”,其实不行。
- 想确认是否所有行都插入成功?得看
affected_rows(如 mysqli 的$mysqli->affected_rows)是否等于预期行数 - 想拿到全部新 ID?MySQL 本身不提供批量返回 ID 的语法,只能靠业务层计算(如已知起始 ID 和行数)或改用带
RETURNING的数据库(如 PostgreSQL) - 如果批量插入中途因唯一键冲突失败,部分行可能已插入,
LAST_INSERT_ID()仍返回第一个成功插入的 ID,不代表整条语句成功
事务中 LAST_INSERT_ID() 的行为与回滚无关
在事务里插入后调用 LAST_INSERT_ID(),即使后续 ROLLBACK,这个函数之前返回的值不会变,也不会“失效”。因为自增 ID 一旦分配就不可回收(除非重启 MySQL 或手动调整 AUTO_INCREMENT 值)。
- 这意味着:事务回滚后,自增计数器依然前进,会造成 ID 空洞 —— 这是正常设计,不是 bug
- 不要依赖
LAST_INSERT_ID()的值来判断事务是否最终提交成功 - 如果业务强依赖“插入即成功且 ID 可用”,应在
COMMIT后再使用该 ID 做后续操作(比如插入关联表)
INSERT 报错(比如外键不匹配),只要自增列已经取号,那个 ID 就作废了。所以高并发下空洞不可避免,也别指望靠 LAST_INSERT_ID() 来做精确的序号连续性校验。











