前言


用了一段时间T-SQL之后,哪怕自己没用过,也多多少少看过SSMS中的SET NOCOUNT ON命令,很多性能优化文章中都有提到这个东西,它们建议尽可能使用这个命令减少网络传输的压力,那么今天来看看它是否是个鸡肋。

SET NOCOUNT的作用

首先来看看这个命令的作用,根据官方说明:阻止在结果集中返回显示受 Transact-SQL 语句或存储过程影响的行计数的消息。在说明中的“备注”部分有这么一段话,注意红字部分:
当 SET NOCOUNT 为 ON 时,将不向客户端发送存储过程中每个语句的 DONE_IN_PROC 消息。  如果存储过程中包含一些并不返回许多实际数据的语句,或者如果过程包含 Transact-SQL 循环,网络通信流量便会大量减少,因此,将 SET NOCOUNT 设置为 ON 可显著提高性能。  

如果大家对上面的DONE_IN_PROC有兴趣,可以看看这篇文章: https://msdn.microsoft.com/en-us/library/dd340553.aspx,但是我觉得没必要过于纠结。

下面创建一个测试例子,并做演示:

USE tempdb
GO

IF object_id('NocountDemo', 'U') IS NOT NULL
    DROP TABLE NocountDemo
GO

CREATE TABLE NocountDemo(
    id INT identity(1, 1),
    NAME VARCHAR(64)
    )
GO

/*
插入测试数据
*/
INSERT INTO NocountDemo
SELECT NAME
FROM master..spt_values

/*
常规使用
*/
SELECT *
FROM NocountDemo
GO

/*
使用SET NOCOUNT选项
*/
SET NOCOUNT ON;

SELECT *
FROM NocountDemo

SET NOCOUNT OFF;

首先看看默认情况,也就是SET NOCOUNT OFF的结果:

然后看看开启NOCOUNT选项的结果:

默认情况下,任何SQL语句成功完成后都会返回一些信息,而NOCOUNT用于控制是否返回影响行数,需要提醒的是,哪怕不是真正的SQL语句,比如开启包含实际执行计划功能,当执行计划成功完成后(注意是完成),也会返回影响行数到客户端(也就是这里的SSMS)。

但是从实操层面,大家是否几乎没有关注过这个信息?确实,是否成功执行完很多时候不需要查看这个信息,一些SELECT语句自然会返回数据结果。所以实际上这个信息常常是非必要的。

作为最佳实践,一般建议不返回影响行数,特别是存储过程,不过有时候它又有存在价值,比如应用程序嵌入的SQL语句的调试。

在很多性能优化的文章中(甚至前面提到的官方文档)都提到,为了减轻网络压力,建议启用这个设置(注意一点,这个设置和其他很多设置不同,它默认是“开启”的,也就是说我们需要做“关闭”操作,而很多操作是需要做“开启”操作)。这个原因貌似很合理,不过作为DBA的习惯,我更愿意深究底层原理,所以下面再看个简单例子,插入一条数据后,循环更新100万次,并获取时间差:

SET NOCOUNT OFF;

DECLARE @i INT = 1;
DECLARE @x TABLE (a INT);

INSERT @x (a)
VALUES (1);

SELECT SYSDATETIME() AS [开始时间];

WHILE @i < 1000000
BEGIN
	UPDATE @x
	SET a = 1;

	SET @i += 1;
END

SELECT SYSDATETIME() AS [结束时间];

在我本机上执行情况如下图:

本文重点关注的是影响行数,所以我们先看影响行数的情况,选择结果集中的【消息】页

从右边的滚动条可以大概预估这个行数应该很多。实际上每次insert都有一行,外加循环外层的SELECT SYSDATETIME()  ,总共10000002行,然后关闭返回影响行数,即使用SET NOCOUNT命令重新执行:

为了避免有人觉得测试没考虑并发问题,我使用SQLQueryStress工具分别对上面两套语句模拟200个线程执行100次(本机配置太低无法执行太多次):

默认情况反复执行多次:

开启NOCOUNT的情况下:

对比一下时间,差异时间不明显,如果多执行几次可能会出现反而更慢的情况,难道网上说的是假的?先别下定论,那问题在哪里?我们再回过头看看别人的描述里面的关键字“网络传输”,貌似发现问题了,因为一直以来都在单机上面测试,而且检查一下SQL Server配置管理器中的网络协议,Shared Memory是开启了,也就是说数据都在内存中传输,跟“网络”没什么关系:

那么看来问题就是这个原因,现在随便找台机器再试一下,我在国际版的Azure上开了个SQL Azure(开VM再装SQL Server实在耗时而且费用不少),如果没有条件的可以找些测试服务器甚至别人的电脑试试。

现在我在本机直接连到美国的SQL Azure上,然后再次分别执行上面的两个语句(为了避免时间过久,把100万次改为10万次):

