MySQL 自增主鍵(AUTO_INCREMENT)插入後,獲取該值有三種標準途徑:SQL 函數 LAST_INSERT_ID()、客戶端驅動 API、存儲過程輸出參數。本文深度對比原理、併發安全性、適用場景、陷阱。
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
SELECT LAST_INSERT_ID(); -- 返回本連接生成的第一個自增值
-- 批量插入返回第一行 ID
INSERT INTO users (name) VALUES ('Bob'), ('Carol'), ('Dave');
SELECT LAST_INSERT_ID(); -- 返回 Bob 的 ID
核心特性:
INSERT/UPDATE/REPLACE 觸發更新| 語言/驅動 | API | 示例 |
|---|---|---|
| PHP PDO | $pdo->lastInsertId() | $pdo->exec("INSERT ..."); $id = $pdo->lastInsertId(); |
| PHP mysqli | $mysqli->insert_id | $mysqli->query("INSERT ..."); $id = $mysqli->insert_id; |
| Python mysqlclient | cursor.lastrowid | cursor.execute("INSERT ..."); id = cursor.lastrowid |
| Java JDBC | Statement.getGeneratedKeys() | stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS); rs = stmt.getGeneratedKeys(); |
| Go database/sql | result.LastInsertId() | res, _ := db.Exec("INSERT ..."); id, _ := res.LastInsertId() |
| Node.js mysql2 | result.insertId | const [res] = await pool.execute("INSERT ..."); id = res.insertId |
💡 本質:驅動內部調用 LAST_INSERT_ID() 或讀取 OK 包中的 insert_id 字段。
DELIMITER //
CREATE PROCEDURE insert_user(IN p_name VARCHAR(50), OUT p_id INT)
BEGIN
INSERT INTO users (name) VALUES (p_name);
SET p_id = LAST_INSERT_ID();
END //
DELIMITER ;
CALL insert_user('Eve', @new_id);
SELECT @new_id; -- 獲取新 ID
| 維度 | LAST_INSERT_ID() | 驅動 API | 存儲過程 OUT |
|---|---|---|---|
| 調用位置 | SQL 層 | 應用層 | SQL/應用層 |
| 併發安全 | ✅ 連接隔離 | ✅ 連接隔離 | ✅ 連接隔離 |
| 批量插入 | 返回首行 ID | 返回首行 ID | 可邏輯控制 |
| 事務回滾 | 值不回滾 | 值不回滾 | 值不回滾 |
| 推薦場景 | SQL 腳本、報表、臨時查詢 | 應用代碼(99% 場景) | 業務邏輯下沉 DB 時 |
START TRANSACTION;
INSERT INTO orders (user_id, total) VALUES (123, 99.9);
SET @order_id = LAST_INSERT_ID();
INSERT INTO order_items (order_id, product_id, qty) VALUES (@order_id, 1, 2), (@order_id, 2, 1);
COMMIT;
應用層用驅動 API(lastInsertId/insert_id/lastrowid),
SQL 腳本用 LAST_INSERT_ID(),
存儲過程用 OUT 參數承接。
永遠別用 MAX(id) —— 併發必翻車。