SELECT子句列导致索引未被使用

时间:2017-05-30 09:09:41

标签: sql-server tsql

我今天试着为一张桌子编制索引并注意到某些东西尴尬并且无法理解原因(即使它看起来如此简单?)。我有一张这样的桌子:

CREATE TABLE [X].[A](
    [TableName] [varchar](250) NOT NULL,
    [Header] [varchar](4000) NOT NULL,
    [Trailer] [varchar](4000) NOT NULL,
    [FileName] [varchar](50) NOT NULL,
    [StartDate] [date] NOT NULL,
    [EndDate] [date] NOT NULL
) ON [PRIMARY]

我们假设我有这样的查询(我简化了一点):

SELECT 
    Header
FROM
    X.A ht (NOLOCK)
WHERE
    ht.TableName = 'asd' AND
    '2017-05-29' BETWEEN ht.StartDate AND ht.EndDate

我想将一个索引放到(TableName, StartDate, EndDate),因为它们位于where子句中。我的索引如下:

CREATE NONCLUSTERED INDEX [NonClusteredIndex-20170530-113847] ON [X].[A]
(
    [TableName] ASC,
    [StartDate] ASC,
    [EndDate] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO

但是我注意到我的查询没有使用索引并且它执行Table Scan,但是当我更改SELECT子句时,如下所示:

SELECT 
    TableName
FROM
    X.A ht (NOLOCK)
WHERE
    ht.TableName = 'asd' AND
    '2017-05-29' BETWEEN ht.StartDate AND ht.EndDate

它确实使用了我的索引并且Index Seek这就是我想要的。发生了什么事?

2 个答案:

答案 0 :(得分:1)

在一个非常简单的例子中,如果你的表有这样的数据:

ID | TableName | Header | Trailer | FileName | StartDate  | EndDate
---+-----------+--------+---------+----------+------------+------------
 1 |    asd    |   H1   |    T1   |    F1    | 2017-05-28 | 2017-05-30
 2 |    asd    |   H2   |    T2   |    F2    | 2017-05-28 | 2017-05-30
 3 |    asd    |   H3   |    T3   |    F3    | 2017-05-28 | 2017-05-30
 4 |    asd    |   H4   |    T4   |    F4    | 2017-05-28 | 2017-05-30
 5 |    asd    |   H5   |    T5   |    F5    | 2017-05-28 | 2017-05-30
 6 |    asd    |   H6   |    T6   |    F6    | 2017-05-28 | 2017-05-30
 7 |    asd    |   H7   |    T7   |    F7    | 2017-05-28 | 2017-05-30
 8 |    xyz    |   H1   |    T1   |    F1    | 2017-05-28 | 2017-05-30

然后您的索引看起来像这样

TableName | StartDate  | EndDate    | ID | 
----------+------------+------------+----+-
   asd    | 2017-05-28 | 2017-05-30 |  1 | 
   asd    | 2017-05-28 | 2017-05-30 |  2 | 
   asd    | 2017-05-28 | 2017-05-30 |  3 | 
   asd    | 2017-05-28 | 2017-05-30 |  4 | 
   asd    | 2017-05-28 | 2017-05-30 |  5 | 
   asd    | 2017-05-28 | 2017-05-30 |  6 | 
   asd    | 2017-05-28 | 2017-05-30 |  7 | 
   xyz    | 2017-05-28 | 2017-05-30 |  8 | 

在这种情况下,您可以返回此查询所需的所有列:

SELECT 
    TableName
FROM
    X.A ht (NOLOCK)
WHERE
    ht.TableName = 'asd' AND
    '2017-05-29' BETWEEN ht.StartDate AND ht.EndDate

但是,如果你想选择Header,那么你不能只使用索引(因为Header)没有存储在索引中,对于你必须找到聚类的每一行key(在此简单示例中为ID),然后查找主表中的相应行以获取Header的值。这个(索引搜索+键查找)是一个昂贵的操作,并且由于你必须为9行执行此操作,并且原始表只包含10个,因此首先简单搜索主表并且不使用它可能更有效索引(Clustered Index Scan)。

如果您一直在查找非常少量的记录,例如xyd,那么很可能会选择索引搜索和键查找。我不认为在这两种方法之间切换需要多少记录是简单的切断,但in an answer to another question我在一张相当小的桌子上找到了3.7%的记录的截止值。我预计随着桌子尺寸的增加,这个百分比会减少。

答案 1 :(得分:0)

您的索引涵盖您要过滤的列,但不包括您要返回的列。这意味着查询仍然必须扫描表格以获取与您的过滤相匹配的行的RowID,以便返回Header值。

如果您在涵盖include列的索引中添加Header,那么您应该获得所需的搜索:

create nonclustered index [NonClusteredIndex-20170530-113847]
on [X].[A]([TableName] asc,[StartDate] asc,[EndDate] asc)
include([Header])
with (pad_index = off,statistics_norecompute = off,sort_in_tempdb = off,drop_existing = off,online = off,allow_row_locks = on,allow_page_locks = on)