Sql-Server

在一個簡單的 SELECT 查詢計劃中,這個 Constant Scan 和 Left Outer Join 來自哪裡?

  • November 27, 2019

我有這張桌子:

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')

但它是通過一個計劃執行的,其中 Index Seek 的結果與一些常量掃描的結果在外部連接,然後輸入到 Compute Scalar:

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

那額外的魔法是什麼?常量掃描後跟左外連接有什麼作用?

兩種語句的語義不同:

  • 如果沒有找到行,第一個不設置變數的值。
  • 第二個總是設置變數,包括 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

執行計劃

選擇分配沒有行到達根節點,因此不會發生分配。

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 語句沒有返回任何行,則變數保留其目前值。如果表達式是不返回值的標量子查詢,則變數設置為 NULL。

對於分配變數,我們建議您使用 SET @local_variable 而不是 SELECT @local_variable。

進一步閱讀:

引用自:https://dba.stackexchange.com/questions/136210