【问题标题】:SQL Server non-clustered index designSQL Server 非聚集索引设计
【发布时间】:2011-10-07 21:08:36
【问题描述】:

这个问题涉及在 SQL Server 2005 中设计非聚集索引。

我有一张有几百万行的大桌子。行只会被读取或插入。大多数操作都是读取。我一直在查看访问表的各种SELECT 查询,目的是提高读取访问速度。磁盘空间不是真正的问题。 (每一行都有一个唯一的 ID,我将它用作聚集索引中的单个字段。)

我的问题是,如果非聚集索引索引的列多于查询使用的列,这是否会导致查询执行速度比完全匹配查询的索引慢?

随着不同查询数量的增加,WHERE 子句中使用的列的排列数量也会增加。我不确定在具有少量列的多个索引(每个查询一个)与在更多列上的更少索引之间进行权衡。

例如,假设我有两个 SELECT 查询。第一个在其WHERE 子句中使用列 A、B、C 和 D,第二个使用 A、B、E 和 F。这里的最佳实践是定义两个索引,一个在 A/B/C/ D 和另一个在 A/B/E/F 上;还是 A/B/C/D/E/F 上的单个索引?

【问题讨论】:

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


【解决方案1】:

首先,索引中列的顺序很重要。因此,相应地构建/调整您的查询将使您能够充分利用您构建的索引。

是分别拥有两个索引还是一个索引取决于争用列的依赖关系以及运行的查询类型。在您的示例中,如果 E 和 F 列与 C 和 D 列相关或依赖于 C 和 D 列,那么让一个索引覆盖所有列是有意义的。

【讨论】:

  • 感谢您的回答!两个后续问题: 1. 当您说列的顺序很重要时,您是指列出现的顺序,还是排序顺序,或两者兼而有之? 2.当你说“如果E和F列与C和D相关或依赖于C和D”时,什么样的关系是重要的? (比如)C/D/E/F 中的值是相互独立的,但每一列都有重复项。
  • 1.是的,列出现在 CREATE INDEX 语句中的顺序。确保您的查询在 WHERE 子句中使用相同的顺序,以最大限度地利用索引。 2.当我说字段之间的关系时,一个简单的例子是找到Jason Bourne,您将使用一个索引,该索引使用具有相同顺序的LASTNAME,FIRSTNAME的索引,然后使用查询WHERE LASTNAME = 'Bourne' AND FIRSTNAME = '杰森'。如果您考虑以与上述相反的顺序使用 WHERE 将无法充分利用索引。
  • (...contd) 列在索引中的顺序取决于业务。想象一个有 70% 的人姓 BOURNE 的城市。那么在 orser 中使用带有 FIRSTNAME、LASTNAME 列的索引实际上是有意义的。
  • "如果您考虑以与上述相反的顺序使用 WHERE,将无法充分利用索引。"这不是真的。您指定 WHERE 条件的顺序无关紧要。
【解决方案2】:

我的问题是,如果非聚集索引索引的列多于查询使用的列,这是否会导致查询执行速度比完全匹配查询的索引慢?

不,拥有更多列不会减慢使用索引中前 1、2、n 列的查询的查询时间。话虽如此,如果您的内存有限,则加载到内存中的索引可能会将其他内容推出内存并减慢查询速度,但如果您有足够的内存,这应该不是问题。

随着不同查询数量的增加,WHERE 子句中使用的列的排列数量也会增加。我不确定在具有少量列的多个索引(每个查询一个)与在更多列上的更少索引之间进行权衡。

您应该首先将最常查询的唯一字段添加到索引中。 具有多列的较少索引可能无法满足您的需求。

例如,如果您有一个包含以下列的索引:

  • A 列
  • B 列
  • C 列
  • D 列
  • E 栏
  • F 列

按此顺序,针对 ColumnA、ColumnB、ColumnC、ColumnD... 进行过滤的查询将使用索引,但如果您只是针对 ColumnE 或 ColumnF 进行查询,则不会使用索引。

