在觸發器、巢狀預存程序等多層級插入場景下,@@IDENTITY 會返回「最後一次跨範圍插入」的識別值,導致業務邏輯錯誤。本文透過實測對比,徹底釐清 @@IDENTITY、SCOPE_IDENTITY()、IDENT_CURRENT() 三者差異與適用場景。
-- 建立測試表
CREATE TABLE T_A (A_ID INT IDENTITY(1,1) PRIMARY KEY, A_Name VARCHAR(20));
CREATE TABLE T_B (B_ID INT IDENTITY(100,1) PRIMARY KEY, B_Name VARCHAR(20));
GO
-- 在 T_A 建立 INSERT 觸發器,同步寫入 T_B
CREATE TRIGGER trg_T_A_Ins ON T_A AFTER INSERT AS
BEGIN
INSERT INTO T_B (B_Name) SELECT 'From Trigger' FROM inserted;
END;
GO
-- 測試
INSERT INTO T_A (A_Name) VALUES ('Direct Insert');
SELECT @@IDENTITY AS AtAtIdentity, SCOPE_IDENTITY() AS ScopeIdentity;
-- 結果:@@IDENTITY = 100 (T_B 的值) | SCOPE_IDENTITY() = 1 (T_A 的值) ✅
| 函數 | 範圍 | 會話隔離 | 適用場景 | 風險 |
|---|---|---|---|---|
@@IDENTITY | 全域 (跨 Trigger/SP/批次) | 當前會話 | 極少數需追蹤跨範圍最後插入的審計場景 | 觸發器/巢狀 SP 會汙染結果 ❌ |
SCOPE_IDENTITY() | 當前範圍 (Batch/SP/Trigger 同層級) | 當前會話 | **99% 情況的首選** ✅ 取得「我剛插入的那張表」自增值 | |
IDENT_CURRENT('table') | 指定資料表 (跨會話、跨範圍) | 全域 | 需知某表全域最後識別值 (如異步同步檢查) | 高併發下可能非當前會話所插 ❌ |
SCOPE_IDENTITY()INSERT INTO T_A (A_Name) OUTPUT INSERTED.A_ID VALUES ('Test');
inserted 虛擬表取得IDENT_CURRENT 僅供監控/診斷,不可用於業務邏輯取號USE tempdb;
GO
CREATE TABLE TZ (Z_id INT IDENTITY(1,1) PRIMARY KEY, Z_name VARCHAR(20) NOT NULL);
GO
-- 測試 1:單純插入
INSERT INTO TZ (Z_name) VALUES ('A');
SELECT @@IDENTITY AS [@@ID], SCOPE_IDENTITY() AS [SCOPE], IDENT_CURRENT('TZ') AS [CURRENT];
-- 三者皆為 1
-- 測試 2:加上觸發器
CREATE TRIGGER trg_TZ ON TZ AFTER INSERT AS
BEGIN
DECLARE @t TABLE (id INT);
INSERT INTO TZ (Z_name) OUTPUT INSERTED.Z_id INTO @t SELECT 'Trigger';
-- 此處若用 @@IDENTITY 會被汙染
END;
GO
INSERT INTO TZ (Z_name) VALUES ('B');
SELECT @@IDENTITY, SCOPE_IDENTITY(), IDENT_CURRENT('TZ');
-- @@IDENTITY = 2 (觸發器插入) | SCOPE_IDENTITY() = 1 (主插入) ✅
牢記:SCOPE_IDENTITY() 是開發者的守護神,@@IDENTITY 是歷史包袱,IDENT_CURRENT() 是監控工具。選對函數,避開隱形 Bug。