SQL的最后4个空格会影响SQL Server性能吗?

3

我有一个奇怪的问题,希望有人能帮助我解决。

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

我有两个查询字符串:

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();

我发现在代码片段中,这两个查询的性能有时会出现很大的差异。
在此代码片段中,SQL_1 的 ps.execute() 只需要约10秒钟。当 SQL_1 运行时,我看到 CPU 使用率很高。服务器的24个CPU全部被使用。
但是,相同代码片段中的 SQL_2 的 ps.execute() 大约需要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个额外的 tab(1 个 tab 字符 = 4 个空格字符)字符,如下所示:
[20] [00] [20] [00] [20] [00] [20] [00] [20] [00] [20] [00][20] [00][20] [00]

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

谢谢。

更新2012-8-20:

我在sql-server管理工具中执行了以下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时(除了最后一个制表符是否存在之外),有时会得到不同的性能表现?

我们的测试不是在一个隔离的环境中进行的。当我用JDBC测试这两个SQL时,其他进程也同时执行具有最后一个制表符的SQL。因此,我们的测试结果受其他进程的影响。

不同的性能根本原因在于它们选择了不同的执行计划。其中一个选择了具有良好并行性的执行计划。另一个选择了具有较差并行性的执行计划。因为所有其他进程都在执行带有制表符的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中的“execute”命令很特殊,其缓存不会被实际最常访问的参数值清除。因此它总是慢的。根据我的实验结果,我认为“sp_prepexec”和“sp_executesql”与“execute”不同,它们的计划缓存可以通过实际最常访问的参数值进行清除,并且与JDBC具有类似的行为。
3) 为什么添加recompile提示会加速上述SQL的性能 在回答这个问题之前,请看一下MSDN在线帮助文档中的以下文字。
“指示SQL Server数据库引擎在查询执行后放弃为查询生成的计划,强制查询优化器在下次执行相同查询时重新编译查询计划。如果没有指定RECOMPILE,则数据库引擎将缓存查询计划并重用它们。在编译查询计划时,RECOMPILE查询提示使用查询中任何本地变量的当前值,如果查询在存储过程中,则使用传递给任何参数的当前值。
RECOMPILE是创建使用WITH RECOMPILE子句的存储过程的有用替代方法,当只需要重新编译存储过程中的一部分查询而不是整个存储过程时。有关详细信息,请参见重新编译存储过程。当创建计划指南时,RECOMPILE也非常有用。”
请注意以下句子: 在编译查询计划时,RECOMPILE查询提示使用查询中任何本地变量的当前值,如果查询在存储过程中,则使用传递给任何参数的当前值。
这意味着RECOMPILE查询提示改变了SSMS执行计划生成的行为。SSMS在没有RECOMPILE查询提示的情况下基于典型参数值生成执行计划,而在有RECOMPILE查询提示的情况下基于当前参数值生成执行计划。因此,重新编译提示使执行计划对于当前参数值(1234)是完美的。
总之,执行计划是由复杂因素选择的。我们必须仔细考虑。

1
查询对我来说看起来非常不同。您能检查一下您是否在问题中放置了正确的查询吗?如果您进行格式化,这也会有所帮助。 - Gordon Linoff
谢谢您的回复。我犯了一个打字错误。现在我已经更正了它们。现在这两个查询除了最后多出来的制表符之外是相同的。 - Pierre Yin
1个回答

0

首先,查询末尾的空格不会有任何影响。在任何数据库中,除了延长传输和解析语句的时间之外,空格都不可能导致性能问题。SQL引擎在编译阶段读取查询并生成执行计划。执行计划是要运行的内容。额外的空格在标记化查询字符串的第一步中就被丢弃了。据我所知,所有数据库引擎都是这样工作的。

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

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

无论如何,您的第一个查询语句都不是正确的语法,所以这可能与您看到的情况有关。选择语句是:

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中隐藏列的友好挖苦,因为MySQL允许这种语法)。


谢谢您的回复。我已经纠正了SQL字符串中的拼写错误。正如我在帖子中提到的那样,我通过在无限循环中调用代码片段长时间运行来得出上述观察结果。这种观察结果并不总是出现。但是我每天都能在某些时候看到它。我会继续尝试获取更多线索。 - Pierre Yin
另一个最有可能的原因是查询执行环境发生了变化,影响了查询优化。例如,其他查询可能正在使用会改变最佳性能路径的资源(临时空间、页面缓存)。运行查询时,请保存实际的执行计划。您可能会发现,在运行较慢时,连接操作被处理的方式不同。 - Gordon Linoff
事实上,我所有的观察都基于同一时间段内的JDBC调用。在SQL Server Management Studio中,相同的SQL似乎可以正常工作。通过wireshark,我发现JDBC将SQL作为远程过程调用执行,而SQL Server Management Studio将相同的SQL作为批处理SQL执行。我不知道这种差异是否与问题有关。如何在SQL Server Management Studio中模拟远程过程调用?我尝试了几种在管理工具中模拟RPC的方法,例如sp_excutesql、sp_prepexec。但是TDS数据包仍然表明SQL被执行为批处理SQL。 - Pierre Yin
我怀疑你的问题在于JDBC调用,而不是底层SQL。你是否测量过数据库内部的性能,排除通信开销,或尝试保存运行的执行计划?这两种方法都将根据服务器的当前配置编译SQL,因此执行计划可能会发生变化。 - Gordon Linoff
我在 SQL Server 主机中尝试了三种执行 SQL 的方法,结果已经附在我的帖子中。这些结果不包括通信开销。我将尝试通过 JDBC 获取执行计划以获得更多线索。谢谢。 - Pierre Yin
谢谢您的评论。虽然您没有直接回答我,但是您给了我一些提示。也许我已经得到了最终答案。我已经在我的帖子里附上了我的结论。再次感谢。 - Pierre Yin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接