带有冗余谓词的 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