【问题标题】:Why there is a sort operator in execution plan when EXCEPTed queries are clustered tables?当 EXCEPTed 查询是聚集表时,为什么执行计划中有排序运算符?
【发布时间】:2019-09-02 09:24:58
【问题描述】:

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

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. 进行表格比较的更好方法(如果有的话)有哪些?

提前感谢您的回答:)

【问题讨论】:

  • 不是图片,而是将实际执行计划xml上传到Paste The Plan。为避免使用排序运算符,聚集索引应位于SELECT 子句中列出的所有列上,以便提供合并运算符所需的排序。
  • 这是一个粘贴到实际执行计划的计划链接:brentozar.com/pastetheplan/?id=Hk35rw9BB
  • 数据已排序。它在第一列排序。考虑:(1, a, banana) EXCEPT (1, z, pineapple)。考虑第一列就足够了吗?不可以。如果您只在第一列 EXCEPT 然后再次查找其他数据,则可以在不排序的情况下获得 EXCEPT
  • @dominjas,如果聚集索引键是唯一的,您可以尝试使用NOT EXISTS 而不是EXCEPT。在这种情况下,也将聚集索引声明为唯一的。
  • @dominjas,感谢分享。我认为你会得到与 NOT EXISTS 相同的计划,因为 SQL Server 理解唯一索引然后使查询在语义上相同。

标签: sql sql-server indexing sql-except


【解决方案1】:

回答您的问题排序还不够吗? - 不。比较是在SELECT 中的所有列上执行的,因此所有列都需要包含在排序中。

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

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 并不完全相同,也有效地执行DISTINCT。如果您将 DISTINCT 添加到 NOT EXISTS,您将在较小的结果表上获得 SORT。

在此处查看 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

【讨论】:

  • 那么,除了列之外的所有列都必须在合并连接之前显式排序?在这种情况下,如果没有实际的排序操作,优化器就无法知道某些内容是否已排序?
  • @dominjas 查看更新的答案,显示如何使用 NOT EXISTS 来实现不排序的结果
  • 感谢您的回答和示例,但是有一种方法可以实现我的目标,除了 key_a/key_b 列作为索引键。我只需要更改索引 - 添加 UNIQUE。在那之后,没有任何计划。请参阅我的问题下的 cmets 以及:sqlfiddle.com/#!18/a3f16/1
【解决方案2】:

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

  1. 为什么在这种情况下聚集索引扫描后还有排序运算符?

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

  1. 当我想使用 EXCEPT 运算符时,如何避免这些类型?

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

  1. 进行表格比较的更好方法(如果有的话)有哪些?

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-12
    • 1970-01-01
    • 2020-04-15
    • 1970-01-01
    相关资源
    最近更新 更多