索引中列的顺序有多重要?


173

我听说您应该在索引声明的开头放置最有选择性的列。例:

CREATE NONCLUSTERED INDEX MyINDX on Table1
(
   MostSelective,
   SecondMost,
   Least
)

首先,我说的是正确的吗?如果是这样,我是否可能会通过重新排列索引中列的顺序来看到性能上的巨大差异,还是更多的“不错的做法”?

我问的原因是因为在通过DTA进行查询之后,建议我创建一个索引,其中几乎所有列都与现有索引相同,只是顺序不同。我正在考虑只是将缺少的列添加到现有索引中,并称之为良好。有什么想法吗?

Answers:


193

看一下这样的索引:

Cols
  1   2   3
-------------
|   | 1 |   |
| A |---|   |
|   | 2 |   |
|---|---|   |
|   |   |   |
|   | 1 | 9 |
| B |   |   |
|   |---|   |
|   | 2 |   |
|   |---|   |
|   | 3 |   |
|---|---|   |

看到第一列比第一列限制更多的结果,如何限制第一列?如果想象一下如何遍历索引,第1列然后是第2列,这样会更容易...您会看到,第一遍中大部分结果的丢失使第二步快得多。

另一种情况,如果您查询第3列,则优化器甚至不会使用索引,因为它对缩小结果集完全没有帮助。 随时在查询中,在进行下一步之前,缩小要处理的结果数的范围即可提高性能。

由于索引也是以这种方式存储的,因此在查询索引时,不会在索引上回溯以找到第一列。

简而言之:不,这不是为了展示,而是有真正的性能优势。


13
在上图中,请记住,只有在查询中指定了列1时,该索引才有用。如果您的查询仅在联接或搜索谓词中指定列2,则将无济于事。因此,订购也很重要。也许不用说,但是想提一下。
CodeCowboyOrg 2015年

3
还要记住,假设您的Index就像上面的图片,并且您对column1和column2进行了查询过滤,但是column2更独特,而您真正想要过滤的实际上是column2,那么仅在其中有一个索引会更有益第2列是第一列。这看起来似乎违反直觉,但请记住,索引存储在几页上,并且是一棵包含一定范围值的树,而上面的第1列否定了1/2的可能性,索引已经知道要直接访问哪个索引页列2的值,不需要列1来缩小设置范围。
CodeCowboyOrg 2015年

4
该图片不能准确表示索引的结构或导航方式。提交答案纠正这种stackoverflow.com/a/39080819/73226
马丁·史密斯

6
@MartinSmith我不同意它是不正确的。诚然,它非常简化,这是我的意图。不过,对于希望深入了解这些级别的人,您的答案将更深入地讲解。如果您查看树的图像,您将以非常简单的方式看到我正在说明的内容。这不是唯一的,甚至不是特定于SQL的。B树索引在许多事物中非常普遍。
尼克·克拉弗

@MartinSmith我也不同意它是不准确的,您所描述的是如何实现覆盖索引的标准行为-一旦执行范围查询,选择性就变得更加重要,因为这将使优化器的索引页数最小化必须扫描;这在具有数百万行的大型表中可能非常重要
Paul Hatcher

127

列的顺序至关重要。现在哪个顺序正确,取决于您要如何查询它。索引可用于执行精确搜索或范围扫描。精确查找是指为索引中所有列的值都指定并且查询恰好位于该行所关注的情况。对于查找,列的顺序无关紧要。范围扫描是仅指定一些列的情况,在这种情况下,顺序变得很重要。只有指定了最左列,然后指定了下一个最左列,SQL Server才可以将索引用于范围扫描。如果您在(A,B,C)上有一个索引,则可以使用该索引对for 或A=@afor 进行范围扫描,A=@a AND B=@b不能进行。情况是混合的,例如B=@bC=@cB=@b AND C=@cA=@a AND C=@cA=@a部分将使用索引,但C=@c不使用索引(查询将扫描所有B值的A=@a,不会“跳到” C=@c)。其他数据库系统具有所谓的“跳过扫描”运算符,当未指定外部列时,该运算符可以利用索引中内部列的某些优势。