如果您在一个表上有六个索引,每个索引只有一列,请采用不同的方法

  • Index1 - 列A
  • Index2 - ColumnB
  • Index3 - ColumnC
  • Index4 - ColumnD
  • Index5 - ColumnE
  • Index6 - ColumnF

在这种情况下,这 6 个索引中只有一个会用于任何查询。

此外,如果您的索引包含的值不是很有选择性,那么它可能对您没有帮助。例如,如果您有一个名为 GENDER 的列,其中可能包含以下值(男性、女性和未知),那么在索引中包含该列可能不会帮助您。当查询运行时,SQL Server 可能会确定它们的列选择性不够,只是假设全表扫描会更快。

有很多方法可以找出您的查询正在使用哪些索引,但我使用的一种方法是查看从未使用过的索引。在您的数据库中运行以下查询,并找出您认为正在使用的索引是否真的被使用了。

SELECT iv.table_name, 
        i.name                           AS index_name, 
        iv.seeks + iv.scans + iv.lookups AS total_accesses, 
        iv.seeks, 
        iv.scans, 
        iv.lookups, 
        t.indextype, 
        t.indexsizemb 
FROM   (SELECT i.object_id, 
                Object_name(i.object_id) AS table_name, 
                i.index_id, 
                SUM(i.user_seeks)        AS seeks, 
                SUM(i.user_scans)        AS scans, 
                SUM(i.user_lookups)      AS lookups 
        FROM   sys.tables t 
                INNER JOIN sys.dm_db_index_usage_stats i 
                    ON t.object_id = i.object_id 
        GROUP  BY i.object_id, 
                    i.index_id) AS iv 
        INNER JOIN sys.indexes i 
            ON iv.object_id = i.object_id 
            AND iv.index_id = i.index_id 
        INNER JOIN (SELECT sys_schemas.name AS schemaname, 
                            sys_objects.name AS tablename, 
                            sys_indexes.name AS indexname , 
                            sys_indexes.type_desc AS indextype , 
    CAST(partition_stats.used_page_count * 8 / 1024.00 AS DECIMAL(10, 3)) AS indexsizemb 
FROM   sys.dm_db_partition_stats partition_stats 
INNER JOIN sys.indexes sys_indexes 
    ON partition_stats.[object_id] = sys_indexes.[object_id] 
        AND partition_stats.index_id = sys_indexes.index_id 
        AND sys_indexes.type_desc <> 'HEAP' 
INNER JOIN sys.objects sys_objects 
    ON sys_objects.[object_id] = partition_stats.[object_id] 
INNER JOIN sys.schemas sys_schemas 
    ON sys_objects.[schema_id] = sys_schemas.[schema_id] 
        AND sys_schemas.name <> 'SYS') AS t 
ON t.indexname = i.name 
AND t.tablename = iv.table_name 
--WHERE t.IndexSizeMB > 200 
WHERE  iv.seeks + iv.scans + iv.lookups = 0 
ORDER  BY total_accesses ASC; 

我通常会跟踪从未使用过的索引,或者在 SQL Server 重新启动后几个月未使用过的索引,并确定是否应该删除它们。有时过多的索引会减慢 SQL Server 找出运行查询的最佳路径的速度,而删除未使用的索引可以加快该过程。

我希望这有助于理解您的索引。