时间对比之后发现,其实相差不大,我们不妨静下来想一下,即使100万次,产生的数据也就几十上百Bytes或者KB,对今时今日的硬件来说不可能出现明显的性能提升,之所以网上有这个说法,很可能是当初的硬件资源存在局限,无法满足今天看起来不算大的数据量。但是不管如何,个人看来,这些确实也有一定的消耗,作为编程习惯,在非必要的情况下还是建议不返回,毕竟真的没什么人用。

问题提炼

对于这个问题,我想到了两件事情:如何对待别人的“善意”和减少网络传输

1.如何对待别人的“善意”


最近上下班路上在看一本书,看完再分享一下,书上多处地方提到一个句子:One thing doesn’t fit all。说白了就是“放之四海而皆准”的反义词。我个人挺赞同这个观点,为什么非要用一个产品去实现所有功能呢?今时今日大量系统的架构都使用了很多混合技术,这也证明了这个观点的可行性。那么回归这个问题,当初别人提出这个优化或编程建议时,确实可能存在网络性能问题、甚至客户端(本例中的SSMS)的内存资源压力从而造成性能压力,正如20年前Oracle优化书籍中提到的一个表索引数不要超过5个、索引叶子节点不要缓存到内存这类建议一样,当年的硬件资源确实没办法很好支持这些特性。再看今天,软硬件已经有了长足的发展,很多当初是对的设置今天看起来已经无关重要甚至是错误的。所以在对待很多所谓的“军规”、“铁律”时,还是要自己实测一下或者论证一下。从本例中看,它不会有什么严重的后果和风险,所以是可以测试的,但是有些风险比较大的最好慎重测试。我个人认为,作为一些编程规范,不妨保留下来,因为它和下面这个有关系。在编程的时候要有减少资源使用的惯性思维。

那什么时候有用呢?前面提到了——查错

在我初为DBA的时候,有一次一个开发问我:为什么明明插入了一条数据到一个表里面,并且成功了,表却没有数据。一开始我以为是回滚,问她要完整的语句,拿过来之后发现就一个简单的insert命令,没有显式事务。然后我自己执行了一下,确实没数据,再看一下影响行数,竟然有两条(1 行受影响),这就引起我的注意了,检查表触发器,果然有一个触发器,并且功能就是一旦有插入动作,马上触发删除。也就是来一个死一个,来两个死一双。

基于这些原因,我认为具体问题具体分析才是最有意义的,哪来那么多所谓的意见建议,不考虑具体环境的建议都是耍流氓。

2.减少网络传输

之所以当初有这个说法,很大程度是因为网络传输压力。那么我们借助这个问题,引申其他资源合理使用的场景,这里说的是网络问题,那就说网络问题吧,单纯从数据库层面来说,通常网络压力在哪里呢?据本人了解,大概在这些方面:

a)        需要传输的数据量,这是真正的网络压力。量越大压力越明显,那么通常我们要做的就是在返回数据时,尽可能控制数据量,比如不要在非必要情况下使用SELECT *,尽可能在离开数据层的时候就把数据量控制下来。

b)        其他功能传输的信息,比如SQL Server一些高可用或者负载分离功能,就拿复制功能来说,为了使订阅服务器的数据与发布服务器的数据一致,发布服务器会根据实际配置实时或定时发送事务日志到订阅端重做,日志产生越多,需要传输的量就越多,对于这部分你能干预的地方就很少,非要做的话,可以在配置发布项时只选择需要同步的行与列。

c)        SQL语句,这部分实际上也不大,但是正如很多编码规范中说的,尽可能使用存储过程,其中一个理由就是直接传输SQL代码不仅不安全,而且量一大的话,也是有开销的,有些SQL代码有还几百KB,对于使用频繁的系统而言,这也是一笔开销。当然不是说禁用,具体情况具体分析吧。另外多说一句,对于超过8KB的SQL代码,SQL Server不缓存执行计划,意味着你要每次重编译可能完全一样的SQL代码,从而造成CPU、内存的压力。

d)        需要导入、导出数据库的其他格式文件,如TXT、Excel等,某些系统需要传输一些文件到服务器再进行导入或者导出数据到文件然后通过某些方式传输出数据库服务器之外,这部分可以通过修改业务逻辑来降低,但是能降低程度可能不高,那么对于这种情况,可以考虑把数据库服务器和应用程序放在一个局域网中,然后把文件最终需要传输的发生地从数据库服务器移到应用程序所在的服务器,由于应用程序容易横向扩展,所以可以通过一些技术把应用程序的负载降低,这样即使文件传输的量不能降低,最起码对数据库层服务器的资源争用能有一定的缓解。

总的来说,我个人建议保留这个功能的常态化关闭、排错时开启的说法。最重要的作用实际还是警示,让使用者时刻注意对资源的合理使用。