掌握了这些知识之后,您可以再次查看索引定义。(MostSelective, SecondMost, Least)仅当MostSelective指定了column 时,索引on 才有效。但是,最有选择性的是,内部列的相关性将迅速降低。很多时候,您会发现(MostSelective) include (SecondMost, Least)或上有一个更好的索引(MostSelective, SecondMost) include (Least)。由于内部列的相关性较低,因此将低选择性列放置在索引中的此类正确位置上,只会使它们产生寻找噪音,因此将它们移出中间页并仅将它们保留在叶子页上是有意义的,因为查询覆盖率的目的。换句话说,将它们移动到INCLUDE。随着Least列大小的增加,这一点变得更加重要。想法是该索引只能使指定以下内容的查询受益MostSelective 无论是精确值还是范围,该列的选择性最高,已经在很大程度上限制了候选行。

另一方面,上的索引(Least, SecondMost, MostSelective)看似错误,但实际上它是一个功能强大的索引。因为它具有Least列作为其最外面的查询,所以它可用于必须在低选择性列上聚合结果的查询。这样的查询在OLAP和分析数据仓库中很普遍,这正是此类索引非常适合它们的地方。这样的索引实际上是出色的聚集索引,恰好是因为它们将物理布局组织在大块相关行上(相同的Least值,通常表示某种类别或类型),并且有助于分析查询。

因此,不幸的是,没有“正确”的命令。您不应该遵循任何曲奇工具的配方,而应针对这些表分析要使用的查询模式,并确定哪个索引列顺序正确。


3
像往常一样令人敬畏的Remus。我将再读几遍您的第三段并进行跟进。我怀疑那可能正是我需要做的。
安倍·米斯勒

“只有指定了最左边的列,然后才指定了最左边的下一个列,SQL Server才能将索引用于范围扫描。” 这正是我的理解所缺少的,谢谢!我不知道只能在最右边使用的索引列上进行范围扫描,但是现在我这样做非常有意义。
Allon Guralnek

此说明适用于Oracle DB吗?
另一个

1
@Roizpi是的,基本上任何具有索引的关系数据库都以相同或非常相似的方式工作。
Tatranskymedved

45

正如Remus所说,这取决于您的工作量。

我想解决已接受答案的一个误导性方面。

对于在索引中的所有列上执行相等搜索的查询,没有显着差异。

下面创建了两个表,并用相同的数据填充它们。唯一的区别是,一个键的选择顺序从最高到最小,而另一个则相反。

CREATE TABLE Table1(MostSelective char(800), SecondMost TINYINT, Least  CHAR(1), Filler CHAR(4000) null);
CREATE TABLE Table2(MostSelective char(800), SecondMost TINYINT, Least  CHAR(1), Filler CHAR(4000) null);

CREATE NONCLUSTERED INDEX MyINDX on Table1(MostSelective,SecondMost,Least);
CREATE NONCLUSTERED INDEX MyINDX2 on Table2(Least,SecondMost,MostSelective);

INSERT INTO Table1 (MostSelective, SecondMost, Least)
output inserted.* into Table2
SELECT TOP 26 REPLICATE(CHAR(number + 65),800), number/5, '~'
FROM master..spt_values
WHERE type = 'P' AND number >= 0
ORDER BY number;

现在对两个表进行查询...

SELECT *
FROM   Table1
WHERE  MostSelective = REPLICATE('P', 800)
       AND SecondMost = 3
       AND Least = '~';

SELECT *
FROM   Table2
WHERE  MostSelective = REPLICATE('P', 800)
       AND SecondMost = 3
       AND Least = '~'; 

...两者都使用索引罚款,并且两者的费用完全相同。

在此处输入图片说明

实际上,公认的答案中的ASCII技术不是索引的结构方式。表1的索引页如下所示(单击图像以完整尺寸打开)。

在此处输入图片说明

索引页面包含包含整个键的行(在这种情况下,实际上为行标识符附加了一个附加的键列,因为该索引并未声明为唯一,但是可以忽略此处的更多信息)。

对于上面的查询,SQL Server不在乎列的选择性。它对根页面进行二进制搜索,发现Key (PPP...,3,~ )>=(JJJ...,1,~ )< (SSS...,3,~ )因此应该读取page 1:118。然后,它对该页面上的键条目进行二进制搜索,并找到要向下浏览的叶页面。

