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

Abe*_*ler 155 sql t-sql sql-server indexing sql-server-2005

我听说你应该在索引声明的开头放置最具选择性的列.例:

CREATE NONCLUSTERED INDEX MyINDX on Table1
(
   MostSelective,
   SecondMost,
   Least
)
Run Code Online (Sandbox Code Playgroud)

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

我问的原因是因为在通过DTA进行查询后,它建议我创建一个索引,其中几乎所有列都与现有索引相同,只是顺序不同.我正在考虑将缺少的列添加到现有索引并调用它.思考?

Nic*_*ver 181

看一下像这样的索引:

Cols
  1   2   3
-------------
|   | 1 |   |
| A |---|   |
|   | 2 |   |
|---|---|   |
|   |   |   |
|   | 1 | 9 |
| B |   |   |
|   |---|   |
|   | 2 |   |
|   |---|   |
|   | 3 |   |
|---|---|   |
Run Code Online (Sandbox Code Playgroud)

首先看一下如何限制A,因为您的第一列消除了比首先限制第二列更多的结果?如果您想象索引必须如何遍历,第1列,然后是第2列等,那么就会更容易...您会看到在第一次通过中砍掉大部分结果会使第2步更快.

另一种情况,如果您在第3列上查询,优化器甚至不会使用索引,因为它在缩小结果集方面没有任何帮助. 无论何时进行查询,在下一步骤之前缩小要处理的结果数量意味着更好的性能.

由于索引也以这种方式存储,因此当您查询索引时,索引中没有回溯以查找第一列.

简而言之:不,它不是为了表演,有真正的性能优势.

  • 在上图中,请记住,只有在查询中指定了列1时,该索引才有用.如果您的查询仅在Join或Search Predicate中指定了第2列,那么它将不会有益.所以订购也很重要.也许这不用说,但想提一下. (10认同)
  • @MartinSmith我不同意这是不准确的.这是非常*非常简化的,这是我的意图.对于那些想深入研究水平的人来说,你的答案深入探讨了关于水平的更多细节.如果你看一下你的树形图像,你会看到我用*非常简单的方式说明的内容.这不是非常独特甚至是SQL特定的; B树索引在很多方面都很常见. (4认同)
  • 拥有多个按不同顺序排序的索引是否有益?例如 A、B、C 和 B、A、C 来帮助实现不同的分组可能性? (3认同)
  • 还要记住,假设您的Index就像上面的图片一样,并且您对column1和column2进行了查询过滤,但是column2更独特,而您真正想要过滤的实际上是column2,那么仅在其中有一个索引会更有益第2列是第一位。这看起来似乎违反直觉,但请记住,索引存储在几页上,并且是一棵包含一定范围值的树,而上面的第1列否定了1/2的可能性,索引已经知道要直接进入哪个索引页列2的值,不需要列1来缩小设置范围。 (2认同)
  • 此图片不能准确表示索引的结构或导航方式.已经提交了一个解决此问题的答案http://stackoverflow.com/a/39080819/73226 (2认同)
  • 没有“下一步”。这样想:所有列都连接在一起,然后针对具有组合列的索引使用组合搜索。 (2认同)

Rem*_*anu 118

列的顺序至关重要.现在哪个订单是正确的,这取决于您将如何查询它.索引可用于执行精确搜索或范围扫描.精确搜索是指定索引中所有列的值并且查询准确归位于行感兴趣的时间.对于搜索,列的顺序无关紧要.范围扫描是指定只有一些列,在这种情况下,当订单变得重要时.仅当指定了最左侧的列时,SQL Server才能使用索引进行范围扫描,然后仅在指定了下一个最左侧的列时才使用索引,依此类推.如果您对(A,B,C)的索引可以用来范围扫描A=@a,为A=@a AND B=@bB=@b,对于C=@c也不B=@b AND C=@c.案例A=@a AND C=@c是混合的,因为在A=@a部分将使用索引,但C=@c不是(查询将扫描所有B值A=@a,不会'跳过' C=@c).其他数据库系统具有所谓的"跳过扫描"运算符,当未指定外部列时,该运算符可以利用索引中的内部列.

掌握了这些知识,您可以再次查看索引定义.索引(MostSelective, SecondMost, Least)将仅在MostSelective指定列时有效.但这是最具选择性的,内部列的相关性将迅速降低.通常,您会发现更好的索引正在开启(MostSelective) include (SecondMost, Least)或开启(MostSelective, SecondMost) include (Least).因为内部列不太相关,所以在索引中的这些正确位置放置低选择性列使得它们只是搜索的噪声,因此将它们移出中间页面并将它们仅保留在叶页上是有意义的,因为查询可覆盖性目的.换句话说,将它们移动到INCLUDE.随着Least列的大小增加,这变得更加重要.我们的想法是,这个索引只能使指定MostSelective为精确值或范围的查询受益,并且该列是最具选择性的,它已经在很大程度上限制了候选行.

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

所以,不幸的是,没有'正确'的顺序.您不应该遵循任何cookie切割器配方,而是分析您将要对这些表使用的查询模式,并确定哪个索引列顺序正确.

  • 像雷弗斯一样令人敬畏的回应.我将再读几遍你的第三段并跟进.我怀疑这可能正是我需要做的. (3认同)
  • @Sam 表达式“A=@a AND B=@b”与“B=@b AND A=@a”在执行过程中基本上无法区分,因此差异并不重要。 (3认同)

Mar*_*ith 43

正如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;
Run Code Online (Sandbox Code Playgroud)

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

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 = '~'; 
Run Code Online (Sandbox Code Playgroud)

...他们都使用索引罚款,两者都给出完全相同的成本.

在此输入图像描述

接受的答案中的ASCII艺术实际上并不是索引的结构.Table1的索引页面如下所示(单击图像以完整大小打开).

在此输入图像描述

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

对于上面的查询,SQL Server不关心列的选择性.它对根页进行二进制搜索,发现密钥 (PPP...,3,~ )>=(JJJ...,1,~ ),< (SSS...,3,~ )因此它应该读取页面1:118.然后,它对该页面上的键条目进行二进制搜索,并找到向下移动的叶子页面.

按选择性顺序更改索引不会影响二进制搜索的预期密钥比较次数或需要导航以进行索引查找的页面数.充其量它可能会略微加快关键比较本身.

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

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

SELECT * ... WHERE  MostSelective = 'P'

SELECT * ...WHERE Least = '~'
Run Code Online (Sandbox Code Playgroud)

上述指数并未涵盖其中任何一项.MostSelective是有选择性的,足以制定一个寻求和查找有价值的计划,但查询Least不是.

然而,这种情况(非覆盖索引寻找复合索引的前导列的子集)只是一个可以由索引帮助的可能的查询类.如果您从未实际搜索过MostSelective它自己或其组合MostSelective, SecondMost并始终通过所有三列的组合进行搜索,那么这种理论优势对您来说毫无用处.

相反的查询,如

SELECT MostSelective,
       SecondMost,
       Least
FROM   Table2
WHERE  Least = '~'
ORDER  BY SecondMost,
          MostSelective 
Run Code Online (Sandbox Code Playgroud)

通过使用通常规定的相反顺序(因为它涵盖了查询),可以支持查找并以所需顺序返回行以启动.

所以这是一个经常重复的建议,但最多它是关于其他查询的潜在好处的启发式方法- 它实际上无法替代实际查看您的工作量.


OMG*_*ies 30

你应该在索引声明的开头放置最具选择性的列.

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

  • 地址

使用该address列的任何查询都可以使用索引,但如果查询只有一个city和/或state引用 - 则无法使用索引.这是因为没有引用最左边的列.查询性能应该告诉您哪个是最优的 - 单个索引,或具有不同顺序的多个组合.好读:金伯利·特里普的引爆点

  • @Abe:如果你查询了地址和城市,但没有说明 - 那么是的,将使用索引.换句话说,数据库能够使用部分索引来满足请求,只要它能够从索引的左侧开始并使用正在查询的字段向右移动.但是,如果您使用Address和State查询但不是city,它仍然可以使用索引,但它不会那么高效 - 因为现在它只能使用索引的Address部分(b/c next是城市,它没有在查询中使用). (3认同)

Ric*_*mes 9

选择性是一个非常小的因素;“最左边”是关键

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

这是一个简单的思考过程: 实际上,索引是所涉及的列的串联。

给出这个基本原理,唯一的区别是比较字符串中较早与较晚不同的两个“字符串”。这只是总成本的一小部分。正如一个答案中提到的那样,没有“第一次通过/第二次通过”。

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

  1. =任何顺序从用,测试的列开始。
  2. 然后添加一个范围列。

例如,极低选择性列必须排在第一位:

WHERE deleted = 0  AND  the_datetime > NOW() - INTERVAL 7 DAY
INDEX(deleted, the_datetime)
Run Code Online (Sandbox Code Playgroud)

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

(对列进行排序有更多规则。)