SQL Server 2008日期时间索引性能错误


11

我们使用的是SQL Server 2008 R2,它有一个很大的表(100M +行),带有主ID索引,而一datetime列则具有非聚集索引。基于使用专门用于索引datetime列order by子句,我们看到了一些非常不寻常的客户端/服务器行为。

我通读了以下帖子:https : //stackoverflow.com/questions/1716798/sql-server-2008-ordering-by-datetime-is-too-slow, 但是客户端/服务器上发生的事情比实际发生的更多在这里开始描述。

如果我们运行以下查询(已编辑以保护某些内容):

select * 
from [big table] 
where serial_number = [some number] 
order by test_date desc

查询每次都会超时。在SQL Server Profiler中,执行的查询在服务器上看起来像这样:

exec sp_cursorprepexec @p1 output,@p2 output,NULL,N'select * .....

现在,如果将查询修改为:

declare @temp int;
select * from [big table] 
where serial_number = [some number] 
order by test_date desc

SQL Server Profiler在服务器上显示了已执行的查询,它立即起作用:

exec sp_prepexec @p1 output, NULL, N'declare @temp int;select * from .....

实际上,您甚至可以放置一个空注释('-;')而不是未使用的声明语句,并获得相同的结果。因此,最初,我们将sp预处理程序视为此问题的根本原因,但是如果您这样做,则:

select * 
from [big table] 
where serial_number = [some number] 
order by Cast(test_date as smalldatetime) desc

它也可以立即工作(您可以将其转换为其他任何datetime类型),并以毫秒为单位返回结果。探查器将对服务器的请求显示为:

exec sp_cursorprepexec @p1 output, @p2 output, NULL, N'select * from .....

因此,这在一定程度上排除sp_cursorprepexec了问题的全部原因。除此之外,sp_cursorprepexec当不使用“ order by”时也会调用,并且结果也立即返回。

我们已经在这个问题上进行了很多搜索,我看到其他人也发布了类似的问题,但是没有一个问题可以将其分解到这个水平。

那么其他人目睹了这种行为吗?有没有人比将无意义的SQL放在select语句前面来更改行为更好的解决方案?由于SQL Server应该在收集数据后调用该命令,因此看来这是服务器中的一个已存在很长时间的错误。我们发现此行为在我们的许多大型表中都是一致的,并且是可重现的。

编辑:

我还应该添加一个forceseek也使问题消失。

我应该添加帮助搜索者的ODBC超时错误引发为:[Microsoft] [ODBC SQL Server驱动程序]操作已取消

