【问题标题】:How important is the order of columns in indexes?索引中列的顺序有多重要?
【发布时间】:2011-01-18 13:23:04
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: sql sql-server sql-server-2005 tsql indexing


    【解决方案1】:

    选择性是一个很小的因素; “最左边”很重要

    复合索引中各个列的选择性在选择顺序时并不重要

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

    鉴于这个理由,唯一的区别是比较两个“字符串”,它们在字符串中较早与较晚不同。这只是总成本的一小部分。正如一个答案中提到的那样,没有“第一遍/第二遍”。

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

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

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

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

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

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

    【讨论】:

    • 投反对票是因为我错了吗?还是因为我有强烈的意见?还是别的什么?
    • 不是我的反对票,但对我来说已删除 = 0 听起来不是低选择性?我想这将是表中的大多数行。
    • @Greg - 我认为这意味着“低选择性” - 也就是说,使用 deleted 对过滤掉不需要的行没有多大帮助。你有更好的例子吗? (这就是我写答案时突然出现在我脑海中的那个。)
    • 我的误解。
    • @ClickOk - 谢谢。我的食谱提供了一些基本信息:mysql.rjweb.org/doc.php/index_cookbook_mysql
    【解决方案2】:

    正如 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 艺术实际上并不是索引的结构。 Table1 的索引页如下所示(点击图片以全尺寸打开)。

    索引页包含包含整个键的行(在这种情况下,实际上为行标识符附加了一个附加键列,因为索引未声明为唯一但可以忽略further information about this can be found here)。

    对于上面的查询,SQL Server 并不关心列的选择性。它对根页面进行二分搜索,发现 Key (PPP...,3,~ )>=(JJJ...,1,~ )< (SSS...,3,~ ),因此它应该读取页面 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 
    

    如果使用通常规定的顺序相反的顺序会有所帮助 - 因为它涵盖了查询,可以支持查找并以启动所需的顺序返回行。

    所以这是一条经常重复的建议,但最多只是对其他查询的潜在好处的启发式 - 它不能替代实际查看您的工作量。

    【讨论】:

    • 这个答案似乎是所有答案中解释最清楚的,但我很怀疑,因为赞成票的数量很少。我很好奇,你还同意这个解释吗?也就是说,你对这个过程的理解有没有改变?如果没有,你知道为什么这个答案在这里得到如此证实吗?
    • @Lopside - 不,答案是正确的。它是在页面上其他人发布的 6 年后发布的,因此在某种程度上解释了投票差异
    • 谢谢。只是为了纠正一个错字,我的意思是说“......这里的答案是如此未经证实?”
    【解决方案3】:

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

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

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

    因此,不幸的是,没有“正确”的顺序。您不应该遵循任何千篇一律的食谱,而是分析您要针对这些表使用的查询模式并确定哪个索引列顺序是正确的。

    【讨论】:

    • 像往常一样出色的反应莱姆斯。我将再阅读您的第三段并跟进。我怀疑这可能正是我需要做的。
    • "SQL Server 只有在指定了最左边的列时才能使用索引进行范围扫描,然后只有在指定下一个最左边的列的情况下,以此类推。" 这正是我所缺少的,谢谢!我不知道范围扫描只能在最右边使用的索引列上完成,但现在我这样做了,这很有意义。
    • 这个解释适用于Oracle DB吗?
    • @Roizpi 是的,基本上任何带有索引的关系数据库都以相同或非常相似的方式工作。
    • @Sam 表达式 A=@a AND B=@bB=@b AND A=@a 并且在执行过程中基本上无法区分,因此差异无关紧要。
    【解决方案4】:

    看看这样的索引:

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

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

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

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

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

    【讨论】:

    • 在上图中,请记住,只有在查询中指定了第 1 列时,该索引才有用。如果您的查询仅在 Join 或 Search Predicate 中指定第 2 列,那么这将无益。所以那里的订单也很重要。也许这是不言而喻的,但想提一下。
    • 另外请记住,假设您的索引如上图,并且您的查询过滤column1和column2,但是column2更独特,您真正想要过滤的实际上是column2,那么它只在第 2 列是第一个索引时更有利。这似乎违反直觉,但请记住,索引存储在多个页面上,并且是具有一系列值的树,而上面的第 1 列确实否定了 1/2 的可能性,索引已经知道要直接进入哪个索引页面Column2 的值,不需要 Column 1 来缩小集合范围。
    • 此图片不能准确表示索引的结构或导航方式。已提交解决此问题的答案stackoverflow.com/a/39080819/73226
    • @MartinSmith 我不同意这是不准确的。这是非常公认的非常简化,这是我的意图。不过,对于那些想要更深入研究的人来说,我们非常感谢您深入了解关卡的更多细节。如果您查看您的树图像,您将看到我以非常简单的方式说明的内容。这不是很独特,甚至不是特定于 SQL 的; B-tree 索引在很多方面都很常见。
    • 拥有多个按不同顺序排列的索引是否有益?例如 A,B,C 和 B,A,C 以帮助进行不同的分组可能性?
    【解决方案5】:

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

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

    • 地址
    • 城市
    • 状态

    使用address 列的任何查询都可以使用索引,但如果查询只有city 和/或state 引用 - 则不能使用索引。这是因为最左边的列没有被引用。查询性能应该告诉您哪个是最佳的 - 单个索引,或具有不同顺序的多个组合。好读:The Tipping Point,作者 Kimberley Tripp

    【讨论】:

    • 如果只有最右边的列没有被使用怎么办?所以查询使用了地址和城市,但没有使用状态。那么会使用索引吗?
    • @Abe:不会使用最右边的 - 你必须满足从左边开始的索引顺序。漏了一个,用不了。
    • @Abe:如果您查询的是地址和城市,但没有说明 - 那么是的,将使用索引。换句话说,数据库能够使用部分索引来满足请求,只要它能够从索引的左侧开始并在使用正在查询的字段时移动到右侧。但是,如果您使用 Address 和 State 而不是 city 进行查询,它可能仍然使用索引,但效率不会那么高 - 因为现在它只能使用索引的 Address 部分(b/c 接下来是city 并且它没有在查询中使用)。
    猜你喜欢
    • 2011-01-12
    • 2014-08-10
    • 2017-12-19
    • 2017-07-04
    • 2017-11-18
    • 1970-01-01
    • 2010-12-22
    • 2011-05-14
    相关资源
    最近更新 更多