SQL Server 查询优化器执行不必要的连接
Posted
技术标签:
【中文标题】SQL Server 查询优化器执行不必要的连接【英文标题】:SQL Server query optimizer performing an unnecessary join 【发布时间】:2016-08-23 02:30:11 【问题描述】:我想知道是否有人可以解释为什么 SQL Server(在我的例子中是 2016 RTM,但我怀疑这不是特定于版本)执行这个看似不必要的 INNER JOIN。
考虑以下两个通过外键连接的表:
CREATE TABLE [dbo].[batches](
[Id] [smallint] IDENTITY(1,1) PRIMARY KEY,
[Date] [date] NOT NULL,
[Run] [tinyint] NOT NULL,
[Clean] [bit] NOT NULL)
CREATE TABLE [dbo].[batch_values](
[Batch_Id] [smallint] NOT NULL,
[Key] [int] NOT NULL,
[Value] [int] NOT NULL,
CONSTRAINT [PK_batch_values] PRIMARY KEY CLUSTERED
( [Batch_Id] ASC, [Key] ASC))
GO
ALTER TABLE [dbo].[batch_values] WITH CHECK
ADD CONSTRAINT [FK_batch_values_batches] FOREIGN KEY([Batch_Id])
REFERENCES [dbo].[batches] ([Id])
GO
ALTER TABLE [dbo].[batch_values] CHECK CONSTRAINT [FK_batch_values_batches]
GO
用一些数据填充表格:
SET NOCOUNT ON;
DECLARE
@BatchCount int,
@BatchId smallint,
@KeyCount int;
SET @BatchCount = 1;
WHILE @BatchCount <= 100
BEGIN
INSERT INTO dbo.[batches]
VALUES (DATEADD(dd, @BatchCount / 10, '2016-01-01'), @BatchCount % 10, @BatchCount % 2);
SET @BatchId = SCOPE_IDENTITY();
SET @KeyCount = 1;
WHILE @KeyCount <= 1000
BEGIN
INSERT INTO dbo.batch_values
VALUES (@BatchId, @KeyCount, RAND() * 1000000 - 500000);
SET @KeyCount = @KeyCount + 1;
END;
SET @BatchCount = @BatchCount + 1;
END;
现在,如果我运行以下查询,执行计划显示 SQL Server 正在对 [batches] 表执行 INNER JOIN,即使没有从中选择任何列,也无法从 [batch_values] 中删除任何记录由于外键约束而连接的结果。
screenshot of query and execution plan
在我看来,查询优化器应该丢弃不必要的 INNER JOIN 并简单地在 [batch_values] 上进行主键搜索,但事实并非如此。
这很重要,因为如果我开发连接多个表的视图以呈现基础数据的“更大图景”以便于使用,那么在查询这些视图时我将受到性能影响。
【问题讨论】:
行不能被删除,但可以相乘。 哦,不,他们不能,因为您在 batch_values 上指定 PK。嗯。 哦,是的,他们可以,因为 FK 只走一条路。它可能从另一个表中丢失。这是一个奇怪的情况,因为它是一个垂直分区的表(都连接到主键上)。如果我以正确的方式阅读 FK,您可以在batch
中创建一条记录,而 batch_values
中不存在该记录(但不是其他方式)
嗨尼克。是的,您正在正确阅读 FK,可能在 [batches] 表中有一个批次,但在 [batch_values] 表中没有相应的值。从语义上讲,这意味着一个空批次,即批次已生成但结果是空的。但是,无法通过与 [batches] 的连接来将 [batch_value] 中的记录相乘,因为 [batches].[Id] 是主键,因此可能只有一个。
不能相乘,但可以排除。这是一个内部连接。我想知道外部联接是否会有所不同
【参考方案1】:
SQL Optimizer 使用 JOIN ELIMINATION 有很多限制
例如如果您在外键中使用了多个列,或者约束不受信任,或者标记为“不用于复制”等。
如果使用外键中的列指定 WHERE 谓词,SQL Server 可能不会使用 JOIN ELIMINATION。
删除 WHERE 或从 WHERE 中删除“Batch_id = 100”,您应该会看到优化器现在使用 JOIN ELIMINATION
该主题的文档有限,因此我无法提供证明链接,但在过去 5-7 年中,许多人针对不同版本报告了此问题,并同意行为是设计使然。我的建议是向 MS 提出事件并直接询问他们是否对您的系统至关重要。
【讨论】:
我又玩了一些,也搜索了一些 JOIN ELIMINATION,看起来你对 WHERE 子句的看法是正确的。以上是关于SQL Server 查询优化器执行不必要的连接的主要内容,如果未能解决你的问题,请参考以下文章
强制SQL Server执行计划使用并行提升在复杂查询语句下的性能
SQL -- SQL Server 查询优化器(Query Optimizers)