带有冗余谓词的 LEFT JOIN 比 CROSS JOIN 执行得更好?

Posted

技术标签:

【中文标题】带有冗余谓词的 LEFT JOIN 比 CROSS JOIN 执行得更好?【英文标题】:LEFT JOIN With Redundant Predicate Performs Better Than a CROSS JOIN? 【发布时间】:2022-01-09 08:49:15 【问题描述】:

我正在查看其中两个语句的执行计划,我有点困惑为什么 LEFT JOIN 语句比 CROSS JOIN 语句执行得更好:

表定义:

CREATE TABLE [Employee] (
    [ID]                int             NOT NULL    IDENTITY(1,1),
    [FirstName]         varchar(40)     NOT NULL,
    CONSTRAINT [PK_Employee] PRIMARY KEY CLUSTERED ([ID] ASC)
);

CREATE TABLE [dbo].[Numbers] (
    [N] INT IDENTITY (1, 1) NOT NULL,
    CONSTRAINT [PK_Numbers] PRIMARY KEY CLUSTERED ([N] ASC)
); --The Numbers table contains numbers 0 to 100,000.

我为每位员工加入“一天”的问题查询:

DECLARE @PeriodStart AS date = '2019-11-05';
DECLARE @PeriodEnd AS date = '2019-11-05';

SELECT E.FirstName, CD.ClockDate
FROM Employee E
    CROSS JOIN (SELECT DATEADD(day, N.N, @PeriodStart) AS ClockDate 
                FROM Numbers N 
                WHERE N.N <= DATEDIFF(day, @PeriodStart, @PeriodEnd)
        ) CD
WHERE E.ID > 2000;

SELECT E.FirstName, CD.ClockDate
FROM Employee E
    LEFT JOIN (SELECT DATEADD(day, N.N, @PeriodStart) AS ClockDate 
                FROM Numbers N 
                WHERE N.N <= DATEDIFF(day, @PeriodStart, @PeriodEnd)
        ) CD ON CD.ClockDate = CD.ClockDate
WHERE E.ID > 2000;

执行计划: https://www.brentozar.com/pastetheplan/?id=B139JjPKK

如您所见,根据优化器的说法,带有看似冗余谓词的第二个(左连接)查询的成本似乎比第一个(交叉连接)查询要低。当周期日期跨越多天时也是如此。

奇怪的是,如果我将 LEFT JOIN 的谓词更改为不同的东西,例如 1 = 1,它将像 CROSS APPLY 一样执行。我还尝试将 LEFT JOIN 的 SELECT 部分更改为 SELECT N 并加入 CD.N = CD.N ...但这似乎也表现不佳。

根据执行计划,第二个查询有一个索引查找,它只从 Numbers 表中读取 3000 行,而第一个查询是读取的 10 倍。第二个查询的索引搜索也有这个谓词(我假设它来自 LEFT JOIN):

dateadd(day,[Numbers].[N] as [N].[N],[@PeriodStart])=dateadd(day,[Numbers].[N] as [N].[N],[@PeriodStart])

我想了解为什么第二个查询似乎执行得这么好,即使我不会除此之外?这与我加入 DATEADD 函数的结果有关吗? SQL 是否在加入前评估 DATEADD 的结果?

【问题讨论】:

CD.ClockDate 是否为空? 您的主要问题似乎是表假脱机:您应该通过仅选择所需的行数来优化它CROSS JOIN (SELECT TOP (DATEDIFF(day, @PeriodStart, @PeriodEnd)) DATEADD(day, N.N, @PeriodStart) AS ClockDate FROM Numbers N ORDER BY N.N)。请将这两个查询计划上传到brentozar.com/pastetheplan,以便我们了解实际情况。 @CaiusJard 实际上,DATEADD 返回一个可为空的,参见小提琴dbfiddle.uk/…。正如您正确猜测的那样,这可能导致行估计值降低 作为“查询成本(相对于批次)”给出的百分比是估计值,而不是实际值。在您的第一个查询中,它预计要处理 633,000 行,而在第二个查询中,它预计只有 63,000 行。因此,它估计底部的工作要少得多,即使它可能是相同的。要正确测试,请尝试SET STATISTICS TIME, IO ON; THEN 运行查询并查看扫描次数、读取次数等进行比较。查看查询计划,我希望它们实际上是非常相似的工作量。 @Charlieface 这是计划:brentozar.com/pastetheplan/?id=B139JjPKK 我也使用您的优化运行它,它似乎比我的两个查询都执行得更好。谢谢! 【参考方案1】:

即使计划几乎相同并且可能需要相同的时间,这些查询得到不同估计的原因似乎是因为DATEADD(day, N.N, @PeriodStart) 可以为空,因此CD.ClockDate = CD.ClockDate 基本上只是验证结果不为空.优化器看不到它总是非空的,因此会降低行估计。


但在我看来,您查询中的主要性能问题是您每次都选择整个数字表。相反,您应该只选择所需的行数

SELECT E.FirstName, CD.ClockDate
FROM Employee E
    CROSS JOIN (
        SELECT TOP (DATEDIFF(day, @PeriodStart, @PeriodEnd) + 1)
            DATEADD(day, N.N, @PeriodStart) AS ClockDate
        FROM Numbers N
        ORDER BY N.N
    ) CD
WHERE E.ID > 2000;

使用这种技术,如果您想将行数与查询的其余部分相关联,您甚至可以使用 CROSS APPLY (SELECT TOP (outerValue)

有关数字表的更多提示,请参阅Itzik Ben-Gan's excellent series

【讨论】:

以上是关于带有冗余谓词的 LEFT JOIN 比 CROSS JOIN 执行得更好?的主要内容,如果未能解决你的问题,请参考以下文章

sql JOINs - JOIN,INNER JOIN,LEFT JOIN,RIGHT JOIN,CROSS JOIN

(', CROSS, FULL, INNER, JOIN, LEFT, NATURAL, ON, RIGHT 或 USING 预期,得到'WITH'

Left Outer Join 的条件谓词评估较晚,导致性能问题。甲骨文 8i

CROSS JOIN和INNER JOIN,LEFT JOIN,RIGHT JOIN,OUTER JOIN之间的区别[重复]

在 Redshift 上混合使用 CROSS JOIN 和 LEFT JOIN

MySQL的几种连接 join/inner join/cross join/逗号/left join/right join/natural join