当存在可信外键时,为什么 SQL Server 查询优化器不会将 OUTER JOIN 转换为 INNER JOIN?

Bry*_*bok 12 performance sql-server optimization query-performance

当第一个表中的连接列定义为 NOT NULL 并且对第二个表中的相应列具有受信任的外键约束时,SQL Server 查询优化器似乎不会将 OUTER JOIN 转换为 INNER JOIN。

在这种情况下,似乎可以将 OUTER JOIN 转换为等效的 INNER JOIN,因为第一个表中的每一行是:

  1. 保证在列中有一个值(NOT NULL 约束),和
  2. 保证在第二个表中有匹配的行(可信外键约束)。

例如,请考虑以下表格:

 CREATE TABLE dbo.tbl_fk
 (
    fk_val CHAR(1) NOT NULL PRIMARY KEY CLUSTERED,
    junk VARCHAR(100) NOT NULL
 );
 
 CREATE TABLE dbo.tbl_main
 (
    id INT NOT NULL PRIMARY KEY CLUSTERED IDENTITY(1,1),
    fk_val CHAR(1) NOT NULL FOREIGN KEY REFERENCES dbo.tbl_fk(fk_val)
 );
Run Code Online (Sandbox Code Playgroud)

在以下查询中,为什么优化器没有将执行计划中的 LEFT OUTER JOIN 转换为 INNER JOIN?

SELECT m.fk_val, f.junk
FROM dbo.tbl_main AS m
LEFT OUTER JOIN dbo.tbl_fk AS f
    ON m.fk_val = f.fk_val;
Run Code Online (Sandbox Code Playgroud)

但是,通过添加谓词以从第二个表中显式删除 NULL 值,优化器将查询计划中的 LEFT OUTER JOIN 转换为 INNER JOIN,如预期的那样:

SELECT m.fk_val, f.junk
FROM dbo.tbl_main AS m
LEFT OUTER JOIN dbo.tbl_fk AS f
    ON m.fk_val = f.fk_val
WHERE f.fk_val IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)

未记录的跟踪标志显示应用规则将查询中的 OUTER JOIN 更改为 INNER JOIN,其中谓词:

SELECT m.fk_val, f.junk
FROM dbo.tbl_main AS m
LEFT OUTER JOIN dbo.tbl_fk AS f
    ON m.fk_val = f.fk_val
WHERE f.fk_val IS NOT NULL
OPTION (QUERYTRACEON 3604, QUERYTRACEON 8621);

***** Rule applied: A LOJ B -> A JN B
Run Code Online (Sandbox Code Playgroud)

这些示例使用 SQL Server 2014 和 2019 进行了测试,使用了许多不同的兼容性级别,并且还使用了旧的和“新的”基数估计器。在所有情况下,行为似乎都相同。

Forrest McDaniel 指出(截至 2010 年),Sybase 似乎在其产品中内置了这样的转换(参见示例 1):http : //dcx.sybase.com/1200/en/dbusage/queryopt-sectb-5356466。 html

Dav*_*oft 7

我不确定查询转换没有发生的“充分理由”,除了它尚未实现。

您可以在查询中看到潜在的好处,例如

SELECT m.fk_val, count(f.junk)
FROM dbo.tbl_main AS m
LEFT OUTER JOIN dbo.tbl_fk AS f
    ON m.fk_val = f.fk_val
group by m.fk_val
Run Code Online (Sandbox Code Playgroud)

如果是 INNER JOIN,则可以在加入之前推送聚合。

SELECT m.fk_val, count(f.junk)
FROM dbo.tbl_main AS m
INNER JOIN dbo.tbl_fk AS f
    ON m.fk_val = f.fk_val
group by m.fk_val
Run Code Online (Sandbox Code Playgroud)

但在外列转换OUTER JOIN + Predicate的情况下,外列谓词要么完全消除,要么可以推送到join之前。

EG,与

SELECT m.fk_val, f.junk
FROM dbo.tbl_main AS m
LEFT OUTER JOIN dbo.tbl_fk AS f
    ON m.fk_val = f.fk_val
WHERE f.fk_val IS NOT NULL
Run Code Online (Sandbox Code Playgroud)

此查询在逻辑上等同于更简单的查询

SELECT m.fk_val, f.junk
FROM dbo.tbl_main AS m
INNER JOIN dbo.tbl_fk AS f
    ON m.fk_val = f.fk_val
Run Code Online (Sandbox Code Playgroud)

它少了一个谓词。

如果您尝试在连接之前推送外部表谓词而不将其更改为 INNER JOIN,您仍然必须在连接之后应用该谓词。

对于类似的东西

SELECT m.fk_val, m.Id
FROM dbo.tbl_main AS m
LEFT OUTER JOIN dbo.tbl_fk AS f
    ON m.fk_val = f.fk_val
WHERE f.junk = 'a';
Run Code Online (Sandbox Code Playgroud)

如果f.junk = 'a'在连接之前应用谓词,则会在 JOIN 之前过滤掉一些行。但是随后与外部行对应的内部行将在外部f.junk <> 'a'联接中幸存下来,并且您必须在联接f.junk = 'a'后再次申请。

  • “因此没有性能优势” - 在我的测试中,当使用基本的 OUTER JOIN 查询(​​例如添加一个 GROUP BY)组成一个更复杂的查询时,优化器会做出不同的决定 - 不同的连接顺序,不同的连接和聚合运算符等。在这种情况下,基于 CPU 时间和运行时间,使用 INNER JOIN 的 GROUP BY 查询比使用 OUTER JOIN(没有谓词)的等效 GROUP BY 查询更有效。使用带有“IS NOT NULL”谓词的 OUTER JOIN 的 GROUP BY 查询等效于使用 INNER JOIN 的 GROUP BY 查询。 (3认同)