「存儲過程插入資料後,如何取得剛插入那筆資料的自增主鍵值?」看似簡單,卻能測試開發者對併發、觸發器副作用的理解深度。
| 錯誤作法 | 風險說明 |
|---|---|
| SELECT MAX(ContactID) FROM Test | 併發環境下極易取得他人插入的 ID |
| SELECT TOP 1 ContactID FROM Test ORDER BY ContactID DESC | 同 MAX(),併發不安全 |
| 組合插入欄位形成唯一鍵反查 | 業務鍵不一定唯一,不可靠 |
| 使用 @@IDENTITY | 若表有觸發器且觸發器插入其他含 Identity 的表,回傳值會是觸發器插入的那張表的 Identity,而非本表 |
-- 建立測試環境
CREATE DATABASE IdentityTest;
USE IdentityTest;
CREATE TABLE TEST (ContactID INT IDENTITY(1,1), firstname VARCHAR(100), lastname VARCHAR(100));
CREATE TABLE TESTHISTORY (HistoryID INT IDENTITY(1,1), ContactID INT, firstname VARCHAR(100), lastname VARCHAR(100));
-- 建立觸發器:插入 TEST 時同步寫入 TESTHISTORY
CREATE TRIGGER i_TEST ON dbo.TEST FOR INSERT AS
BEGIN
SET NOCOUNT ON;
INSERT INTO TESTHISTORY (ContactID, FirstName, LastName)
SELECT ContactID, FirstName, LastName FROM Inserted;
END;
-- 測試 1:無觸發器干擾時兩者相同
INSERT INTO TEST DEFAULT VALUES; SELECT @@IDENTITY; -- 1
INSERT INTO TEST DEFAULT VALUES; SELECT SCOPE_IDENTITY(); -- 3
-- 測試 2:有觸發器時 @@IDENTITY 錯誤
INSERT INTO TEST DEFAULT VALUES; SELECT @@IDENTITY; -- 回傳 TESTHISTORY 的 HistoryID (1)
INSERT INTO TEST DEFAULT VALUES; SELECT @@IDENTITY; -- 回傳 5 (TESTHISTORY 第二筆)
-- 測試 3:併發模擬
-- 連線 A 插入第 6 筆
-- 連線 B 插入第 7 筆
-- 連線 A 執行 SELECT @@IDENTITY → 回傳 3 (TESTHISTORY 第三筆) ❌
-- 連線 A 執行 SELECT SCOPE_IDENTITY() → 回傳 6 ✅
SQL Server 2000/2005/2008 開發必須使用 SCOPE_IDENTITY() 取得剛插入的 Identity 值。@@IDENTITY 因觸發器副作用與併發問題極不安全,僅適合 SQL Server 7.0 等舊版本無選擇時使用。