3

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

我的 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 个查询的性能有时非常不同。

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

但是ps.excute()同一代码段中的 SQL_2 大约需要 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 的性能吗?

我认为这不是由 SQL Server 2008 R2 中的空间降低性能中所述的自动参数化缓存引起的

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

从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)

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

4

1 回答 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 中的隐藏列,这将允许这种语法)。

于 2012-08-18T18:18:31.367 回答