【问题标题】:SELECT query poor execution plan with "WHERE A=1 OR A=2 clause"SELECT 使用“WHERE A=1 OR A=2 子句”查询糟糕的执行计划
【发布时间】:2012-07-17 09:35:21
【问题描述】:

我们目前有一个LOGS TABLE,由多个日志编号(XLOG 列)划分,并在有限的时间范围内访问。

该表使用集群“自然”主键声明,其中XLOG 是日志标识符,XDATE 是时间戳,XHW and XCELL 是硬件标识符,确保日志事件的唯一性:

 CREATE TABLE [dbo].[LOGS](
    [XDATE] [datetime] NOT NULL,
    [XHW] [nvarchar](3) NOT NULL,
    [XCELL] [nvarchar](3) NOT NULL,
    [XALIAS] [nvarchar](255) NULL,
    [XMESSAGE] [nvarchar](255) NULL,
    [XLOG] [int] NOT NULL,
 CONSTRAINT [PK_LOG] PRIMARY KEY CLUSTERED ([XLOG] ASC,[XDATE] ASC,[XHW] ASC,[XCELL] ASC)

问题是使用相同查询访问多个日志时会出现可怕的执行计划(例如,下面的示例查询中的XLOG = 1 OR XLOG = 1002),请求#1:

SELECT TOP 100 XDATE, XHW, XCELL, XMESSAGE, XLOG FROM LOGS
WHERE XDATE > '2012-06-12T00:00:00' AND XDATE < '2012-07-13T08:29:03.250'
AND (XLOG = 1 OR XLOG = 1002)
ORDER BY XDATE DESC, XLOG DESC

编辑:所需的 100 行不仅来自日志 #1,而且来自两个日志,混合,日期排序。这就是两个查询的返回值。

统计数据在测试前更新。

实际的执行计划基本上使用聚集索引查找来获取百万行数据,其中谓词为XLOGXDATE,(由于我们有 XLOG= 并且我们 TOP 顺序,它可能只获取第一/最后 100 行通过 XDATE)

聚集索引查找操作详情:

预期的执行计划是

我试图重写查询,但除了UNION ALL 之外找不到其他方法。结果查询返回相同的结果(具有正确的计划!),但感觉过于复杂(并且不能通过 XLOG 上的 JOIN 进行调整,但这不是问题)请求 #2:

WITH A AS (SELECT TOP 100 XDATE,  XHW, XCELL, XMESSAGE, XLOG FROM LOGS
WHERE XDATE > '2012-06-12T00:00:00' AND XDATE < '2012-07-13T08:29:03.250'
AND XLOG = 1
ORDER BY XDATE DESC),

B AS (SELECT TOP 100 XDATE,  XHW, XCELL, XMESSAGE, XLOG FROM LOGS
WHERE XDATE > '2012-06-12T00:00:00' AND XDATE < '2012-07-13T08:29:03.250'
AND XLOG = 1002
ORDER BY XDATE DESC)

SELECT TOP 100 * FROM (
    SELECT * FROM A
    UNION ALL 
    SELECT * FROM B 
) A
ORDER BY XDATE DESC, XLOG DESC

问题:请求 #1 有什么问题?在尝试对数百万行进行排序之前,如何重写/修改它以考虑“TOP”?是否需要另一个索引、提示或一些额外的统计信息来解决问题?我有义务重写请求 #2 之类的查询吗?

编辑:从数量上看,这张表包含十几个日志,有些每月只有一个事件,而其他每月有数百万个事件。

这种查询最常用于此表(还有其他带有额外过滤器的变体,但它们与此问题无关——除了使用请求 #2 时的复杂性)。

编辑 #2: 我尝试了将聚集索引更改为 (XDATE,XLOG,...) 而不是 (XLOG,XDATE,...) 的解决方案 -- nb: This Composite主键是这样设计的,因为 XLOG 列的选择性低。

我在生产数据库的副本上测试了这个查询,对照只有一千行的日志:查询计划生成了很多 I/O(它只从 XLOG=12 中过滤掉几行范围广泛的XDATEs)。所以这个特殊的解决方案是不行的。

SELECT TOP 100 XDATE, XHW, XCELL, XMESSAGE, XLOG FROM LOGS
WHERE XDATE > '2012-06-12T00:00:00' AND XDATE < '2012-07-13T08:29:03.250'
AND (XLOG = 12 AND XALIAS LIKE 'KEYWORD%' )
ORDER BY XDATE DESC, XLOG DESC, XHW DESC, XCELL DESC

PS:顺便说一句,我们与 PostgreSQL 9.1 有相同的行为 - 所以它与数据库无关,更可能是错误的查询或错误的表设计。

【问题讨论】:

  • 这张表上有哪些索引?
  • 除了主键没有其他索引
  • 索引是一个选项。这不是我为解决这个问题而探索的解决方案。

标签: sql-server sql-server-2008 sql-server-2005 postgresql sql-execution-plan


【解决方案1】:

问题是数据库不知道您想要的前 100 行都是 XLOG=1,因此必须获取所有可能的 XLOG,然后排序以找到前 100。

在第二种情况下,您提供了更多信息或减少了所选行,以便优化器可以只使用索引进行排序。

另一种方法是在 XDATE DESC、XLOG DESC 上创建聚集索引,然后优化器将知道它不必排序并使主键成为哈希或其他索引。如果此查询是最常用的查询,则此查询才有意义。

【讨论】:

  • "数据库不知道你要的前100行都是XLOG=1":不,不!事实上,它们绝对不是仅来自一本航海日志。在实践中,条目是混合的。
  • 确实,在 XDATE 上创建聚集索引,然后 XLOG 才有意义。这可能会将查询计划变成聚集索引 SCAN。
  • 然而,当其他日志有很多最近的事件时,我担心从一个特定的古老日志中检索数据的行数很少。
【解决方案2】:

按 XDate 排序导致问题。数据必须按 xdate 排序才能获得前 100 名,这就是您有这种排序的原因。最好的方法是在 xdate、xlog 上建立一个索引。但这会增加开销。当其他事情不起作用时,这应该是选项。试试下面的方法。

SELECT TOP 100 XDATE, XHW, XCELL, XMESSAGE, XLOG
into #mytop100
FROM LOGS 
WHERE XDATE > '2012-06-12T00:00:00' AND XDATE < '2012-07-13T08:29:03.250' 
AND (XLOG = 1) 
ORDER BY XDATE DESC
union all
SELECT TOP 100 XDATE, XHW, XCELL, XMESSAGE, XLOG FROM LOGS 
WHERE XDATE > '2012-06-12T00:00:00' AND XDATE < '2012-07-13T08:29:03.250' 
AND (XLOG = 1002) 
ORDER BY XDATE DESC

select TOP 100 XDATE, XHW, XCELL, XMESSAGE, XLOG from #mytop 100 ORDER BY XDATE DESC, XLOG DESC 

还尝试将主 sql 放入 #mytop100 中,看看它是否选择了一个好的计划。我敢打赌它会,但仍然会检查。

【讨论】:

  • 好的,但这相当于我的查询#2 ...提供了足够的信息,它可以正常工作
  • 与临时表有点不同,因为外部顶部转换的机会不存在,因此计划应该足够好。那么哪种方法适合你?
  • CTA(查询 #2)工作正常,您的临时表解决方案(纠正了一些错字)也工作正常。但是现在想象有 10 个日志要查询,大约有 10 个额外的过滤器(WHERE foo LIKE 'bar%')。这看起来不像来自地狱的噩梦动态 sql 查询吗?
  • 然后在 xdate 上创建一个 nonclusutered 索引。如果您没有任何其他类型的查询或大多数查询属于这种类型并且执行了很多时间。考虑将 xdate 作为第一个键和xlog 作为您的聚集索引键的 key2
猜你喜欢
  • 2021-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多