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 server查询优化器结果的方法

SQL -- SQL Server 查询优化器(Query Optimizers)

一条SQL在MySQL中是如何执行的

Sql Server 优化 SQL 查询:如何写出高性能SQL语句

sql server 统计信息