OUTPUT子句是最安全、原子性最强的获取自增ID方式,可一次性返回多行插入的全部ID及关联字段,避免SCOPE_IDENTITY()仅返回最后一个ID、易受并发干扰等问题;但要求目标表不能有启用的触发器。

可以直接用 OUTPUT 子句捕获刚插入的自增列值,无需额外 SELECT SCOPE_IDENTITY() 或触发器——这是最安全、原子性最强的方式。
为什么不能只靠 SCOPE_IDENTITY()?
它只返回单个 ID,且在并发插入时若中间夹杂其他语句(比如日志写入、临时表操作),可能被干扰;更关键的是,如果插入多行,SCOPE_IDENTITY() 只能拿到最后一条的 ID,丢掉其余所有。而 OUTPUT 能按行原样返回整套插入结果。
OUTPUT 基本写法与注意事项
必须和 INSERT 语句紧邻使用,不能换行加 GO,也不能放在事务外单独执行。返回目标可以是表变量、临时表或直接到客户端。
- 用表变量接收时,定义结构必须和
OUTPUT列完全一致(包括数据类型、是否允许 NULL) -
INSERTED是唯一可用的伪表,不能写成DELETED或省略前缀 - 自增列名必须显式写出,不能用
*(OUTPUT INSERTED.*在含计算列或大对象类型时会报错)
DECLARE @InsertedIDs TABLE (ID INT);
INSERT INTO Users (Name, Email)
OUTPUT INSERTED.ID INTO @InsertedIDs
VALUES ('Alice', 'a@example.com'), ('Bob', 'b@example.com');
批量插入时怎么拿全部 ID 并关联原始数据?
如果原始数据有业务标识(比如导入批次号、临时序号),建议在 OUTPUT 中一并输出,避免后续靠顺序硬匹配。SQL Server 不保证 INSERTED 的物理顺序和 VALUES 子句完全一致(尤其开启并行或有触发器时)。
- 向表变量输出时,可额外包含
OUTPUT INSERTED.ID, INSERTED.Name等非自增字段 - 若需保留插入前的上下文,可在
INSERT前把原始数据存进临时表,再用OUTPUT关联更新 - 不要依赖
@@ROWCOUNT推算 ID 范围——自增步长可能不是 1,还可能跳号
常见报错和绕过方式
The target table 'xxx' of the DML statement cannot have any enabled triggers if the statement contains an OUTPUT clause. —— 这是最常卡住人的限制:只要表上有启用的 INSTEAD OF 或 AFTER 触发器,OUTPUT 就直接失败。
- 临时禁用触发器不可取(破坏业务逻辑、不支持只禁用部分触发器)
- 改用
INSERT ... SELECT+OUTPUT绕过:把数据先塞进临时表,再从临时表插入目标表 - 实在无法移除触发器,只能退回到
INSERT+SELECT TOP (@n) ID FROM ... ORDER BY ID DESC,但要加WITH (XLOCK, HOLDLOCK)防并发错乱
真正麻烦的从来不是语法本身,而是触发器存在时的兼容处理,以及多行插入后如何可靠地把 ID 和原始业务记录一一对应上——这两点容易在测试环境被忽略,上线后才暴露。










