当数据按聚集索引的顺序时,覆盖索引是否会得到回报?

时间:2016-11-14 13:15:38

标签: sql sql-server indexing

我的情景,我有帖子,按类别分组。对于类别的概述列表,我想显示具有类别的前10个帖子的摘要(与显示完整数据的类别的详细视图相对)。前10个帖子由分数决定,分数来自另一个表格(实际上是一个索引视图 - 但这在这里无关紧要。)

表结构如下:

CREATE TABLE [dbo].[Categories]
(
    [Id] INT NOT NULL IDENTITY CONSTRAINT [PK_Categories] PRIMARY KEY,
    [Key] CHAR(10) CONSTRAINT [UK_Categories_Key] UNIQUE,
    [Caption] NVARCHAR(500) NOT NULL,
    [Description] NVARCHAR(4000) NULL
)
GO

CREATE TABLE [dbo].[Posts]
(
    [Id] INT NOT NULL IDENTITY CONSTRAINT [PK_Posts] PRIMARY KEY,
    [CategoryId] INT NOT NULL CONSTRAINT [FK_Posts_Category] FOREIGN KEY REFERENCES [dbo].[Categories] ([Id]),
    [Key] CHAR(10) CONSTRAINT [UK_Post_Key] UNIQUE,
    [Text] NVARCHAR(4000) NULL,
    [SummaryText] AS
        CASE WHEN LEN([Text]) <= 400
            THEN CAST([Text] AS NVARCHAR(400))
            ELSE CAST(SUBSTRING([Text], 0, 399) + NCHAR(8230) AS NVARCHAR(400)) --First 399 characters and ellipsis
        END
        PERSISTED
)
GO

CREATE TABLE [dbo].[Scores] (
    [Id] INT NOT NULL IDENTITY CONSTRAINT [PK_Scores] PRIMARY KEY,
    [CategoryId] INT NOT NULL CONSTRAINT [FK_Scores_Category] FOREIGN KEY REFERENCES [dbo].[Categories] ([Id]),
    [PostId] INT NOT NULL CONSTRAINT [FK_Scores_Post] FOREIGN KEY REFERENCES [dbo].[Posts] ([Id]),
    [Value] INT NOT NULL
)
GO

CREATE INDEX [IX_Scores_CategoryId_Value_PostId]
    ON [dbo].[Scores] ([CategoryId], [Value] DESC, [PostId])
GO

我现在可以使用视图来获取每个类别的前十个帖子:

CREATE VIEW [dbo].[TopPosts]
AS
SELECT c.Id AS [CategoryId], cp.PostId, p.[Key], p.SummaryText, cp.Value AS [Score]
FROM [dbo].[Categories] c
CROSS APPLY (
    SELECT TOP 10 s.PostId, s.Value
    FROM [dbo].[Scores] s
    WHERE s.CategoryId = c.Id
    ORDER BY s.Value DESC
) AS cp
INNER JOIN [dbo].[Posts] p ON cp.PostId = p.Id

我了解CROSS APPLY将使用覆盖索引IX_Scores_CategoryId_Value_PostId,因为它包含类别ID(对于WHERE)值(对于ORDER BYSELECT)和帖子ID(适用于SELECT),因此速度相当快。

现在的问题是:INNER JOIN怎么样?连接谓词使用post ID,它是Post表的聚簇索引(主键)的键。当我创建包含SELECT的所有字段的覆盖索引(见下文)时,我是否可以显着提高查询性能(具有更好的执行计划,减少的I / O,索引缓存等),即使访问聚集索引已经是一个非常快速的操作?

覆盖指数如下所示:

CREATE INDEX [IX_Posts_Covering]
    ON [dbo].[Posts] ([Id], [Key], [SummaryText])
GO

更新

由于我的问题的方向似乎并不完全清楚,让我更详细地记下我的想法。我想知道覆盖索引(或包含列的索引)是否可以更快,原因如下(并且性能增益值得):

  1. 硬盘驱动器访问。第二个索引会比聚集索引小得多,SQL Server必须通过较少的页面才能获得更好的读取性能。这是正确的,你会看到差异吗?
  2. 内存消耗。要将数据加载到内存中,我假设SQL Server必须将整行加载到内存中,然后选择所需的列。这不会增加内存消耗吗?
  3. CPU。我的假设是,您不会看到CPU使用率存在可测量的差异,因为从列中提取行本身并不是CPU操作。正确的吗?
  4. 缓存。我的理解是,您不会发现缓存有太大差异,因为SQL Server只会缓存它返回的数据,而不是整行。或者我错了吗?
  5. 这些基本上是(或多或少受过教育的)假设。如果有人可以告诉我这个非常具体的问题,我会非常感激。

