最后4个Sql空格会破坏Sql Server的性能?

时间:2012-08-18 14:59:50

标签: performance sql-server-2008-r2

我遇到了一个奇怪的问题。也许这里有人可以帮助我。

我的SQL Server版本是2008 R2。它运行在一个24核的服务器上。

我有2个查询字符串:

String SQL_1 = "select 
                   t.testconfig_id, t.minuteSequence, t.location_id, 
                   sum(t.vuPerNode) as totalVu, 
                   sum(t.backOffPctSum) / sum(t.recordNum) as avgBackOffPct
                from
                   (select
                       p.testconfig_id, p.minuteSequence, r.location_Id, 
                       SUM(p.activeCount) * 1.0 / COUNT(1) as vuPerNode,
                       SUM(p.backOffPct) as backOffPctSum, COUNT(1) as recordNum
                    from
                       loadtest_progress_in_minute p (nolock)
                    join 
                       loadtestRunrecord r (nolock) on p.test_id = r.test_id and p.nodeId = r.nodeId                                               
                    where
                       p.test_id = ? 
                    group by 
                       p.testconfig_id, p.minuteSequence, p.nodeId, r.location_id) t
                group by
                   t.testconfig_id, t.minuteSequence, t.location_id
                order by
                   t.testconfig_id, t.minuteSequence, t.location_id  option (maxdop 23)"

String SQL-2 = "select 
                   t.testconfig_id, t.minuteSequence, t.location_id, 
                   sum(t.vuPerNode) as totalVu, 
                   sum(t.backOffPctSum) / sum(t.recordNum) as avgBackOffPct
                from
                   (select
                       p.testconfig_id, p.minuteSequence, r.location_Id, 
                       SUM(p.activeCount) * 1.0 / COUNT(1) as vuPerNode,
                       SUM(p.backOffPct) as backOffPctSum, COUNT(1) as recordNum
                    from
                       loadtest_progress_in_minute p (nolock)
                    join 
                       loadtestRunrecord r (nolock) on p.test_id = r.test_id and p.nodeId = r.nodeId                                               
                    where
                       p.test_id = ? 
                    group by 
                       p.testconfig_id, p.minuteSequence, p.nodeId, r.location_id) t
                group by
                   t.testconfig_id, t.minuteSequence, t.location_id
                order by
                   t.testconfig_id, t.minuteSequence, t.location_id  option (maxdop 23)       "

两个查询之间的唯一区别是SQL-2最后有一个额外的制表符。

我在同一环境中使用以下代码运行这两个查询:

PreparedStatemen ps = conn.prepareStatement(SQL_1);
//PreparedStatemen ps = conn.prepareStatement(SQL_2);

ps.setLong(1234);
ps.execute();

我发现2个查询的性能有时在代码片段中非常不同。

