简单条件分解查询优化器及其性能
Posted
技术标签:
【中文标题】简单条件分解查询优化器及其性能【英文标题】:Simple condition break down query optimizer and its performance 【发布时间】:2015-10-06 11:39:58 【问题描述】:我有一个简单的查询:
select top 10 *
FROM Revision2UploadLocations r2l
inner join Revisions r on r2l.RevisionId = r.Id
INNER JOIN [Databases] [D] on [R].[DatabaseId] = [D].[Id]
INNER JOIN [SqlServers] [S] on [D].[InstanceId] = [S].[Id]
where --r.ValidationStatus in (2, 3) and
r2l.[ChecksumWasSent] = 0 AND r2l.Status = 2
这个查询通常执行0.5s:
但是相同的未注释条件的查询被执行了 5s (!!!) 并且有一个非常奇怪的执行计划 (Revisions 和 SqlServers 虽然没有链接列和最具选择性的条件 "r2l.[ChecksumWasSent] = 0 AND r2l.Status = 2" 在查询处理结束时执行:
ValidationStatus 是普通的 int 非空列。 列 Revision2UploadLocations.RevisionId、Revisions.DatabaseId、Databases.InstanceId 已编入索引。 以下是表格说明:
CREATE TABLE [SqlServers]
(
[Id] int identity(1,1) NOT NULL CONSTRAINT PK_SqlServers PRIMARY KEY,
...
)
CREATE TABLE [Databases](
[Id] int identity(1,1) NOT NULL CONSTRAINT PK_Databases PRIMARY KEY,
[InstanceId] int NOT NULL,
[Name] nvarchar(128) NOT NULL,
...
CONSTRAINT FK_Databases_SqlServers FOREIGN KEY ([InstanceId]) REFERENCES [SqlServers]([Id])
)
CREATE INDEX [IX_Databases_DatabaseId] ON [Databases] ([InstanceId] ASC)
CREATE TABLE [Revisions]
(
[Id] int identity(1, 1) NOT NULL,
[DatabaseId] int NOT NULL,
[BackupStatus] tinyint NOT NULL,
[ValidationStatus] tinyint NOT NULL,
...
CONSTRAINT PK_Revisions PRIMARY KEY([Id]),
CONSTRAINT FK_Revisions_Databases FOREIGN KEY ([DatabaseId]) REFERENCES [Databases]([Id])
)
CREATE INDEX [IX_Revisions_DatabaseId] ON [Revisions] ([DatabaseId] ASC)
CREATE TABLE [Revision2UploadLocations]
(
[Id] int NOT NULL IDENTITY (1, 1) CONSTRAINT PK_Revision2UploadLocations PRIMARY KEY,
[Status] int NOT NULL,
RevisionId int NOT NULL,
[ChecksumWasSent] bit NOT NULL,
CONSTRAINT FK_r2l_Revisions FOREIGN KEY ([RevisionId]) REFERENCES [Revisions]([Id])
)
CREATE INDEX [IX_Revision2UploadLocations_RevisionId] ON [Revision2UploadLocations] ([RevisionId] ASC)
如何提高此查询的性能?
编辑现在我有更多细节: 一些表(SqlServers 和 Databases)有 1-10 条记录,但 Revisions 和 Revision2UploadLocations)有 500K+ 记录,因此查询优化决定使用全扫描而不是索引搜索小表并优先使用。 Query Performance Tuning (SQL Server Compact):
小表格是一种内容适合一个或几个数据页的表格。避免索引非常小的表,因为执行表扫描通常更有效。
作为临时解决方案,我尝试使用查询提示 FORCE ORDER:Query Hint (SQL Server Compact) 响应时间从 5 秒减少到 0.5 秒。
但我认为这不是一个好的解决方案。
【问题讨论】:
【参考方案1】:Geoffrey 的解决方案没有给您预期的结果。 第一条语句选择 10 行,不保证它们的 r.ValidationStatus 为 2 或 3。所以最后,您可以获得少于 10 行(甚至根本没有行)。 我认为您可以将查询重写为:
SELECT top 10 *
FROM Revisions r
INNER JOIN Revision2UploadLocations r2l
ON r2l.RevisionId = r.Id
AND r2l.[ChecksumWasSent] = 0
AND r2l.Status = 2
INNER JOIN [Databases] [D] on [D].[Id] = [R].[DatabaseId]
INNER JOIN [SqlServers] [S] on [S].[Id] = [D].[InstanceId]
WHERE r.ValidationStatus in (2, 3)
如果 r2l.[ChecksumWasSent] 数据类型是位(布尔值):
0大于1,可以在RevisionId + Status上创建索引 1 比 0 多得多,您可以创建和定义 RevisionId + ChecksumWasSent + Status【讨论】:
我已经尝试过这个变体和其他一些类似的变体,但是查询优化器足够聪明,可以检测这些查询的身份,但不幸的是,它没有那么聪明,无法构建最佳计划。我会尝试推荐的索引,谢谢! 你有Revision.ValidationStatus
的索引吗?它也可以提供帮助。你有多少种不同的状态?不同状态之间的行分布是什么?
我已经尝试过索引 (RevisionId, Status) 和 (RevisionId, ChecksumWasSent, Status)。这很奇怪,但是 QA 仍然使用完整扫描来进行修订,然后才索引搜索 Revision2UploadLocations。 Revision2UploadLocations 的条件更具选择性。 ValidationStatus 具有以下值密度:1 (811)、2(441252)、4 (4)。
Revisions
和Revision2UploadLocations
中有多少行?你需要所有表的所有列 (*) 吗?如果没有,您可以在表别名前面加上星号
如果 Revision2UploadLocations
中的行数很多,而 ChecksumWasSent=0 的行数比 ChecksumWasSent=1 的行数少,并且如果您的 SqlServer发布/版本支持它,您可以在 RevisionId + Status
上创建过滤索引,其中 ChecksumWasSent=0【参考方案2】:
我过去发现,如果我首先将查询的第一部分插入到临时表中,并带有要进一步过滤的字段(“ValidationStatus”],然后查询你的临时表,性能/速度会很大更好的。 所以最初的查询是这样的:
select *
into #tmp
FROM Revision2UploadLocations r2l
inner join Revisions r on r2l.RevisionId = r.Id
INNER JOIN [Databases] [D] on [R].[DatabaseId] = [D].[Id]
INNER JOIN [SqlServers] [S] on [D].[InstanceId] = [S].[Id]
where --r.ValidationStatus in (2, 3) and
r2l.[ChecksumWasSent] = 0 AND r2l.Status = 2
那么最终的选择是:
select * from #tmp
where ValidationStatus in (2,3)
不需要索引,而且我知道优化器并不总是有效的奇怪之处,但这种方法在过去曾多次对我有用。
【讨论】:
我编辑了代码,因为我最初是从给定的 OPs 代码中复制和粘贴的。 “选择前 10 个 *”应该刚刚选择 *。但无论如何,关键是在将 (2,3) 中的 ValidationStatus 插入临时表之前,不要对其进行过滤。 对不起,但我认为这种方式类似于 FORCE ORDER 选项。我们严格设置表格处理的顺序。对我来说,正确的方法是向查询优化器提供更多信息并让他决定如何处理表。以上是关于简单条件分解查询优化器及其性能的主要内容,如果未能解决你的问题,请参考以下文章