last_insert_id() 返回旧值主要是因会话级变量未及时读取或连接复用未重置;它只返回本会话最近一次插入的首个自增id,不跨会话、不跨事务,批量插入时仅返起始id。

MySQL 插入后 LAST_INSERT_ID() 返回旧值?先看事务隔离和连接复用
不是 SQL 写错了,大概率是连接没关、事务没提交,或者用了连接池却没清空会话状态。MySQL 的 LAST_INSERT_ID() 是会话级变量,只在当前连接内有效,且仅对本会话中最近一次成功插入的自增 ID 生效。
- 同一连接里连续执行两次
INSERT,但中间没调LAST_INSERT_ID()—— 第二次调用仍返回第一次的 ID(除非第二次也触发了新插入) - 使用连接池(如 HikariCP、Druid)时,连接被复用,上一个请求留下的
LAST_INSERT_ID()值可能还在,必须在每次获取连接后显式重置或立即读取 - 在未提交的事务中插入,然后在另一个连接查
SELECT LAST_INSERT_ID(),结果为 0 —— 它不跨会话,也不跨事务可见
innodb\_autoinc\_lock\_mode = 2(交错模式)导致自增 ID 跳变,但不影响 LAST_INSERT_ID()
MySQL 5.1.22+ 默认启用 innodb_autoinc_lock_mode = 2,提升并发插入性能,但会让自增 ID 出现“间隙”(比如插入 1,2,4,5),这是正常行为,不是 bug。
-
LAST_INSERT_ID()返回的仍是本次语句实际分配到的第一个 ID(例如批量插入INSERT INTO t VALUES (),(),()分配了 10,11,12,则返回 10) - 跳号不影响业务逻辑,除非你依赖“连续 ID”做分页、导出或外部系统映射 —— 这种依赖本身就有风险
- 切勿为避免跳号而降级为
innodb_autoinc_lock_mode = 0(传统锁模式),它会在大批量插入时严重阻塞其他 INSERT
批量插入时 LAST_INSERT_ID() 只返回起始 ID,别误以为能拿到全部
执行 INSERT INTO user (name) VALUES ('a'),('b'),('c') 后调 SELECT LAST_INSERT_ID(),返回的是第一个生成的 ID(比如 100),不是 100,101,102 的数组。
- MySQL 不提供“本次插入所有 ID”的内置函数;需要自己用
SELECT MAX(id) FROM user WHERE ...配合时间/条件过滤(不推荐,竞态高) - 更稳妥的做法:拆成单条插入(牺牲吞吐保 ID 可控),或改用带唯一业务键的表结构,用业务键反查 ID
- ORM 如 MyBatis 的
useGeneratedKeys="true"或 JPA 的@GeneratedValue底层仍是靠LAST_INSERT_ID(),所以同样只返回首 ID
优化建议:别等插入完再查 ID,优先用 INSERT ... SELECT 或提前预留
真正影响延迟的往往不是自增 ID 生成本身(纳秒级),而是网络往返 + 连接调度 + 事务提交开销。想“快”,得绕开“插入 → 查 ID → 下一步操作”这个串行链路。
- 如果下步操作是关联插入(如写
order再写order_item),直接用INSERT INTO order_item (order_id, ...) SELECT LAST_INSERT_ID(), ...在服务端完成,省一次 round-trip - 高频场景可预生成 ID 段(如用独立号段服务发一批 1000 个 ID 缓存本地),但要处理宕机丢失和分配冲突
- 确认是否真需要自增主键:UUID 或雪花 ID 本地可生成,无竞争,但索引性能略差、存储大;权衡点在写吞吐 vs 读效率 vs 业务兼容性
自增 ID 的“延迟”绝大多数时候是架构层面的等待,不是 MySQL 自身卡顿。盯着 auto_increment_offset 或 innodb_autoinc_lock_mode 调参,不如先看连接怎么拿、事务怎么交、下一步操作能不能合并。











