我的情景,我有帖子,按类别分组。对于类别的概述列表,我想显示具有类别的前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 BY
和SELECT
)和帖子ID(适用于SELECT
),因此速度相当快。
现在的问题是:INNER JOIN
怎么样?连接谓词使用post ID,它是Post
表的聚簇索引(主键)的键。当我创建包含SELECT
的所有字段的覆盖索引(见下文)时,我是否可以显着提高查询性能(具有更好的执行计划,减少的I / O,索引缓存等),即使访问聚集索引已经是一个非常快速的操作?
覆盖指数如下所示:
CREATE INDEX [IX_Posts_Covering]
ON [dbo].[Posts] ([Id], [Key], [SummaryText])
GO
更新
由于我的问题的方向似乎并不完全清楚,让我更详细地记下我的想法。我想知道覆盖索引(或包含列的索引)是否可以更快,原因如下(并且性能增益值得):
这些基本上是(或多或少受过教育的)假设。如果有人可以告诉我这个非常具体的问题,我会非常感激。
答案 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表的两个极端情况:
因此,即使您创建了覆盖索引,除非您使用提示,否则优化程序将永远不会使用它。
更新:
答案 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等。您必须确定覆盖索引使用的额外空间是否可以证明标称性能增益,如果有的话。