set nocount on 必须加,否则每条语句都返回影响行数消息,徒增网络开销;它抑制“(n rows affected)”等done_in_proc信息,减少往返流量,提升性能,且不影响@@rowcount更新和实际结果集返回。

SET NOCOUNT ON 必须加,否则每条语句都返回影响行数,徒增网络开销。减少往返次数不是“锦上添花”,而是高延迟环境下最直接有效的提速手段——尤其当客户端与数据库跨地域部署时,一次 RTT 可能高达 50–200ms,6 次查询就拖慢半秒以上。
为什么存储过程能减少网络往返
核心在于把多步逻辑压进一次数据库调用。比如转账操作:不走存储过程时,应用要依次做查余额、校验限额、扣款、入账、提交事务,至少 5–6 次 round-trip;而封装成存储过程后,客户端只发一个 EXEC usp_transfer_money @from=1001, @to=1002, @amount=500,所有校验和更新都在服务端完成。
SET NOCOUNT ON 是基础但常被忽略的开关
默认情况下,SQL Server 对每个 INSERT/UPDATE/DELETE 都返回类似 “(1 row affected)” 的消息。这些消息虽小,但在高频调用或结果集大的场景下,会显著增加网络负载和客户端解析开销。
- 必须在存储过程开头显式加上
SET NOCOUNT ON,结尾无需OFF - 它不影响
SELECT返回的结果集,只抑制“X rows affected”这类元信息 - 未启用时,ADO.NET 等驱动可能因等待这些消息而阻塞,导致超时误判
批量操作比游标或循环更彻底地消除往返
游标本质是“把集合操作拆成 N 次单行请求”,哪怕写在存储过程里,也等于主动放弃减少往返的机会。真正有效的是用集合语义一次性处理数据。
- 错例:
DECLARE cur CURSOR FOR SELECT id FROM orders WHERE status = 'pending'+ 循环UPDATE—— 每次循环都是一次内部执行+潜在日志刷写 - 正例:直接
UPDATE orders SET status = 'processed' WHERE status = 'pending' AND created_at - 若需中间计算(如按用户汇总再更新),优先用 CTE 或临时表,而非嵌套循环
避免参数嗅探导致计划失效,间接维持低往返稳定性
看似无关,实则关键:如果存储过程因首次参数生成了低效执行计划,后续调用即使数据分布不同,仍复用该计划,可能导致某次查询慢到触发重试或超时,变相增加往返次数。
- 对参数敏感的查询,加
OPTION (RECOMPILE)强制重编译(适合低频、参数差异大的场景) - 或用局部变量“断开”参数嗅探:
DECLARE @local_id INT = @input_id; SELECT ... WHERE id = @local_id - 不要依赖
sp_executesql自动解决——它只解决 SQL 注入和计划缓存,不解决嗅探本身
真正难的不是写个存储过程,而是让每一次调用都“值回票价”:不因隐式消息、游标陷阱或劣质执行计划,把省下来的网络时间又浪费在服务端低效执行上。











