应优先使用 SCOPE_IDENTITY() 或 OUTPUT 子句获取刚插入的自增 ID;SCOPE_IDENTITY() 返回当前作用域最后生成的 IDENTITY 值,安全可靠,而 @@IDENTITY 易受触发器干扰,IDENT_CURRENT() 存在并发风险。

SQL Server 中用 SCOPE_IDENTITY() 获取刚插入的自增 ID
直接用 SCOPE_IDENTITY(),它返回当前作用域(即当前存储过程、批处理或触发器)内最后生成的 IDENTITY 值,安全可靠。别用 @@IDENTITY 或 IDENT_CURRENT() —— 前者会被触发器干扰,后者跨会话不安全。
常见错误现象:插入后查 @@IDENTITY 得到的是触发器里插入的另一个表的 ID,而不是你想要的主表 ID。
-
SCOPE_IDENTITY()必须紧跟在INSERT语句之后执行,中间不能有其他影响标识列的操作(如另一条INSERT) - 它只对
IDENTITY列有效,对SEQUENCE或NEWID()无效 - 返回类型是
numeric(38,0),如果目标变量声明为int且值超范围,会截断报错
带 OUTPUT 子句的 INSERT 更灵活也更推荐
当需要获取不止 ID(比如还要返回插入时间、计算字段或多个列),或者想避免额外查询开销时,优先用 OUTPUT。它在插入的同时就把结果集返回,原子性强,还支持写入临时表或表变量。
使用场景:插入用户后立即返回 UserID 和默认生成的 CreatedTime;批量插入多行并捕获全部新 ID。
-
OUTPUT INSERTED.ID, INSERTED.Name INTO @TempIDs可把结果存进表变量,适合后续逻辑复用 - 不能在
INSERT ... EXEC或远程表中使用OUTPUT - 如果插入语句含
JOIN或子查询,INSERTED只反映目标表的新值,不包含关联数据
为什么不用 @@IDENTITY?真实踩坑案例
假设主表 Orders 有触发器,每次插入后自动往 AuditLog 插一条记录,而 AuditLog.ID 也是 IDENTITY。这时执行 INSERT INTO Orders ...; SELECT @@IDENTITY,返回的几乎肯定是 AuditLog 的 ID,不是 Orders 的。
这种问题在线上查半天才发现是触发器干扰,尤其在迁移旧代码或加审计日志后高频出现。
-
@@IDENTITY是会话级的,不受作用域限制,任何同会话中的IDENTITY操作都会覆盖它 -
IDENT_CURRENT('Orders')看起来精准,但它查的是表级最新值,可能被其他并发插入污染 - 只有
SCOPE_IDENTITY()和OUTPUT能真正绑定到“你这条 INSERT”
存储过程中完整写法示例
下面是一个典型安全写法,兼顾可读性和健壮性:
CREATE PROCEDURE InsertOrder
@CustomerID INT,
@Amount DECIMAL(10,2),
@NewOrderID INT OUTPUT
AS
BEGIN
INSERT INTO Orders (CustomerID, Amount, CreatedDate)
VALUES (@CustomerID, @Amount, GETDATE());
<pre class="brush:php;toolbar:false;">-- 必须紧接 INSERT 后,且确保没其他 INSERT 干扰
SET @NewOrderID = SCOPE_IDENTITY();
-- 或改用 OUTPUT(如果还需其他列)
-- INSERT INTO Orders (CustomerID, Amount, CreatedDate)
-- OUTPUT INSERTED.OrderID
-- VALUES (@CustomerID, @Amount, GETDATE());END
注意 SET @NewOrderID = SCOPE_IDENTITY() 这一行不能被注释掉或挪到条件分支里——哪怕只是加了个 IF EXISTS 判断,也可能导致作用域变化,让 SCOPE_IDENTITY() 返回空或错误值。











