微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

sql-server – 这个Constant Scan和Left Outer Join来自一个简单的SELECT查询计划?

我有这张桌子:
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;

执行计划

没有行到达根节点,因此不会发生任何分配.

A行始终到达根节点,因此发生变量赋值.

额外的恒定扫描和嵌套循环左外连接无需关注.特别是连接是便宜的,因为它保证在其外部输入上遇到一行,并且在内部输入上最多只有一行(在您的示例中).

还有其他方法可以确保从子查询生成行以确保发生变量赋值.一种是使用冗余标量聚合(没有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;

请注意,标量聚合生成一行,即使它没有收到任何输入.

文档:

> SET @local_variable (Transact-SQL)
> SELECT @local_variable (Transact-SQL)

If the SELECT statement returns no rows,the variable retains its present value. If expression is a scalar subquery that returns no value,the variable is set to NULL.

For assigning variables,we recommend that you use SET @local_variable instead of SELECT @local_variable.

进一步阅读:

Fun with Aggregates

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