我遇到了一个奇怪的问题。也许这里有人可以帮助我。
我的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)
总之,执行计划是由复杂因素选择的。我们必须仔细考虑。
答案 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中的隐藏列,这将允许这种语法。