Sql-Server
在一個簡單的 SELECT 查詢計劃中,這個 Constant Scan 和 Left Outer Join 來自哪裡?
我有這張桌子:
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;
使用
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;
執行計劃
額外的常量掃描和嵌套循環左外連接無需擔心。特別是連接很便宜,因為它保證在其外部輸入上遇到一行,並且在內部輸入上最多遇到一行(在您的範例中)。
還有其他方法可以確保從子查詢生成行以確保發生變數分配。一種是使用冗餘標量聚合(無 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;
請注意,即使沒有接收到輸入,標量聚合也會生成一行。
文件:
如果 SELECT 語句沒有返回任何行,則變數保留其目前值。如果表達式是不返回值的標量子查詢,則變數設置為 NULL。
對於分配變數,我們建議您使用 SET @local_variable 而不是 SELECT @local_variable。
進一步閱讀: