Answers:
看一下这样的索引:
Cols
1 2 3
-------------
| | 1 | |
| A |---| |
| | 2 | |
|---|---| |
| | | |
| | 1 | 9 |
| B | | |
| |---| |
| | 2 | |
| |---| |
| | 3 | |
|---|---| |
看到第一列比第一列限制更多的结果,如何限制第一列?如果想象一下如何遍历索引,第1列然后是第2列,这样会更容易...您会看到,第一遍中大部分结果的丢失使第二步快得多。
另一种情况,如果您查询第3列,则优化器甚至不会使用索引,因为它对缩小结果集完全没有帮助。 随时在查询中,在进行下一步之前,缩小要处理的结果数的范围即可提高性能。
由于索引也是以这种方式存储的,因此在查询索引时,不会在索引上回溯以找到第一列。
简而言之:不,这不是为了展示,而是有真正的性能优势。
列的顺序至关重要。现在哪个顺序正确,取决于您要如何查询它。索引可用于执行精确搜索或范围扫描。精确查找是指为索引中所有列的值都指定并且查询恰好位于该行所关注的情况。对于查找,列的顺序无关紧要。范围扫描是仅指定一些列的情况,在这种情况下,顺序变得很重要。只有指定了最左列,然后指定了下一个最左列,SQL Server才可以将索引用于范围扫描。如果您在(A,B,C)上有一个索引,则可以使用该索引对for 或A=@a
for 进行范围扫描,A=@a AND B=@b
但不能进行。情况是混合的,例如B=@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
指定了column 时,索引on 才有效。但是,最有选择性的是,内部列的相关性将迅速降低。很多时候,您会发现(MostSelective) include (SecondMost, Least)
或上有一个更好的索引(MostSelective, SecondMost) include (Least)
。由于内部列的相关性较低,因此将低选择性列放置在索引中的此类正确位置上,只会使它们产生寻找噪音,因此将它们移出中间页并仅将它们保留在叶子页上是有意义的,因为查询覆盖率的目的。换句话说,将它们移动到INCLUDE。随着Least
列大小的增加,这一点变得更加重要。想法是该索引只能使指定以下内容的查询受益MostSelective
无论是精确值还是范围,该列的选择性最高,已经在很大程度上限制了候选行。
另一方面,上的索引(Least, SecondMost, MostSelective)
看似错误,但实际上它是一个功能强大的索引。因为它具有Least
列作为其最外面的查询,所以它可用于必须在低选择性列上聚合结果的查询。这样的查询在OLAP和分析数据仓库中很普遍,这正是此类索引非常适合它们的地方。这样的索引实际上是出色的聚集索引,恰好是因为它们将物理布局组织在大块相关行上(相同的Least
值,通常表示某种类别或类型),并且有助于分析查询。
因此,不幸的是,没有“正确”的命令。您不应该遵循任何曲奇工具的配方,而应针对这些表分析要使用的查询模式,并确定哪个索引列顺序正确。
正如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
则不然。
但是,这种情况(在复合索引的前导列的子集上不包含索引搜索)只是索引可以帮助的一类可能的查询。如果您从不真正地单独搜索MostSelective
或MostSelective, SecondMost
通过全部三列的组合进行搜索,那么这种理论上的优势对您毫无用处。
相反的查询如
SELECT MostSelective,
SecondMost,
Least
FROM Table2
WHERE Least = '~'
ORDER BY SecondMost,
MostSelective
可以通过具有通常规定的顺序的相反顺序来获得帮助-因为它涵盖了查询,因此可以支持查找并以所需的顺序返回行来引导。
因此,这是一个经常重复的建议,但最多不过是对其他查询的潜在好处的一种试探法-它不能替代实际查看您的工作量。
您应该在索引声明的开头放置最有选择性的列。
正确。索引可以是复合的-由多列组成-由于最左边的原则,顺序很重要。原因是数据库从左到右检查列表,并且必须找到与定义的顺序匹配的对应列引用。例如,在具有列的地址表上建立索引:
使用该address
列的任何查询都可以使用索引,但是如果查询中只有一个city
和/或state
引用,则不能使用该索引。这是因为未引用最左边的列。查询性能应该告诉您哪个是最佳的-单个索引或具有不同顺序的多个组合。阅读:金伯利·特里普(Kimberley Tripp)的《引爆点》
所有其他答案都是错误的。
选择订单时,复合索引中各个列的选择性无关紧要。
这是一个简单的思考过程: 有效地,索引是所涉及列的串联。
考虑到这一原理,唯一的区别是比较字符串中前后不同的两个“字符串”。这只是总成本的一小部分。如一个答案中所述,没有“第一遍/第二遍”。
那么,应该使用什么顺序?
=
,在任何顺序。例如,非常低的选择性列必须首先在此列:
WHERE deleted = 0 AND the_datetime > NOW() - INTERVAL 7 DAY
INDEX(deleted, the_datetime)
在索引中交换顺序将使其完全忽略deleted
。
(还有很多用于排序列的规则。)
deleted
并不能帮助您过滤掉不需要的行。你有更好的例子吗?(那是我写《答案》时突然想到的那个。)