3 个答案:

答案 0 :(得分:5)

这是一个有趣的问题,因为您提出的所有四个子问题都可以通过&#34来回答;它取决于&#34;,这通常是主题有趣的好迹象。

首先,如果你对SQL Server的工作原理有不健康的迷恋(就像我一样),那么源代码源于#34; Microsoft SQL Server Internals&#34;,Delaney等人。您不需要阅读所有~1000页,存储引擎上的章节本身就足够有趣。

我不知道这个特定覆盖索引在这个特定情况下是否有用的问题,因为我认为其他答案已经很好地涵盖(没有双关语意),包括建议使用{{1}对于不需要自己编制索引的列。

  

第二个索引会比群集小得多   索引,SQL Server必须通过较少的页面上的HD,其中   会产生更好的读取性能。这是正确的,你会看到吗?   区别?

如果您认为在覆盖索引的聚集索引页面的读取页面之间选择 ,则覆盖索引较小 1 ,这意味着更少的I / O,更好的性能,所有的好处。但查询不是在真空中执行 - 如果这不是表上的唯一查询,则缓冲池可能已经包含大部分或全部聚簇索引,在这种情况下,磁盘读取性能可能会受到负面影响阅读不太常用的覆盖指数。数据页面的总增长也可能会降低整体性能。优化器仅考虑单个查询;它不会根据所有组合的查询仔细调整缓冲池使用情况(通过简单的LRU策略删除页面)。因此,如果您过度创建索引,尤其是不经常使用的索引,则整体性能将受到影响。而且,当插入或更新数据时,它甚至没有考虑索引的固有开销。

即使我们假设覆盖指数是净收益,问题&#34;你会看到差异&#34; (如果,表现可测量增加)只能凭经验有效地回答。 INCLUDE是您的朋友(以及SET STATISTICS IO ON,在测试环境中)。您可以根据假设进行尝试和猜测,但由于结果取决于执行计划,索引的大小,SQL Server的总内存量,I / O特性,所有数据库的负载以及查询模式应用程序,除了大概猜测索引是否可能有用之外,我不会这样做。一般来说,当然,如果你有一个非常宽的桌子和一个小的覆盖指数,不难看出这是如何得到回报的。一般来说,你会很快看到索引不够,而不是来自太多的索引。但真正的数据库并不是在推广上运行。

  

要将数据加载到内存中,我假设SQL Server必须这样做   将整行加载到内存中,然后选择所需的列。   不会增加内存消耗吗?

见上文。聚集索引占用的页数多于覆盖索引,但是内存使用是否受到正面或负面影响取决于每个索引的使用方式。在最糟糕的情况下,聚集索引被其他不会从覆盖索引中获利的查询密集使用,而覆盖索引仅对稀有查询有帮助,因此所有覆盖索引都会导致缓冲池流失会减慢您的大部分工作量。这可能是不寻常的,也是服务器可以通过内存升级进行的一个标志,但它当然是可能的。

  

我的假设是你不会看到CPU的可测量差异   用法,因为从列中提取行本身不是CPU   操作。正确的吗?

CPU 使用通常不受行大小的影响。执行时间(反过来, 会影响使用,具体取决于您希望并行运行的查询数)。通过为服务器提供足够的内存来解决I / O瓶颈之后,仍然需要在内存中扫描数据。

  

我的理解是你在缓存中看不到很多不同,   因为SQL Server只会缓存它返回的数据,而不是   整行。或者我错了吗?

行存储在页面上,SQL Server将其读取的页面缓存在缓冲池中。它不会缓存结果集,也不会缓存作为查询执行的一部分生成的任何中间数据或单个行。如果在最初的空缓冲池上执行两次查询,第二个通常会更快,因为它需要的页面已经在内存中,但这是加速的唯一来源。

考虑到这一点,请参阅第一个问题的答案 - 是的,缓存会受到影响,因为覆盖索引的页面(如果使用的话)将与聚集索引的页面分开缓存(如果使用的话)。

1 如果由于页面拆分而导致覆盖索引严重碎片,则覆盖索引实际上可能不会更小。但这是一个学术观点,因为它并不是关于哪个索引在物理上更大,而是实际访问了多少页面。

答案 1 :(得分:4)

不,你不需要这个覆盖索引。

限制每个表的索引数:表可以包含任意数量的索引。但是,索引越多,修改表时产生的开销就越大。因此,在从表格中检索数据的速度和更新表格的速度之间存在权衡

您的方案更可能是作为OLTP系统而不是数据仓库,它将具有大量的在线事务(插入,更新,删除)。因此,创建此覆盖索引将减慢您的修改操作。

<强>更新

是的,每个类别将有10个帖子。因此,如果您有N个类别类型,则返回结果集最多为10 * N个帖子记录。

关于索引的另一个指南:如果您经常要检索大表中行的 15%,请创建索引。 (我的SQL调优讲师建议我们5%)如果超过15%,当我们使用Index时,最终执行计划将不是最佳的。

让我们考虑一下有关POST表的两个极端情况:

  1. 帖子表只有10 * N条记录,每个类别类型被帖子记录10次。因此,最终的执行计划将完全扫描POST表而不是使用任何索引。
  2. Post表的数量大于(10 * N / 15%),因此它将检索Post表中少于15%的行。优化程序将使用Post ID字段进行连接操作。它应该是一个哈希联接。
  3. 因此,即使您创建了覆盖索引,除非您使用提示,否则优化程序将永远不会使用它。

    更新:

    Clustered and Nonclustered Indexes Described

答案 2 :(得分:4)

您的非聚集覆盖索引可能会为聚类索引提供名义上的附加性能优势,但这取决于您查询的数据大小。如果行数相对较小,则可能没有任何有用的优势。

退一步,假设你的连接谓词只是[Posts]。[Id],将[Key]和[SummaryText]列添加为索引中的关键列是不必要的。应该将它们添加为非键列:

CREATE NONCLUSTERED INDEX [IX_Posts_Covering]
    ON [dbo].[Posts] ([Id])
    INCLUDE ([Key], [SummaryText])
GO

Per Microsoft:MSDN - Create Indexes with Included Columns

  

使用较大的索引键大小重新设计非聚簇索引,以便只有用于搜索和查找的列才是关键列。将覆盖查询的所有其他列转换为非键列。通过这种方式,您将拥有覆盖查询所需的所有列,但索引键本身很小且效率很高。

     

在非聚簇索引中包含非键列,以避免超出最多16个键列的当前索引大小限制,并且最大索引键大小为900字节。在计算索引键列数或索引键大小时,数据库引擎不考虑非键列。

基本上,覆盖索引复制[dbo]。[Posts]表,不包括[CategoryId]和[Text]列。因为覆盖索引中的列数较少,所以SQL应该能够为每个索引页填充更多行。基于这个假设(不可否认,可能需要仔细审查),当SQL遍历b-tree时,寻找跨页面来查找匹配的行,它可能在覆盖索引上名义上更好,因为它有更少的页面需要加载和查看

无论索引选择如何,您也可以考虑将您的联接到[帖子]表放入交叉申请。这可能会迫使寻求,尽管你的数据构成将决定效率。

CREATE VIEW [dbo].[TopPosts]
AS
SELECT c.[Id] AS [CategoryId], cp.[PostId], 
    cp.[Key], cp.[SummaryText], cp.[Value] AS [Score]
FROM [dbo].[Categories] c
CROSS APPLY (
    SELECT TOP 10 s.[PostId], s.[Value], p.[Key], p.[SummaryText]
    FROM [dbo].[Scores] s
    INNER JOIN [dbo].[Posts] p ON s.[PostId] = p.[Id]
    WHERE s.[CategoryId] = c.[Id]
    ORDER BY s.[Value] DESC
) AS cp

在一天结束时,它将取决于您的数据大小,磁盘IO,RAM等。您必须确定覆盖索引使用的额外空间是否可以证明标称性能增益,如果有的话。

索引使用情况的细分:https://dba.stackexchange.com/a/42568/2916