【讨论】:

    【解决方案3】:

    现有的答案已经很好了。这里有一个新的想法:在一定的工作负载和内存可用性下找到一组最优的索引是一个难题,需要在大搜索空间中进行穷举搜索。

    数据库引擎优化顾问 (DTA) 实现了这一点!我建议你记录一个有代表性的工作量(包括写!),让 DTA 给你建议。它也会考虑磁盘空间。

    【讨论】:

      【解决方案4】:

      磁盘空间不是问题。

      请不要这样想。如果您有 500 GB 的可用空间,则无关紧要。表或索引越大,从磁盘读取所需的时间越多,在内存(即缓冲池)中占用的空间越多,满足查询所需的逻辑读取就越多。有关此主题的更多详细信息,请查看此处: http://www.sqlservercentral.com/articles/data-modeling/71725/

      (每一行都有一个唯一的 ID,我将其用作 聚集索引。)

      您的大多数查询是否在 WHERE 子句中使用该 ID?如果不是,那么它可能不是聚集索引的好选择。

      我的问题是,如果非聚集索引索引的列多于 由查询使用,这是否会转化为比查询执行更慢的查询 与查询完全匹配的索引?

      这取决于几个因素。你说的还有多少领域?一个 1 字节的 TINYINT 字段?还是几个字段组成 300 个字节?除非您使用过滤索引,否则您需要将索引的大小加上聚集索引的大小(对于非唯一索引)乘以行数。正如我上面提到的,占用的空间越多确实意味着速度越慢,但实际上在 100 MB 上增加 5 MB 可能不会有明显的差异。

      请记住,索引设计既是艺术也是科学。您需要考虑最常执行哪些查询以及使用哪些 ORDER BY 以及 WHERE 子句。您需要记住,如果索引的前导列不存在于查询中,则不会使用索引,即使索引的其余字段在查询中也是如此。

      一般来说,您不想单独索引每个字段,因为:

      1. 索引过多会减慢 DML 操作,即使此表上的大多数操作都是 SELECT 也是一个问题
      2. 索引过多会增加死锁的机会
      3. 要求 4 个字段的查询不会使用 4 个单独的索引。大多数情况下,优化器会选择它认为效果最好的那个,有时它可能会选择将其中两个组合在一起,尤其是当您有 OR 条件时

      例如,假设我有两个 SELECT 查询。第一个使用列 A, B、C、D 在其 WHERE 子句中,第二个使用 A、B、E 和 F。

      最好只对 A 和 B 编制索引,然后看看效果如何。如果该组合是唯一的,则考虑使用复合聚集索引的可能性。如果它们不是唯一的但仍被大多数查询使用,请考虑创建聚集索引:A、B、ID 字段。最后包含 ID 字段会赋予组合唯一性。这很重要,因为如果您的聚集索引不是主键,那么您真的需要将聚集索引声明为 UNIQUE,这样它就没有隐藏的唯一标识符字段。根据定义,主键是唯一的。

      还要查看索引的 INCLUDE 选项。

      是的,列顺序确实很重要,因为它决定了索引的组织方式。想想拥有 ActionDate、CustomerID 与 CustomerID、ActionDate 之间的区别。如果 ActionDate 是第一个,则更容易找到某个日期范围内的所有 CustomerID。但是,如果您只关心一个客户并且想要他们信息的几个不同日期,您将不得不跳过整个索引,因为他们的数据将分散在整个索引中。在这种情况下,您最好先使用 CustomerID,因为您可以更快地缩小到数据开始的位置,然后根据日期获取您想要的数据。

      但是不,您的 WHERE 条件的顺序与是否使用索引无关。 SQL Server 使用基于成本的优化器扫描所有条件并使用索引(前导列)的统计信息来确定最合适的计划应该是什么。

      最后,一定要测试各种策略。不要只尝试一件事然后继续前进。您的描述非常笼统——甚至没有给出字段的数据类型或字段的使用方式——所以这里任何高度具体的建议都是值得怀疑的。使用 SET STATISTICS IO ON 并查找逻辑读取。这个数字越低越好!

      【讨论】:

        猜你喜欢
        • 2012-10-01
        • 2011-04-20
        • 2018-05-08
        • 2014-04-27
        • 2013-03-22
        • 1970-01-01
        • 2011-05-13
        • 2013-08-20
        相关资源
        最近更新 更多