普通的SELECT查询计划中的“恒定扫描”和“左外部联接”来自何处?


21

我有这张桌子:

CREATE TABLE [dbo].[Accounts] (
    [AccountId] UNIQUEIDENTIFIER UNIQUE NOT NULL DEFAULT NEWID(),
    -- WHATEVER other columns
);
GO
CREATE UNIQUE CLUSTERED INDEX [AccountsIndex]
    ON [dbo].[Accounts]([AccountId] ASC);
GO

该查询:

DECLARE @result UNIQUEIDENTIFIER
SELECT @result = AccountId FROM Accounts WHERE AccountId='guid-here'

使用包含单个“索引搜索”的查询计划执行-符合预期:

SELECT <---- Clustered Index Seek

该查询的作用相同:

DECLARE @result UNIQUEIDENTIFIER
SET @result = (SELECT AccountId FROM Accounts WHERE AccountId='guid-here')

但是它是通过以下计划执行的:将“索引搜索”的结果与某些“恒定扫描”的结果进行“左外连接”,然后馈入“计算标量”:

SELECT <--- Compute Scalar <--- Left Outer Join <--- Constant Scan
                                      ^
                                      |------Clustered Index Seek

那额外的魔力是什么?持续扫描后跟左外部联接有什么作用?

Answers:


29

这两个语句的语义不同:

  • 如果找不到行,则第一个不设置变量的值。
  • 第二个总是设置变量,如果找不到行,则将其设置为null。

常量扫描会产生一个空行(无列!),如果基表中没有匹配项,则将导致变量被更新。左联接确保空行在联接中幸存。可以认为变量分配发生在执行计划的根节点。

使用 SELECT @result

-- Set initial value
DECLARE @result uniqueidentifier = {guid 'FE2CA909-1162-4C6C-A7AC-33B257E28539'};

-- @result does not change
SELECT @result = AccountId 
FROM Accounts 
WHERE AccountId={guid '7AD4D33C-1ED7-4183-B7F3-48C33D666525'};

SELECT @result;

结果1

使用 SET @result

-- Set initial value
DECLARE @result uniqueidentifier = {guid 'FE2CA909-1162-4C6C-A7AC-33B257E28539'};

-- @result set to null
SET @result = 
(
    SELECT AccountId 
    FROM Accounts 
    WHERE AccountId={guid '7AD4D33C-1ED7-4183-B7F3-48C33D666525'}
);

SELECT @result;

结果2

执行计划

SELECT分配没有行到达根节点,因此不会发生分配。

SET分配行总是到达根节点,因此发生变量分配。


多余的“恒定扫描”和“嵌套循环左外连接”无需担心。联接特别便宜,因为它保证在外部输入上遇到一行,而在内部输入上最多遇到一行(在您的示例中)。

还有其他方法可确保从子查询生成行以确保发生变量分配。一种是使用冗余的标量聚合(没有group by子句):

-- Set initial value
DECLARE @result uniqueidentifier = {guid 'FE2CA909-1162-4C6C-A7AC-33B257E28539'};

-- @result set to null
SET @result = 
    (
        SELECT MAX(AccountId)
        FROM Accounts 
        WHERE AccountId={guid '7AD4D33C-1ED7-4183-B7F3-48C33D666525'} 
    );
SELECT @result;

结果3

标量汇总执行计划

请注意,即使标量聚合没有收到输入,它也会产生一行。

说明文件:

如果SELECT语句不返回任何行,则该变量将保留其当前值。如果expression是不返回值的标量子查询,则该变量将设置为NULL。

对于分配变量,我们建议您使用SET @local_variable而不是SELECT @local_variable。

进一步阅读:

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.