为什么 EXCEPTed 查询是聚簇表时执行计划中有排序运算符?

Why there is a sort operator in execution plan when EXCEPTed queries are clustered tables?

我正在构建一个数据仓库,我发现两个 tables 数据比较语句有问题。我使用 EXCEPT 运算符来比较具有聚集索引(普通 int 字段作为键)的 tables。我的问题是在查询执行计划中,在两个聚集索引扫描之后都有排序运算符。 这是一个代码示例:

create table temp.table_a
(
    key_a int identity,
    some_field_a int,
    some_field2_a varchar(10)
);

insert into temp.table_a
(
    some_field_a,
    some_field2_a
)
select
    n,
    'abcd'
from meta.GENERATE_SEQUENCE(1,1000);

create clustered index cix_table_a_key_a on temp.table_a (key_a);


create table temp.table_b
(
    key_b int identity,
    some_field_b int,
    some_field2_b varchar(10)
);

insert into temp.table_b
(
    some_field_b,
    some_field2_b
)
select 
    n,
    'abcd'
from meta.GENERATE_SEQUENCE(1,1000);

create clustered index cix_table_b_key_b on temp.table_b (key_b);

(GENERATE_SEQUENCE 是行生成器)

现在 EXCEPT 查询:

select 
    key_a,
    some_field_a,
    some_field2_a
from temp.table_a

except

select 
    key_b,
    some_field_b,
    some_field2_b
from temp.table_b

这是执行计划的图片:

我知道 Merge Join 需要排序的输入,但它不是已经排序了吗?我的意思是,我们需要的唯一排序列是 key_a/key_b。由于聚簇索引,这已经完成了。不需要对其他列进行排序,因为在 key_a/key_b 的每个值中只有一行 - 无需排序。

所以,我的问题是:

  1. 为什么这种情况下聚簇索引扫描后还有排序运算符?
  2. 当我想使用 EXCEPT 运算符时,如何避免这些排序?
  3. 进行table比较的更好方法是什么(如果有的话)?

提前感谢您的回答:)

回答你的问题它还不够排序吗? - 没有。比较是在 SELECT 中的所有列上进行的,因此所有列都需要被列入排序。

我提供了 2 种可能的解决方案,一种是将所有列添加到索引中,另一种是使用 NOT EXISTS - 请注意,如果 table_a 中存在重复,这可能会 return 重复行.

1.) 在索引中包含这些列,您将在 SQLFiddle 链接查询计划中看到在这种情况下没有使用排序。

SQL Fiddle

MS SQL Server 2017 架构设置:

create table table_a
(
    key_a int identity,
    some_field_a int,
    some_field2_a varchar(10)
);

WITH Tally (n) AS
(
    -- 1000 rows
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
)
insert into table_a
(
    some_field_a,
    some_field2_a
)
select
    n,
    'abcd'
from Tally;

create clustered index cix_table_a_key_a on table_a (key_a,some_field_a,
    some_field2_a);


create table table_b
(
    key_b int identity,
    some_field_b int,
    some_field2_b varchar(10)
);

WITH Tally (n) AS
(
    -- 1000 rows
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
)
insert into table_b
(
    some_field_b,
    some_field2_b
)
select 
    n,
    'abcd'
from Tally;

create clustered index cix_table_b_key_b on table_b (key_b, some_field_b, some_field2_b);

查询 1:

select 
    key_a,
    some_field_a,
    some_field2_a
from table_a

except

select 
    key_b,
    some_field_b,
    some_field2_b
from table_b

Results:

2.) 另一种选择是使用 NOT EXISTS 而不是 EXCEPT - 应该注意的是,这些与 EXCEPT 有效执行 [=18] 并不完全相同=] 也是。如果将 DISTINCT 添加到 NOT EXISTS,您将对较小的结果进行排序 table。

请在此处查看 SQLFIDLLE:

SQL Fiddle

MS SQL Server 2017 架构设置:

create table table_a
(
    key_a int identity,
    some_field_a int,
    some_field2_a varchar(10)
);

WITH Tally (n) AS
(
    -- 1000 rows
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
)
insert into table_a
(
    some_field_a,
    some_field2_a
)
select
    n,
    'abcd'
from Tally;

create clustered index cix_table_a_key_a on table_a (key_a);


create table table_b
(
    key_b int identity,
    some_field_b int,
    some_field2_b varchar(10)
);

WITH Tally (n) AS
(
    -- 1000 rows
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
)
insert into table_b
(
    some_field_b,
    some_field2_b
)
select 
    n,
    'abcd'
from Tally;

create clustered index cix_table_b_key_b on table_b (key_b);

查询 1:

select 
    key_a,
    some_field_a,
    some_field2_a
from table_a
WHERE NOT EXISTS
(
  select NULL 
  from table_b
  WHERE
    key_b = key_a AND
    some_field_b = some_field_a AND
    some_field2_b = some_field2_a
)

Results:

感谢大家的帮助。以下是我从评论和答案中收集到的问题的完整答案:

  1. Why there are sort operators after clustered index scans in this situation?

存在排序运算符是因为索引键列(key_a、key_b)对于优化器而言不是唯一的。

  1. How can I avoid these sorts when I want to use EXCEPT operator?

确保您的索引键列是唯一的 - 使用 UNIQUE CLUSTERED INDEX 或对这些字段设置约束。

  1. What are the better ways (if there is any) of doing table comparison?

Steve Ford (@steve-ford) 的回答中给出了考虑向索引键添加更多列或使用 NOT EXISTS 而不是 EXCEPT 的替代解决方案。