具有冗余谓词的 LEFT JOIN 比 CROSS JOIN 执行得更好?
LEFT JOIN With Redundant Predicate Performs Better Than a CROSS JOIN?
我正在查看其中两个语句的执行计划,我有点困惑为什么 LEFT JOIN
语句比 CROSS JOIN
语句执行得更好:
Table 定义:
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.
问题中的查询,我向每个员工加入一个 'day':
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 table 中读取 3000 行,而第一个查询的读取次数是其 10 倍。第二个查询的索引查找也有这个谓词(我假设它来自 LEFT JOIN):
dateadd(day,[Numbers].[N] as [N].[N],[@PeriodStart])=dateadd(day,[Numbers].[N] as [N].[N],[@PeriodStart])
我想了解为什么第二个查询似乎执行得更好,即使我不会例外?这与我加入 DATEADD 函数的结果有关吗? SQL 是否在加入之前评估 DATEADD 的结果?
这些查询得到不同估计的原因,即使计划几乎相同并且可能花费相同的时间,似乎是因为 DATEADD(day, N.N, @PeriodStart)
可以为空,因此 CD.ClockDate = CD.ClockDate
基本上只是验证结果不为空。优化器看不到它始终为非空,因此因此降低了行估计值。
但在我看来,您查询中的主要性能问题是您每次都select查询整个数字table。相反,您应该 select 您需要的行数
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)
。
有关数字 table 的更多提示,请参阅 Itzik Ben-Gan's excellent series
我正在查看其中两个语句的执行计划,我有点困惑为什么 LEFT JOIN
语句比 CROSS JOIN
语句执行得更好:
Table 定义:
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.
问题中的查询,我向每个员工加入一个 'day':
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 table 中读取 3000 行,而第一个查询的读取次数是其 10 倍。第二个查询的索引查找也有这个谓词(我假设它来自 LEFT JOIN):
dateadd(day,[Numbers].[N] as [N].[N],[@PeriodStart])=dateadd(day,[Numbers].[N] as [N].[N],[@PeriodStart])
我想了解为什么第二个查询似乎执行得更好,即使我不会例外?这与我加入 DATEADD 函数的结果有关吗? SQL 是否在加入之前评估 DATEADD 的结果?
这些查询得到不同估计的原因,即使计划几乎相同并且可能花费相同的时间,似乎是因为 DATEADD(day, N.N, @PeriodStart)
可以为空,因此 CD.ClockDate = CD.ClockDate
基本上只是验证结果不为空。优化器看不到它始终为非空,因此因此降低了行估计值。
但在我看来,您查询中的主要性能问题是您每次都select查询整个数字table。相反,您应该 select 您需要的行数
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)
。
有关数字 table 的更多提示,请参阅 Itzik Ben-Gan's excellent series