T-SQL注意事项(1)——SET NOCOUNT ON的去与留的更多相关文章

  1. 部分常见ORACLE面试题以及SQL注意事项

    部分常见ORACLE面试题以及SQL注意事项 一.表的创建: 一个通过单列外键联系起父表和子表的简单例子如下: CREATE TABLE parent(id INT NOT NULL, PRIMARY ...

  2. sql 游标例子 根据一表的数据去筛选另一表的数据

    sql 游标例子 根据一表的数据去筛选另一表的数据 DECLARE @MID nvarchar(20)DECLARE @UTime datetime DECLARE @TBL_Temp table( ...

  3. sql server中的 SET NOCOUNT ON 的含义

    每次我们在使用查询分析器调试SQL语句的时候,通常会看到一些信息,提醒我们当前有多少个行受到了影响,这是些什么信息?在我们调用的时候这些信息有用吗?是否可以关闭呢? 答案是这些信息在我们的客户端的应用 ...

  4. 金色的 SQL注意事项(1)

    page(1-75) 最好是没有意义的主键字段,以方便未来的扩展. PS:主键,以后标书编码填错须要改的时候,关联表都须要跟着改.假设是一个无意义的自增字段是主键就无此原因. 主键最好不要设置为联合主 ...

  5. SQL 注意事项

    -------选择表名 配置Ctrl+3 能够select * 桌 USE [NB] GO /* 物: StoredProcedure [dbo].[SP_Select] 脚本日期: 05/28/20 ...

  6. sql注意事项积累

    1.一定要记住,SQL 对大小写不敏感! 2.sql中的单引号 '',如果单引号中是字符串,代表是常量 如,select 'b.phoneNumeber' from test; 如果是数字,如'123 ...

  7. Entity Framework --Entity SQL注意事项

    Entity SQL 是 ADO.NET 实体框架 提供的 SQL 类语言,用于支持 实体数据模型 (EDM).Entity SQL 可用于对象查询和使用 EntityClient 提供程序执行的查询 ...

  8. Django关于SQL注意事项

    执行原生SQL: from django.db import connection, connections cursor = connection.cursor() cursor.execute( ...

  9. MyBatis 动态SQL注意事项

随机推荐

  1. Shiro集成Web

    Shiro不仅可以集成到web中,也可以集成Spring. 1.在WEB中添加Shrio支持 2.WEB中INI配置 3.JSP/GSP标签 在WEB中添加Shrio支持 如果要想在web中使用Shr ...

  2. Chtholly Nota Seniorious

    题目背景 大样例下发链接: https://pan.baidu.com/s/1nuVpRS1 密码: sfxg こんなにも.たくさんの幸せをあの人に分けてもらった だから.きっと 今の.私は 谁が何と ...

  3. hdu 5877 线段树(2016 ACM/ICPC Asia Regional Dalian Online)

    Weak Pair Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)Total ...

  4. 【USACO Feb 2014】Cow Decathlon

    题目描述 约翰有 N 头奶牛,组成了一直队伍参加全能比赛.比赛一共有 N 项,每头奶牛必须参加一项比 赛,每项比赛也必须有一头奶牛参加.任何一头奶牛可以胜任任何一项比赛,但得分不一样.如果第 i 头奶 ...

  5. 【bzoj4444 scoi2015】国旗计划

    题目描述 A 国正在开展一项伟大的计划 —— 国旗计划.这项计划的内容是边防战士手举国旗环绕边境线奔袭一圈.这项计划需要多名边防战士以接力的形式共同完成,为此,国土安全局已经挑选了 NN 名优秀的边防 ...

  6. [POI2000] 最长公共子串

    给出几个由小写字母构成的单词,求它们最长的公共子串的长度. 任务 从文件中读入单词 计算最长公共子串的长度 输出结果到文件 输入 文件的第一行是整数 n,1<=n<=5,表示单词的数量.接 ...

  7. ubuntu Linux下C语言open函数打开或创建文件与read,write函数详细讲解

    open(打开文件) 相关函数 read,write,fcntl,close,link,stat,umask,unlink,fopen 表头文件 #include<sys/types.h> ...

  8. 数据库学习番外篇 神奇的Redis

    数据库学习番外篇 神奇的Redis 由于最近呢小猿我找到了自己的女神,所以整个学习计划都被打乱了,本来想着一天看一张<SQLServer宝典>的.没成想,我竟然脱离了单身狗的队伍. 最近准 ...

  9. SQL之LIMIT ,OFFSET

    SELECT prod_name FROM Products LIMIT OFFSET ; LIMIT 4 OFFSET 3指示MySQL等DBMS返回从第3行(从0行计数)起的4行数据.第一个数字是 ...

  10. JS 中判断空值 undefined 和 null

    1.JS 中如何判断 undefined JavaScript 中有两个特殊数据类型:undefined 和 null,下节介绍了 null 的判断,下面谈谈 undefined 的判断. 以下是不正 ...