此代码段中SQL_1的{p> ps.excute()仅花费大约10秒。当SQL_1运行时,我看到CPU使用率很高。服务器的24个CPU全部被利用。

但同一代码段中的SQL_2的ps.excute()大约需要150秒。 SQL_2运行时CPU使用率很慢。只使用24个CPU中的2个。

SQL_1和SQL_2同时运行。

但上述观察并非总是如此。 SQL_1和SQL_2的某些ps.execute()性能相同。 SQL_1和SQL_2的某些时间ps.execute()性能如上所述。

这就是我发现的。这让人非常困惑。 SQL的最后一个空格会损害SQL Server的性能吗?

我认为它不是由自动参数化缓存引起的,如中所述 Space in SQL Server 2008 R2 slows down performance

因为我通过长时间无限循环调用代码片段得到了上述观察结果。

来自wireshark我发现Microsoft JDBC会在SQL字符串和参数之间添加2个额外的标签(1个标签字符= 4个空格字符)字符,如下所示:

[20] [00] [20] [00] [20] [00] [20] [00] [20] [00] [20] [00][20] [00][20] [00]

我不知道这是否与我的问题有关。

感谢。

2012-8-20更新:

我在sql-server management studio中执行了以下3个sql。他们有相同的执行计划。但我有奇怪的发现:

declare @sql varchar(max);

set @sql='Declare @testId bigint;set @testId = 1234;select p.testconfig_id,        p.minuteSequence,r.location_Id, SUM(p.activeCount) * 1.0 / COUNT(1) as vuPerNode, SUM(p.backOffPct) as backOffPctSum, COUNT(1) as recordNum from loadtest_progress_in_minute p with( nolock,index(idx_loadtest_progress_in_minute_1) ) join loadtestRunrecord r ( nolock ) on p.test_id = r.test_id and p.nodeId = r.nodeId where p.test_id =  @testId group by p.testconfig_id, p.minuteSequence, p.nodeId, r.location_id option (maxdop 23)';
execute (@sql);

这个sql的表现非常糟糕。大约需要90秒。只使用了2个CPU。

Declare @sSQL nvarchar(2000);
Declare @paramDefine nvarchar(2000);
Declare @testId bigint;
set @testId = 1234;

set @sSQL = N'                  select              t.testconfig_id, t.minuteSequence, t.location_id, sum(t.vuPerNode) as totalVu,              sum(t.backOffPctSum) / sum(t.recordNum) as avgBackOffPct        from            (           select                  p.testconfig_id, p.minuteSequence, r.location_Id, SUM(p.activeCount) * 1.0 / COUNT(1) as vuPerNode,                 SUM(p.backOffPct) as backOffPctSum, COUNT(1) as recordNum           from                loadtest_progress_in_minute p        ( nolock )                 join                loadtestRunrecord r          ( nolock )                 on p.test_id = r.test_id and p.nodeId = r.nodeId            where               p.test_id = @P0             group by                p.testconfig_id, p.minuteSequence, p.nodeId, r.location_id          ) t                     group by            t.testconfig_id, t.minuteSequence, t.location_id        order by            t.testconfig_id, t.minuteSequence, t.location_id        option (maxdop 23)              ';
set @paramDefine = N'@P0 bigint';
execute sp_executesql @sSQL, @paramDefine, @P0 = @testId;

这个sql在管理工作室只需要大约10秒钟。使用所有CPU。

declare @p1 int
--set @p1=1
exec sp_prepexec @p1 output,N'@P0 bigint',N'                select                t.testconfig_id, t.minuteSequence, t.location_id, sum(t.vuPerNode) as totalVu,            sum(t.backOffPctSum) / sum(t.recordNum) as avgBackOffPct        from            (           select                  p.testconfig_id, p.minuteSequence, r.location_Id, SUM(p.activeCount) * 1.0 / COUNT(1) as vuPerNode,                 SUM(p.backOffPct) as backOffPctSum, COUNT(1) as recordNum           from                loadtest_progress_in_minute p        ( nolock )                 join                loadtestRunrecord r          ( nolock )                 on p.test_id = r.test_id and p.nodeId = r.nodeId            where               p.test_id = @P0             group by                p.testconfig_id, p.minuteSequence, p.nodeId, r.location_id          ) t                     group by            t.testconfig_id, t.minuteSequence, t.location_id        order by            t.testconfig_id, t.minuteSequence, t.location_id        option (maxdop 23)                      ',@P0=1234
select @p1

这个SQL性能很快。只需大约10秒钟。使用所有CPU。

更新2012-8-21

直到现在,我还没有得到关于我发现的最终结论。因为Windows世界没有打开,也许我们从来没有得到SQL Server内部的细节。在这里,我只是解释一下我发现了什么。一些解释只是我的猜测。我希望它对其他人有帮助。

1)为什么我有时会在JDBC中获得两个类似SQL的不同性能(除了最后一个制表符是否存在外,这些SQL是相同的)

我们的测试不在孤立的环境中。当我使用JDBC测试两个SQL时,其他进程也会执行SQL,同时最后一个选项卡字符存在。所以我们的测试结果受到其他过程的影响。

不同表现的根本原因是他们选择不同的执行计划。一个人选择了具有良好parellesim的执行计划。另一个人选择了糟糕的parellesim执行计划。由于所有其他进程都使用制表符执行SQL,因此不带制表符的SQL将被视为新SQL。因此,当执行没有制表符的SQL时,它会根据记录统计信息根据典型参数值生成新的执行计划。也许典型值在第一时间并不擅长表现。但实际访问的参数值直方图可以刷新执行计划缓存。没有制表符的SQL仅用于我的测试。我的测试仅使用参数值(1234)。 SQL服务器认为(1234)经常访问SQL并使用实际最常见的访问参数值刷新执行计划(1234)。所以性能变得很好。

