LAST_INSERT_ID() 返回当前会话中最近一次INSERT或REPLACE语句生成的第一行自增主键值,具有会话隔离性、语句级特性且不受其他操作干扰。

LAST_INSERT_ID() 返回的是什么值
LAST_INSERT_ID() 返回的是当前会话中最近一次 INSERT 或 REPLACE 语句生成的自增主键值。它**不是全局的、不依赖表名、也不受其他会话影响**,只和当前连接(session)内最后一次成功插入有关。
关键点在于:这个函数返回的是“语句级”的结果——哪怕你用一条 INSERT ... SELECT 插入了 100 行,LAST_INSERT_ID() 也只返回第一行生成的 ID(不是最大 ID,也不是最后插入的 ID)。
- 如果插入多行且使用了
INSERT ... VALUES (a),(b),(c),返回的是第一行的自增 ID - 如果插入失败(如唯一键冲突),
LAST_INSERT_ID()不更新 - 在存储过程中调用多个
INSERT,每次调用后都应立即读取,否则会被后续插入覆盖
在存储过程中正确获取刚插入的 ID
必须在 INSERT 之后、任何其他可能触发自增操作的语句(包括另一条 INSERT、甚至对另一张带自增列的表的操作)之前,调用 LAST_INSERT_ID() 并赋值给变量。
常见错误是把获取逻辑写在存储过程末尾,或中间夹杂了日志插入、临时表操作等——这些都可能悄悄改变 LAST_INSERT_ID() 的值。
DELIMITER $$ CREATE PROCEDURE insert_user_and_log(IN p_name VARCHAR(50)) BEGIN DECLARE new_id BIGINT UNSIGNED; <p>INSERT INTO users (name) VALUES (p_name); -- ✅ 紧接着获取,不能延迟 SET new_id = LAST_INSERT_ID();</p><p>-- ❌ 错误示例:下面这行如果 users_log 表也有自增主键,就会覆盖 new_id 的来源 -- INSERT INTO users_log (user_id, action) VALUES (new_id, 'created');</p><p>INSERT INTO users_log (user_id, action) VALUES (new_id, 'created'); END$$ DELIMITER ;</p>
为什么不能用 SELECT MAX(id) 替代
用 SELECT MAX(id) FROM table 看似简单,但在并发场景下完全不可靠:另一个会话可能在你 INSERT 后、SELECT MAX 前插入了一条记录,导致你拿到的是别人的 ID。
LAST_INSERT_ID() 是会话安全的,天然隔离,不需要加锁或事务控制(当然,事务本身仍需合理设计)。
-
MAX(id)还可能被误读为“最新插入”,但其实只是“当前最大”,不代表刚插的那条 - 如果表中存在手动指定 ID 的插入(如
INSERT INTO t(id,name) VALUES(100,'x')),MAX(id)会跳变,而LAST_INSERT_ID()仍准确反映本次自增生成的值 - 性能上,
LAST_INSERT_ID()是内存变量访问,MAX(id)是全索引扫描(即使有索引,也要走一遍)
注意 INSERT IGNORE / ON DUPLICATE KEY UPDATE 的行为
当使用 INSERT IGNORE 或 ON DUPLICATE KEY UPDATE 时,LAST_INSERT_ID() 的行为容易被忽略:
- 如果因重复键被忽略(
INSERT IGNORE),LAST_INSERT_ID()**不会更新**,保持之前值 - 如果发生更新(
ON DUPLICATE KEY UPDATE),且没有显式设置id = LAST_INSERT_ID(id),则LAST_INSERT_ID()**也不会更新** - 若想在更新时也捕获 ID,需显式写成:
INSERT INTO t(id,name) VALUES(0,'x') ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id)
也就是说,别假设“只要写了 INSERT,就一定有新 ID”——得看实际执行路径是否真触发了自增列的分配。
最稳妥的做法:始终检查 ROW_COUNT() 和 LAST_INSERT_ID() 的组合,确认插入是否真实发生。