2012年10月12日添加:仍在寻找根本原因((已构建了要提供给Microsoft的示例,提交后,我将在此处交叉发布任何结果)。我一直在研究工作查询(带有添加的注释/声明语句)和非工作查询之间的ODBC跟踪文件。基本走线差异如下所示。在完成所有SQLBindCol讨论之后,它会在对SQLExtendedFetch的调用上发生。调用失败,返回代码为-1,然后父线程进入SQLCancel。由于我们可以使用Native Client和Legacy ODBC驱动程序来产生这种情况,因此我仍然指出服务器端存在一些兼容性问题。

(clip)
MSSQLODBCTester 1664-1718   EXIT  SQLBindCol  with return code 0 (SQL_SUCCESS)
        HSTMT               0x001EEA10
        UWORD                       16 
        SWORD                        1 <SQL_C_CHAR>
        PTR                0x03259030
        SQLLEN                    51
        SQLLEN *            0x0326B820 (0)

MSSQLODBCTester 1664-1718   ENTER SQLExtendedFetch 
        HSTMT               0x001EEA10
        UWORD                        1 <SQL_FETCH_NEXT>
        SQLLEN                     1
        SQLULEN *           0x032677C4
        UWORD *             0x032679B0

MSSQLODBCTester 1664-1fd0   ENTER SQLCancel 
        HSTMT               0x001EEA10

MSSQLODBCTester 1664-1718   EXIT  SQLExtendedFetch  with return code -1 (SQL_ERROR)
        HSTMT               0x001EEA10
        UWORD                        1 <SQL_FETCH_NEXT>
        SQLLEN                     1
        SQLULEN *           0x032677C4
        UWORD *             0x032679B0

        DIAG [S1008] [Microsoft][ODBC SQL Server Driver]Operation canceled (0) 

MSSQLODBCTester 1664-1fd0   EXIT  SQLCancel  with return code 0 (SQL_SUCCESS)
        HSTMT               0x001EEA10

MSSQLODBCTester 1664-1718   ENTER SQLErrorW 
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6

MSSQLODBCTester 1664-1718   EXIT  SQLErrorW  with return code 0 (SQL_SUCCESS)
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C [       5] "S1008"
        SDWORD *            0x08BFFF08 (0)
        WCHAR *             0x08BFF85C [      53] "[Microsoft][ODBC SQL Server Driver]Operation canceled"
        SWORD                      511 
        SWORD *             0x08BFFEE6 (53)

MSSQLODBCTester 1664-1718   ENTER SQLErrorW 
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6

MSSQLODBCTester 1664-1718   EXIT  SQLErrorW  with return code 100 (SQL_NO_DATA_FOUND)
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6
(clip)

添加了Microsoft Connect案例10/12/2012:

https://connect.microsoft.com/SQLServer/feedback/details/767196/order-by-datetime-in-odbc-fails-for-clean-sql-statements#details

我还应该注意,我们确实为功能查询和非功能查询查询了查询计划。它们都可以根据执行次数适当地重用。刷新缓存的计划并重新运行不会改变查询的成功。


如果您尝试会怎样select id, test_date from [big table] where serial_number = ..... order by test_date-我只是想知道这是否SELECT *会对您的表现产生负面影响。如果您有一个非聚集索引,test_date并且具有一个聚集索引id(假设这就是所谓的),那么该查询应该被该非聚集索引覆盖,因此应该很快返回
marc_s 2012年

对不起,很好。我应该包括我们尝试使用各种组合大量修改所选的列空间(删除'*'等)。通过这些更改,上述行为仍然存在。
DBtheDBA 2012年

我已经将我的帐户链接到该网站。如果主持人想将帖子移到该站点,则可以选择两种方法。我的一位开发人员在我发布此网站后向我指出了该网站。
DBtheDBA 2012年

这里使用哪个客户端堆栈?没有整个跟踪文本,这似乎是个问题。尝试将原始通话包装在里面sp_executesql,看看会发生什么。
乔恩·塞格尔

1
缓慢执行计划是什么样的?参数嗅探?
马丁·史密斯

Answers:


6

毫无疑问,您基本上会随机选择一个好(或)或(坏)的计划因为没有明确的选择可供使用。虽然对ORDER BY子句具有强制性,从而避免了排序,但datetime列上的非聚集索引对于此查询而言是非常糟糕的选择。可以使查询的索引更好的是on (serial_number, test_date)。更好的是,这将非常适合使用聚簇索引键。

根据经验,时间序列应按时间列进行聚类,因为绝大多数请求都对特定时间范围感兴趣。如果数据也固有​​地在选择性低的列上进行分区(例如您的serial_number似乎是这种情况),则应将此列添加为集群键定义中最左边的列。


我在这里有点困惑。为什么计划将基于the orderby子句?计划是否不应该限制where条件本身,因为排序仅应在获取行之后进行?服务器为什么要在设置整个结果集之前尝试对记录进行排序?
DBtheDBA 2012年

5
这也不能解释为什么在查询开始时添加注释会影响运行时间。
cfradenburg 2012年

另外,我们的表几乎总是按序列号而不是test_date查询。我们在两者上都具有非聚集索引,并且仅在表的id列上聚集。这是一个可操作的数据存储,在其他列上添加聚簇索引只会导致页面拆分,并且性能会降低。
DBtheDBA 2012年

1
@DBtheDBA:如果您想对“错误”提出索赔,则需要进行适当的调查和披露。有关表和导出的统计信息的确切架构,请遵循如何在SQL Server 2005和SQL Server 2008中如何生成必要数据库元数据的脚本以创建仅统计信息的数据库,尤其是所有重要的脚本统计信息脚本统计信息和直方图。将这些添加到帖子信息以及重现该问题的步骤。
雷木斯·鲁萨努

1
我们在搜索过程中之前已读过这篇文章,并且我理解您的意思,但是服务器在此处执行的操作存在根本缺陷。我们已经重建了表和索引,并将其复制到了新表中。recompile选项不能解决问题,这是一个大提示,说明出现了问题。我毫不怀疑将聚集索引放在所有内容上可能会解决此问题,但这不是根本原因的解决方案,这是一种解决方法,并且在大型表上代价很高。
DBtheDBA 2012年

0

记录有关如何重现该错误的详细信息,并将其提交到connect.microsoft.com。我检查了一下,看不到任何与此相关的信息。


明天,我将让我的DBA输入脚本来创建一个可重现的环境。我不认为这没那么难。如果有人有兴趣亲自尝试,我也会在这里发布。
DBtheDBA 2012年

打开连接项时也要发布它。这样,如果其他人遇到此问题,他们会指出正确的方向。而且任何关注此问题的人都可能希望对该项目投反对票,因此Microsoft更有可能对此予以关注。
cfradenburg 2012年

0

我的假设是您正在运行查询计划缓存。(雷木思可能说的和我说的一样,只是用不同的方式。)

这是有关SQL如何计划缓存的大量详细信息。

详细信息:有人针对特定的[some number]较早地运行了该查询。SQL查看提供的值,相关表/列的索引和统计信息等,并建立了一个对该特定[某些数字]有效的计划。然后,它缓存了该计划,运行了该计划,并将结果返回给调用方。

稍后,其他人针对不同的[some number]值运行相同的查询。此特定值导致结果行的数量大不相同,并且引擎应为此查询实例创建不同的计划。但这不是那样的。取而代之的是,SQL接受查询,并且(或多或少)对查询缓存进行区分大小写的搜索,以查找查询的现有版本。当它从较早的位置找到一个时,便使用该计划。

其想法是,它节省了决定计划和构建计划所需的时间。这个想法的漏洞在于,当使用值产生完全不同的结果运行同一查询时。他们应该有不同的计划,但没有。首先运行查询的人将有助于为随后运行查询的每个人设置行为。

一个简单的例子:在姓氏='SMITH'的[people]中选择* –在美国非常受欢迎的姓氏。

运行BONAPARTE的查询时,将重新使用为SMITH构建的计划。如果SMITH引起了表扫描(如果表中的行是99%SMITH,则可能很好),那么BONAPARTE也将获得表扫描。如果BONAPARTE在SMITH之前运行,则可能会构建和使用使用索引的计划,然后将其再次用于SMITH(对于表扫描可能会更好)。人们可能不会注意到SMITH的性能很差,因为他们期望性能很差,因为必须读取整个表并且不会直接注意到读取索引和跳到表。

关于您应该进行的任何更改,我怀疑SQL只是将其视为完全不同的查询并针对您的[some number]值建立新计划。

要对此进行测试,请对查询进行无意义的更改,例如在FOR和表名之间添加一些空格,或在末尾添加注释。快吗 如果是这样,那是因为该查询与缓存中的查询略有不同,所以SQL做了它对“新”查询的处理。

对于解决方案,我将看三件事。首先,请确保您的统计信息是最新的。当查询看起来奇怪或随机时,这实际上应该是您要做的第一件事。您的DBA应该这样做,但是事情确实发生了。确保最新统计信息的通常方法是重新索引表,这不一定是一件轻便的事情,但是也有一些选项可以更新统计信息。

要考虑的第二件事是按照Remus的建议添加索引。使用更好/不同的索引,一个值相对于另一个值可能会更稳定,并且变化不会那么大。

如果那没有帮助,那么第三件事是尝试使用RECOMPILE关键字在每次运行语句时强制执行新计划:

从[大表]中选择*,其中serial_number = [some number]按test_date desc的顺序排列OPTION(RECOMPILE)

这里有一篇文章描述了类似的情况。坦白说,我以前只看到RECOMPILE应用于存储过程,但是它似乎可以与“常规” SELECT语句一起使用。金伯利·特里普从未误导我。

您可能还会研究称为“ 计划指南 ” 的功能,但是它更复杂并且可能会过大。


为解决其中一些问题:1.统计信息已更新,正在更新。2.我们已经尝试了几种方式建立索引(覆盖索引等),但是这个问题似乎更依赖于order by针对日期时间索引的用法。3.只是使用RECOMPILE选项尝试了您的想法,但还是失败了,这让我有些惊讶,我希望它能奏效,尽管我不知道这是否是生产解决方案。
DBtheDBA 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.