当我切换回带有制表符的SQL时,SQL Server将采用较旧的执行计划缓存。此缓存可以由其他正在运行的进程引入,并受其他进程影响。还基于实际最流行的访问参数值生成该执行计划。但是这个值受到其他过程的影响。因此它可能不是(1234)并且基于该值的执行计划在性能上对于(1234)是不利的。这就是为什么带有制表符的SQL性能有时会很糟糕的原因。

因为有时我的带有制表符字符的SQL测试程序也可以刷新执行计划缓存,如果我的测试运行频率足以改变实际的访问参数值。具有制表符的SQL的性能有时也会变得很好。

2)为什么SSMS中的以下SQL总是很慢

declare @sql varchar(max)

set @sql='Declare @testId bigint;set @testId = 36887;select p.testconfig_id, p.minuteSequence,r.location_Id, SUM(p.activeCount) * 1.0 / COUNT(1) as vuPerNode, SUM(p.backOffPct) as backOffPctSum, COUNT(1) as recordNum from loadtest_progress_in_minute p with( nolock,index(idx_loadtest_progress_in_minute_1) ) join loadtestRunrecord r ( nolock ) on p.test_id = r.test_id and p.nodeId = r.nodeId where p.test_id =  @testId group by p.testconfig_id, p.minuteSequence, p.nodeId, r.location_id option (maxdop 23)'
execute (@sql)

因为SSMS还根据记录统计的典型参数值生成执行计划。参数值(1234)是非典型值。所以上面的SQL在开始时很慢。我猜这'执行' SSMS中的命令是特殊的,其缓存不会被实际最常见的访问参数值刷新。所以它总是很慢。根据我的实验结果,我猜' sp_prepexec'和' sp_executesql'不同于执行'并且它们的计划缓存可以通过实际最常见的访问参数值刷新,并且具有与JDBC类似的行为。

3)为什么添加重新编译提示会加快上述SQL的性能 在回答此问题之前,请先查看MSDN在线帮助文​​档中的以下文本。

"指示SQL Server数据库引擎在执行后丢弃为查询生成的计划,强制查询优化器在下次执行相同查询时重新编译查询计划。如果不指定RECOMPILE,数据库引擎会缓存查询计划并重用它们。编译查询计划时,RECOMPILE查询提示使用查询中任何局部变量的当前值,如果查询位于存储过程中,则将当前值传递给任何参数。

当只需要重新编译存储过程内部的查询子集而不是整个存储过程时,RECOMPILE是创建使用WITH RECOMPILE子句的存储过程的有用替代方法。有关更多信息,请参阅重新编译存储过程。在创建计划指南时,RECOMPILE也很有用。"

请注意以下句子:  编译查询计划时,RECOMPILE查询提示使用查询中任何局部变量的当前值,如果查询位于存储过程中,则将当前值传递给任何参数。

这意味着RECOMPILE查询提示会更改SSMS执行计划生成行为。 SSMS基于具有RECOMPILE查询提示的典型参数值生成执行计划,而SSMS基于当前参数值使用RECOMPILE查询提示生成执行paln。因此,重新编译提示使执行计划适用于当前参数值(1234)

总之,执行计划是由复杂因素选择的。我们必须仔细考虑。

1 个答案:

答案 0 :(得分:0)

首先,查询结尾处的空白区域没有区别。除了延长传输和解析语句的时间之外,空白空间可能会导致任何数据库出现性能问题。不可思议。 SQL Engine在编译阶段读取查询并生成执行计划。执行计划是运行的。在标记查询字符串的第一步中会丢失额外的空白区域。据我所知,所有数据库引擎都以这种方式工作。

在测试查询性能时,您需要处理性能可变性的首要原因:缓存。第二次运行查询时,它通常会更快,因为表已经在页面缓存中。

一种方法是清除运行之间的缓存。另一种方法是多次运行查询,并忽略第一次运行。

在任何情况下,您的第一个查询都不是正确的语法,因此可能与您所看到的内容有关。 select语句是:

select t.id1, t.sequence, t.id2, sum(t.vu) as totalVu,
       sum(t.backOffPctSum) / sum(t.recordNum) as avgBackOffPct

小组是:

group by t.testconfig_id, t.minuteSequence, t.location_id

变量t.id1,t.sequence和t.id2应该在SQL Server或任何合理的数据库中导致编译时错误,因为它们既不在聚合函数中也不在group by子句中(这是友好的挖掘MySQL中的隐藏列,这将允许这种语法。