【问题标题】:nonclustered index on primary key column?主键列上的非聚集索引?
【发布时间】:2013-12-02 03:40:59
【问题描述】:

我在一个表上有一个主键(比如 ContactID)。 SQL Server 自动在该列上创建和维护一个聚集索引。当我运行 Tuning Advisor(针对性能跟踪)时,它似乎建议在同一列上使用另一个 INDEX - 在 contactID 列上使用 NON CLUSTERED 索引。这有什么帮助 - 因为列上已经有一个聚集索引?

【问题讨论】:

  • 新索引中是否包含其他列?新索引的排序方式是否与其他索引相同?
  • 你能发布推荐索引的脚本吗?

标签: sql-server performance sql-server-2008 indexing


【解决方案1】:

如果查询优化顾问建议在主键上使用非聚集索引,那么它也在建议在另一列(或多列)上使用聚集索引。

主键是一个约束,而不是一个索引。 MS SQL Server 所做的假设是,主键也是从表中检索数据的主要方式(通过 'where ContactID = 2' 或 ContactID 上的表之间的连接)。该假设意味着在构成主键的列上也会自动创建聚集索引。这种行为还有其他原因,但现在让我们保持简单。

现在,如果针对该表的大多数查询都针对联系人名字(ContactFirstName 字段),例如“Where ContactFirstName LIKE 'Muh%'”,那么 SQL Server 将建议将聚集索引从 ContactID 更改为 ContactFirstName,因为表只能有 1 个聚集索引。主键约束仍然存在(并防止重复行),但表中的数据将按 ContactFirstName 物理排序。

调优顾问消耗的工作量将决定调优顾问的建议。调优顾问还将仅使用来自工作负载的最高资源查询的一部分,而不是整个工作负载来做出决定。

【讨论】:

  • 这是有道理的。谢谢
【解决方案2】:

在非常特殊的情况下,例如包含 ContactID 的表很大,和/或行很大(即很多大的 varchar),与将 ContactID 作为聚集索引相比,扫描聚集索引可能会占用大量 IO/内存索引 AND 作为非聚集索引。

如果查询需要按 ContactID 扫描表,并且 ContactID 上只有一个聚集索引,则从磁盘读取整行数据。但是,如果您在 ContactID 上还有一个非聚集索引,则只会从磁盘读取 ContactID。

这与集群与非集群在磁盘上的存储方式有关。聚集索引由 ContactID 存储,但所有行数据也与它一起存储。假设每行大到可以占用一页(8KB),那么扫描 100,000,000 行需要 800,000,000 kb 的磁盘 io。

非聚集索引只会将 ContactID 存储在其“行”中。假设 ContactID 为 8 字节 (bigint),则 1000 行非聚集索引可以放在一页 (8KB) 中。现在,通过 ContactID 扫描 100,000,000 行,只需要 (100,000,000 / 1000 * 8) = 800,000 KB 的磁盘 io。

如果正在分析的查询被相当频繁地调用,则 800,000 KB 与 800,000,000 KB 相比意义重大。

然而,正如 Evadman 所建议的,Tuning Advisor 只关注特定的工作负载。在大多数情况下,额外的非聚集索引只是插入/删除的额外工作量。

现实生活中的例子,我使用一个有很多 varchars 的表。聚集索引为 5521 MB。有几个查询,每秒调用多次,最终对聚集索引列进行部分扫描(我们称之为 P_ID)。 P_ID 上的非聚集索引为 211 MB(比聚集 Idx 小 26 倍)。这导致查询执行时间以及磁盘和内存负载显着减少。

奖励:查询聚集索引和非聚集索引的大小

DECLARE @TableName VARCHAR(200)
SET @TableName = 'NAME_OF_YOUR_TABLE'

SELECT
    OBJECT_NAME(i.OBJECT_ID) AS TableName,
    i.name AS IndexName,
    i.index_id AS IndexID,
    8 * SUM(a.used_pages) AS 'Indexsize(KB)',
    (8 * SUM(a.used_pages)) / 1024 AS 'Indexsize(MB)'
FROM sys.indexes AS i
JOIN sys.partitions AS p 
    ON p.OBJECT_ID = i.OBJECT_ID AND p.index_id = i.index_id
JOIN sys.allocation_units AS a 
    ON a.container_id = p.partition_id
WHERE OBJECT_NAME(i.object_id) = @TableName
GROUP BY i.OBJECT_ID,i.index_id,i.name
ORDER BY OBJECT_NAME(i.OBJECT_ID),i.index_id

【讨论】:

  • 谢谢。这也很有帮助。
  • 很好的例子。 ContactID 的非聚集索引将被称为“覆盖索引”;索引包含满足查询所需的所有数据。 ContactID 听起来像是一个递增的数字来标识一行,因此它本身可能对应用程序没有用处。但是,如果另一个表的外键是 ContactID,这意味着对另一个表的每次插入/更新都需要检查 ContactID 是否存在。由于在该查询中只需要 ContactID,因此 ContactID 上的覆盖索引将满足它。因此,跳过了对大型聚集索引的完整扫描。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-21
  • 2018-04-26
  • 1970-01-01
  • 2012-12-30
  • 2011-05-15
  • 2021-12-14
  • 2012-08-26
相关资源
最近更新 更多