SQL Server INSERT OUTPUT必须紧接在INSERT INTO之后、VALUES之前,不支持RETURNING;需用OUTPUT INSERTED.id等指定字段,不可省略INSERTED前缀,且结果须通过ExecuteScalar或表变量承接。

SQL Server INSERT OUTPUT必须跟在VALUES前面
很多人写成 INSERT INTO users (...) VALUES (...) RETURNING id,直接报错 Incorrect syntax near 'RETURNING'。SQL Server 不支持 RETURNING,只认 OUTPUT,而且位置很关键:它必须紧接在 INSERT INTO ... 之后、VALUES(或 SELECT)之前。
正确写法是:
INSERT INTO users (name, email)
OUTPUT INSERTED.id, INSERTED.created_at
VALUES ('Charlie', 'c@example.com');-
INSERTED是保留关键字,不能省略或拼错;写成inserted(小写)虽不报错但属于习惯问题,建议统一大写保持可读性 - 如果表有计算列或默认值(比如
created_at DEFAULT GETDATE()),OUTPUT INSERTED.created_at拿到的就是插入时实际生成的值,不是 NULL - 不能单独执行
OUTPUT子句——它必须依附于INSERT/UPDATE/DELETE,否则语法错误
OUTPUT INSERTED.* 和 OUTPUT INSERTED.id 性能差异明显
用 OUTPUT INSERTED.* 看起来省事,但实际会把整行所有字段都序列化返回,包括大文本(TEXT、NVARCHAR(MAX))、二进制(VARBINARY)等字段,哪怕你只需要一个 id。
尤其在高并发插入场景下,网络传输和客户端内存开销会显著上升。更隐蔽的问题是:如果某列是 SPARSE 或含 FILESTREAM,INSERTED.* 可能触发额外 I/O。
- 只选必要字段,比如
OUTPUT INSERTED.id, INSERTED.version - 避免在批量插入(
INSERT ... SELECT)中用INSERTED.*,返回结果集体积不可控 - 若需调试,临时用
INSERTED.*没问题;上线前务必收敛字段列表
OUTPUT 结果不能直接当变量用,得靠 ExecuteScalar 或临时表承接
执行带 OUTPUT 的语句后,结果不是自动赋给某个变量的。常见误解是以为 DECLARE @new_id INT; INSERT ... OUTPUT INSERTED.id 就能把值塞进 @new_id——不行,SQL Server 不支持这种隐式赋值。
正确承接方式分两种:
- 应用层调用时,用
ExecuteScalar()(如 ADO.NET)直接取第一行第一列:int id = (int)cmd.ExecuteScalar(); - 想在同一批 T-SQL 里继续用,得用临时表或表变量接收:
OUTPUT INSERTED.id INTO @temp_ids,然后SELECT @new_id = id FROM @temp_ids - 注意:如果插入零行,
ExecuteScalar()返回null,不是 0;表变量里也查不到数据,别漏判空
事务中 OUTPUT 返回的是未提交数据,其他会话看不见
这点和 PostgreSQL 的 RETURNING 行为一致,但容易被忽略。在事务内执行 INSERT ... OUTPUT,拿到的 INSERTED 值确实是刚写入的,但它对其他连接不可见,直到 COMMIT。
这意味着:
- 不能靠它做跨连接的“实时通知”——比如插入后立刻让另一个服务查这个 ID,查不到
- 如果事务回滚,
OUTPUT返回的值就作废了,但应用层已经拿到了,后续逻辑要自己处理一致性 - 在存储过程中使用时,别假设
OUTPUT返回的 ID 已“落地”,它只是当前事务快照里的值
真正需要强一致性反馈的场景,比如发消息、更新缓存,得等事务提交后再触发,不能只依赖 OUTPUT 的即时返回。

















