萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 資料庫 >> MySQL 教學

MySQL 插入資料後獲取自增 ID 完整指南:LAST_INSERT_ID() vs @@IDENTITY vs 驅動層 API

在 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 APIStatement 級✅ 是Java/JDBC 標準做法
ExecuteScalar() (ADO.NET)ADO.NET APICommand 級✅ 是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 (各自獨立)

三、各語言/框架最佳實踐

1. C# / .NET (MySql.Data / MySqlConnector)

// 推薦:使用 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。

2. Java / JDBC (MySQL Connector/J)

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);
        }
    }
}

3. Python (PyMySQL / mysqlclient)

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()

4. PHP (PDO / mysqli)


// 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";

5. Node.js (mysql2 / promise)

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

6. Go (go-sql-driver/mysql)

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)

四、進階場景與陷阱

1. 批次插入多行:只返回第一行 ID

INSERT INTO users (name) VALUES ('A'), ('B'), ('C');
SELECT LAST_INSERT_ID();  -- 返回第一行 ID (如 100),後續行為 101, 102...

需自行計算:第一行 ID + 行索引。

2. INSERT ... ON DUPLICATE KEY UPDATE

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) 指定)

3. 事務中獲取 ID

在同一交易內,LAST_INSERT_ID() 可正常獲取;交易回滾後,自增值**不回滾**(已消耗),下次插入會跳號,這是正常行為。

4. 混合存儲引擎陷阱

LAST_INSERT_ID() 僅對 AUTO_INCREMENT 有效。MyISAM/MEMORY 表同樣支援;但若表無自增主鍵,返回 0。

五、總結

獲取 MySQL 自增 ID 的黃金法則:

  1. 優先使用驅動原生 API(LastInsertedId、getGeneratedKeys、lastrowid、insert_id),無額外網路往返,效能最佳。
  2. 次選 SELECT LAST_INSERT_ID(),在同一連線、同一交易內執行,併發安全。
  3. 絕不使用 SELECT MAX(id) 或 SELECT @@IDENTITY 替代(前者併發不安全,後者同義但非標準)。
  4. 批次插入僅返回首行 ID,其餘需推算。

掌握這些,告別「插入後查不準 ID」的困擾。

copyright © 萬盛學電腦網 all rights reserved