按选择顺序更改索引不会影响二进制搜索的预期键比较数,也不会影响进行索引搜索时需要导航的页面数。它充其量可能会略微加快键比较本身。

有时,首先对最有选择性的索引进行排序对于您工作负载中的其他查询是有意义的。

例如,如果工作负载包含以下两种形式的查询。

SELECT * ... WHERE  MostSelective = 'P'

SELECT * ...WHERE Least = '~'

上面的索引没有涵盖其中任何一个。MostSelective具有足够的选择性,可以制定值得进行查找和查找的计划,但针对的查询Least则不然。

但是,这种情况(在复合索引的前导列的子集上不包含索引搜索)只是索引可以帮助的一类可能的查询。如果您从不真正地单独搜索MostSelectiveMostSelective, SecondMost通过全部三列的组合进行搜索,那么这种理论上的优势对您毫无用处。

相反的查询如

SELECT MostSelective,
       SecondMost,
       Least
FROM   Table2
WHERE  Least = '~'
ORDER  BY SecondMost,
          MostSelective 

可以通过具有通常规定的顺序的相反顺序来获得帮助-因为它涵盖了查询,因此可以支持查找并以所需的顺序返回行来引导。

因此,这是一个经常重复的建议,但最多不过是对其他查询的潜在好处的一种试探法-它不能替代实际查看您的工作量。


31

您应该在索引声明的开头放置最有选择性的列。

正确。索引可以是复合的-由多列组成-由于最左边的原则,顺序很重要。原因是数据库从左到右检查列表,并且必须找到与定义的顺序匹配的对应列引用。例如,在具有列的地址表上建立索引:

  • 地址

使用该address列的任何查询都可以使用索引,但是如果查询中只有一个city和/或state引用,则不能使用该索引。这是因为未引用最左边的列。查询性能应该告诉您哪个是最佳的-单个索引或具有不同顺序的多个组合。阅读:金伯利·特里普(Kimberley Tripp)的《引爆点》


如果只是未使用最右边的列怎么办?因此,查询使用地址和城市,但未使用州。会使用索引吗?
安倍·米斯勒

@Abe:不会使用最右边的-您必须满足从左边开始的索引顺序。小姐,不能使用它。
OMG小马

4
@Abe:如果查询的是地址和城市,但未注明-那么,将使用索引。换句话说,数据库能够使用部分索引来满足请求,只要它能够从索引的左侧开始并在使用要查询的字段时向右移动即可。但是,如果您使用“地址和州”而不是“城市”查询,它可能仍使用索引,但效率不高-因为现在它只能使用索引的“地址”部分(b / c接下来是城市,查询中未使用它)。
JaredC

6

所有其他答案都是错误的。

选择订单时,复合索引各个列的选择性无关紧要。

这是一个简单的思考过程: 有效地,索引是所涉及列的串联。

考虑到这一原理,唯一的区别是比较字符串中前后不同的两个“字符串”。这只是总成本的一小部分。如一个答案中所述,没有“第一遍/第二遍”。

那么,应该使用什么顺序?

  1. 开始与测试柱(一个或多个)=,在任何顺序。
  2. 然后在一个范围列上固定。

例如,非常低的选择性列必须首先在此列:

WHERE deleted = 0  AND  the_datetime > NOW() - INTERVAL 7 DAY
INDEX(deleted, the_datetime)

在索引中交换顺序将使其完全忽略deleted

(还有很多用于排序列的规则。)


否决票是因为我错了吗?还是因为我有强烈的意见?或者是其他东西?
里克·詹姆斯

不是我的不赞成票,但对我来说,删除= 0听起来像是选择性不低吗?我想这将是表中大多数行。
格雷格

@Greg-我认为这意味着“低选择性”-也就是说,使用deleted并不能帮助您过滤掉不需要的行。你有更好的例子吗?(那是我写《答案》时突然想到的那个。)
里克·詹姆斯

我误会了。
格雷格

1
@ClickOk-谢谢。我的食谱提供了一些基本信息: mysql.rjweb.org/doc.php/index_cookbook_mysql
Rick James
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.