不能用SELECT MAX(id),因其并发不安全、跳号、无事务保障且性能差;LAST_INSERT_ID()需紧接INSERT调用,单行返回该行ID、多行返回第一行ID、显式指定ID时仍返回指定值;各语言应使用驱动封装接口而非手动查询;连接池中需防ID被复用覆盖。

为什么不能用 SELECT MAX(id)?
它返回的不是“你刚插的那条”,而是当前表里最大的 id,问题一堆:
并发时可能拿到别人刚插入的 ID;
如果最大 id 的记录被删过,MAX(id) 就跳号;
没事务保障,哪怕加了 SELECT ... FOR UPDATE 也救不回来;
大表扫一遍 MAX() 或排序,性能直接掉底。
LAST_INSERT_ID() 必须紧接 INSERT 调用
它只对当前连接中最近一次成功 INSERT 生效,且中间不能夹其他影响自增的操作:
单行 INSERT INTO t (x) VALUES ('a') → 返回该行 ID;
多行 INSERT INTO t (x) VALUES ('a'),('b'),('c') → 返回**第一行**的 ID,不是最后一条;
显式指定自增列值(如 INSERT INTO t (id, x) VALUES (100, 'a'))→ LAST_INSERT_ID() 仍返回 100(除非开了 sql_mode=NO_AUTO_VALUE_ON_ZERO);UPDATE 或不含自增列的 INSERT 不会改变它的值。
各语言客户端别自己查 LAST_INSERT_ID()
驱动层都封装好了原生接口,手动 SELECT LAST_INSERT_ID() 多一次网络往返,还引入竞态风险:
PHP PDO:用 $pdo->lastInsertId(),别写 $pdo->query('SELECT LAST_INSERT_ID()');
MySQLi 面向对象:直接读属性 $mysqli->insert_id;
Python PyMySQL:用 cursor.lastrowid;
Node.js mysql2:取 result.insertId,比手动查快且安全。
连接池场景下最容易丢 ID
你以为的“同一个连接”可能已被归还复用,上一个请求留下的 LAST_INSERT_ID() 还在那儿,你一读就错了:
ORM(如 SQLAlchemy、MyBatis)默认不绑定连接,得确认是否开启连接绑定或显式控制生命周期;
MyBatis 中必须配 useGeneratedKeys="true" + keyProperty="id",ID 是回填到传入对象的字段里,不是方法返回值;
存储过程里要用 SET new_id = LAST_INSERT_ID() 立刻捕获,别攒着等后面再取——变量类型建议用 BIGINT,避免截断。


















