【问题标题】:Last 4 Whitespaces of Sql Would Hurt Sql Server Performance?Sql 的最后 4 个空格会损害 Sql Server 性能吗?
【发布时间】:2012-08-14 17:11:52
【问题描述】:

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

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

我发现有时在代码 sn-p 中这 2 个查询的性能非常不同。

ps.excute() 的 SQL_1 在这段代码 sn -p 只花费大约 10 秒。我看到 SQL_1 运行时 CPU 使用率很高。服务器的24个CPU都用完了。

但是ps.excute() 的 SQL_2 在相同的代码 sn-p 花费大约 150 秒。 SQL_2 运行时 CPU 使用率很慢。 24 个 CPU 中只有 2 个被使用。

SQL_1 和 SQL_2 同时运行。

但上述观察并非总是如此。有时ps.execute() SQL_1 和 SQL_2 的性能是相同的。有时ps.execute() SQL_1 和 SQL_2 的性能和我上面描述的一样。

这就是我发现的。这很令人困惑。 SQL 的最后一个空格会影响 SQL Server 的性能吗?

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

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

从 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 在 management studio 中只需要大约 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 world 没有打开,我们可能永远无法得到SQL Server 内部的细节。在这里,我只是解释一下我的发现。一些解释只是我的猜测。我希望它对其他人有帮助。

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

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

性能不同的根本原因是他们选择了不同的执行计划。一个选择了具有良好parellesim的执行计划。另一个选择了parellesim不好的执行计划。因为所有其他进程都在执行带制表符的 SQL,所以不带制表符的 SQL 被视为新 SQL。因此在执行不带制表符的 SQL 时,它会根据记录的统计信息,根据典型参数值生成一个新的执行计划。也许典型值在第一次表现不佳。但是实际访问的参数值直方图可以刷新执行计划缓存。没有制表符的 SQL 仅在我的测试中使用。我的测试只使用参数值(1234)。 SQL server 认为 SQL 经常访问 (1234) 并用实际最常访问的参数值 (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' 与 'execute' 不同,它们的计划缓存可以通过实际最常见的访问参数值刷新,并且与 JDBC 具有相似的行为。

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

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

RECOMPILE 是创建使用 WITH RECOMPILE 子句的存储过程的有用替代方法,当必须重新编译存储过程中的查询子集而不是整个存储过程时。有关详细信息,请参阅重新编译存储过程。在您创建计划指南时,重新编译也很有用。”

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

这意味着 RECOMPILE 查询提示改变了 SSMS 执行计划生成行为。 SSMS 基于典型参数值生成执行计划,而没有 RECOMPILE 查询提示,而 SSMS 则基于当前参数值生成执行计划,并使用 RECOMPILE 查询提示。所以重新编译提示使执行计划非常适合当前参数值(1234)

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

【问题讨论】:

  • 查询对我来说看起来很不一样。你能检查你是否在问题中输入了正确的查询?如果您格式化它们也会有所帮助。
  • 感谢您的回复。我犯了一个拼写错误。现在我纠正他们。现在除了最后一个额外的制表符之外,这 2 个查询是相同的。

标签: performance sql-server-2008-r2


【解决方案1】:

首先,查询末尾的空格不会产生影响。除了延长传输和解析语句的时间之外,空白可能会在任何数据库中导致性能问题,这是不可想象的。 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 中的隐藏列,这将允许这种语法)。

【讨论】:

  • 感谢您的回复。我更正了 SQL 字符串的错字。正如我在帖子中提到的,我通过在无限循环中长时间调用代码 sn-p 得到了上述观察结果。这种观察并不总是可见。但我可以在每天的某个时间看到它。我会努力争取更多线索。
  • 其他最可能的原因是运行查询的环境发生变化而影响查询优化。例如,其他查询可能正在使用改变最佳性能路径的资源(临时空间、页面缓存)。运行查询时,请保存实际的执行计划。您可能会发现连接运行速度较慢时处理方式不同。
  • 其实我所有的观察都是基于同一时间段的JDBC调用。在 SQL Server Management Studio 上,相同的 SQL 似乎工作正常。从wireshark,我发现JDBC将SQL作为远程过程调用执行,而SQL Server Management Studio将相同的SQL作为批处理sql执行。我不知道差异是否与问题有关。如何在 SQL Server Management Studio 中模拟远程过程调用?我尝试了几种在management studio中模拟RPC的方法,比如sp_excutesql、sp_prepexec。但是TDS报文还是表明该SQL是作为Batch SQL执行的。
  • 我怀疑您的问题出在 JDBC 调用上,而不是底层 SQL。您是否测量了数据库中的性能(不包括通信开销),或者尝试保存运行的执行计划?两种方法都将根据服务器的当前配置编译 SQL,因此执行计划可能会发生变化。
  • 我在sql server主机的sql server management studio中尝试了3种方式执行sql。结果附在我的帖子中。这些结果不包括通信开销。我将尝试通过 JDBC 获取执行计划以获取更多线索。谢谢。
猜你喜欢
  • 2011-10-13
  • 1970-01-01
  • 2010-12-17
  • 1970-01-01
  • 1970-01-01
  • 2011-01-10
  • 1970-01-01
  • 2021-12-14
  • 2013-11-26
相关资源
最近更新 更多