在 MySQL 中插入帶 AUTO_INCREMENT 主鍵的資料後,如何準確獲取剛生成的 ID?本文詳細對比 LAST_INSERT_ID()、@@IDENTITY、驅動層 API(mysql_insert_id()、ExecuteScalar、GeneratedKeys)、併發安全性、事務邊界與常見陷阱,提供各語言框架最佳實踐。
| 方式 | 類型 | 作用域 | 併發安全 | 適用場景 |
|---|---|---|---|---|
LAST_INSERT_ID() | SQL 函數 | 會話級 | ✅ 是 | SQL 腳本、預存程序、驅動不支援時 |
@@IDENTITY | 系統變數 | 會話級 | ✅ 是 | 同 LAST_INSERT_ID(),語法糖 |
mysql_insert_id() (C API) | C 函數 | 連線級 | ✅ 是 | C/C++、PHP mysql_* 舊擴展 |
Connection.getGeneratedKeys() (JDBC) | JDBC API | Statement 級 | ✅ 是 | Java/JDBC 標準做法 |
ExecuteScalar() (ADO.NET) | ADO.NET API | Command 級 | ✅ 是 | C#/.NET 執行 SELECT LAST_INSERT_ID() |
LAST_INSERT_ID(expr) | SQL 函數 | 會話級 | ✅ 是 | 手動設定下一次自增值(罕用) |
LAST_INSERT_ID() 返回的是當前連線(Session)最近一次成功插入自增值產生的 ID,**與其他連線完全隔離**。併發環境下每個連線各自維護自己的值,互不干擾。
-- 連線 A
INSERT INTO users (name) VALUES ('Alice');
SELECT LAST_INSERT_ID(); -- 返回 1001
-- 連線 B (同時進行)
INSERT INTO users (name) VALUES ('Bob');
SELECT LAST_INSERT_ID(); -- 返回 1002 (各自獨立)
// 推薦:使用 GetGeneratedKeys (MySqlConnector 2.0+ / MySql.Data 8.0+)
using var conn = new MySqlConnection(connStr);
await conn.OpenAsync();
var sql = "INSERT INTO users (name, email) VALUES (@name, @email)";
using var cmd = new MySqlCommand(sql, conn);
cmd.Parameters.AddWithValue("@name", "張三");
cmd.Parameters.AddWithValue("@email", "zhang@example.com");
// 關鍵:設定 UpdatedRowSource = None,避免額外 SELECT
cmd.UpdatedRowSource = UpdateRowSource.None;
await cmd.ExecuteNonQueryAsync();
// 取得自增 ID (MySqlConnector 支援)
ulong newId = cmd.LastInsertedId; // MySqlConnector 專屬屬性
// 或通用做法:執行 SELECT LAST_INSERT_ID()
cmd.CommandText = "SELECT LAST_INSERT_ID()";
var id = await cmd.ExecuteScalarAsync();
Console.WriteLine($"New ID: {id}");
避坑: 舊版教學常用 ExecuteScalar 執行 INSERT 直接取 ID,**錯誤**!ExecuteNonQuery 返回影響行數,ExecuteScalar 返回首行首欄。INSERT 不返回結果集,必須分兩步或用驅動原生 API。
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, "李四");
ps.setString(2, "li@example.com");
ps.executeUpdate();
// 標準 JDBC 方式:獲取生成的鍵
try (ResultSet rs = ps.getGeneratedKeys()) {
if (rs.next()) {
long newId = rs.getLong(1);
System.out.println("New ID: " + newId);
}
}
}
import pymysql
conn = pymysql.connect(host='localhost', user='root', password='pass', db='mydb', charset='utf8mb4')
try:
with conn.cursor() as cursor:
sql = "INSERT INTO users (name, email) VALUES (%s, %s)"
cursor.execute(sql, ('王五', 'wang@example.com'))
new_id = cursor.lastrowid # 直接取屬性
print(f"New ID: {new_id}")
conn.commit()
finally:
conn.close()
// PDO
$pdo = new PDO("mysql:host=localhost;dbname=mydb;charset=utf8mb4", 'root', 'pass');
$pdo->exec("INSERT INTO users (name, email) VALUES ('趙六', 'zhao@example.com')");
$newId = $pdo->lastInsertId();
echo "New ID: $newId";
// mysqli
$mysqli = new mysqli('localhost', 'root', 'pass', 'mydb');
$mysqli->query("INSERT INTO users (name, email) VALUES ('錢七', 'qian@example.com')");
$newId = $mysqli->insert_id;
echo "New ID: $newId";
const mysql = require('mysql2/promise');
const conn = await mysql.createConnection({host:'localhost', user:'root', password:'pass', database:'mydb'});
const [result] = await conn.execute(
"INSERT INTO users (name, email) VALUES (?, ?)",
['孫八', 'sun@example.com']
);
console.log('New ID:', result.insertId); // result.insertId 即為自增 ID
db, _ := sql.Open("mysql", "root:pass@tcp(localhost:3306)/mydb")
result, _ := db.Exec("INSERT INTO users (name, email) VALUES (?, ?)", "周九", "zhou@example.com")
id, _ := result.LastInsertId()
fmt.Println("New ID:", id)
INSERT INTO users (name) VALUES ('A'), ('B'), ('C');
SELECT LAST_INSERT_ID(); -- 返回第一行 ID (如 100),後續行為 101, 102...
需自行計算:第一行 ID + 行索引。
INSERT INTO users (id, name) VALUES (100, 'Test')
ON DUPLICATE KEY UPDATE name = 'Updated';
SELECT LAST_INSERT_ID(); -- 若插入新行:返回 100;若更新現有行:返回 0 (MySQL 8.0.20+ 可用 LAST_INSERT_ID(id) 指定)
在同一交易內,LAST_INSERT_ID() 可正常獲取;交易回滾後,自增值**不回滾**(已消耗),下次插入會跳號,這是正常行為。
LAST_INSERT_ID() 僅對 AUTO_INCREMENT 有效。MyISAM/MEMORY 表同樣支援;但若表無自增主鍵,返回 0。
獲取 MySQL 自增 ID 的黃金法則:
LastInsertedId、getGeneratedKeys、lastrowid、insert_id),無額外網路往返,效能最佳。SELECT LAST_INSERT_ID(),在同一連線、同一交易內執行,併發安全。SELECT MAX(id) 或 SELECT @@IDENTITY 替代(前者併發不安全,後者同義但非標準)。掌握這些,告別「插入後查不準 ID」的困擾。