对分区表查询的错误估计

Als*_*sin 7 sql-server partitioning cardinality-estimates

我想知道为什么 SQL Server 在如此简单的情况下会做出错误的估计。有一个场景。

CREATE PARTITION FUNCTION PF_Test (int) AS RANGE RIGHT 
FOR VALUES (20140801, 20140802, 20140803)

CREATE PARTITION SCHEME PS_Test AS PARTITION PF_Test ALL TO ([Primary])

CREATE TABLE A
(
  DateKey int not null,
  Type int not null,
  constraint PK_A primary key (DateKey, Type) on PS_Test(DateKey)
)

INSERT INTO A (DateKey, Type)
SELECT
  DateKey = N1.n  + 20140801,
  Type = N2.n + 1
FROM dbo.Numbers N1
  cross join dbo.Numbers N2
WHERE N1.n BETWEEN 0 AND 2
  and N2.n BETWEEN 0 AND 10000 - 1

UPDATE STATISTICS A (PK_A) WITH FULLSCAN, INCREMENTAL = ON

CREATE TABLE B
(
  DateKey int not null,
  SubType int not null,
  Type int not null,
  constraint PK_B primary key (DateKey, SubType) on PS_Test(DateKey)
)

INSERT INTO B (DateKey, SubType, Type)
SELECT
  DateKey,
  SubType = Type * 10000 + N.n,
  Type
FROM A
  cross join dbo.Numbers N
WHERE N.n BETWEEN 1 AND 10

UPDATE STATISTICS B (PK_B) WITH FULLSCAN, INCREMENTAL = ON
Run Code Online (Sandbox Code Playgroud)

所以设置非常简单,统计信息就位,当我们查询一张表时,SQL Server 可以产生正确的估计。

select COUNT(*) from A where DateKey = 20140802
--10000
select COUNT(*) from B where DateKey = 20140802
--100000
Run Code Online (Sandbox Code Playgroud)

但是在这个简单的选择中,估计是很遥远的,我看不出为什么。

SELECT a.DateKey, a.Type
FROM A
  JOIN B
    ON b.DateKey = a.DateKey
    AND b.Type = a.Type
WHERE a.DateKey = 20140802
Run Code Online (Sandbox Code Playgroud)

执行计划

在聚集索引搜索之后,估计与实际相差 57%!现实世界的查询更糟,估计与实际相差 2%。

PS 数字表重现设置

DECLARE @UpperBound INT = 1000000;

;WITH cteN(Number) AS
(
  SELECT ROW_NUMBER() OVER (ORDER BY s1.[object_id]) - 1
  FROM sys.all_columns AS s1
  CROSS JOIN sys.all_columns AS s2
)
SELECT n = [Number] INTO dbo.Numbers
FROM cteN WHERE [Number] <= @UpperBound;

CREATE UNIQUE CLUSTERED INDEX CIX_Number ON dbo.Numbers(n)
WITH 
(
  FILLFACTOR = 100,      -- in the event server default has been changed
  DATA_COMPRESSION = ROW -- if Enterprise & table large enough to matter
);
Run Code Online (Sandbox Code Playgroud)

PPS 相同的场景但非分区工作完美。

Pau*_*ite 9

估计(使用新的基数估计器)适用于普通连接,但在优化器考虑colocated join选项时不太准确。

当连接以相同方式分区的两个表时,可以使用并置连接(又名按分区连接)。这个想法是一次加入一个分区,使用嵌套循环应用由恒定扫描(内存中的值表)提供的分区 ID 驱动。

定期加入

由于并置连接涉及嵌套循环应用,您可以通过指定OPTION (HASH JOIN)例如强制优化器避免这种情况:

强制散列连接的计划

该计划中的两个目标是:

Seek Keys[1]: Prefix:
    PtnId1000, [dbo].[A].DateKey = Scalar Operator((3)), Scalar Operator((20140802))
Seek Keys[1]: Prefix:
    PtnId1003, [dbo].[B].DateKey = Scalar Operator((3)), Scalar Operator((20140802))
Run Code Online (Sandbox Code Playgroud)

优化器在这两种情况下都应用了静态分区消除,为两种搜索和以下连接提供了准确的估计。

协同连接

当优化器考虑并置连接(如问题所示)时,搜索是:

同地加入计划

Seek Keys[1]: Prefix:
    PtnId1000, [dbo].[A].DateKey = Scalar Operator([Expr1006]), Scalar Operator((20140802))
Seek Keys[1]: Prefix:
    PtnId1003, [dbo].[B].DateKey = Scalar Operator([Expr1006]), Scalar Operator((20140802))
Run Code Online (Sandbox Code Playgroud)

...其中[Expr1006]是 Constant Scan 运算符返回的值。

基数估计器现在看不到DateKey值和分区 id 是相互依赖的,就像使用文字常量时一样。换句话说,对于估计器来说,内部的值[Expr1006]指定与 相同的分区并不明显DateKey = 20140802

因此,CE 选择(默认情况下)使用正常指数退避方法来估计两个(显然独立的)谓词的选择性。

这解释了馈送连接的减少的基数估计。此选项的较低明显成本(由于错误估计)意味着优化器选择并置连接而不是常规连接,即使很明显(对人类而言)它没有提供任何价值。

有几种方法可以解决逻辑中的这个差距,包括使用查询提示USE HINT ('ASSUME_MIN_SELECTIVITY_FOR_FILTER_ESTIMATES'),但这将影响整个查询,而不仅仅是有问题的并置连接替代方案。正如 Erik 在他的回答中指出的那样,您还可以暗示使用旧版 CE。

有关并置连接的更多信息,请参阅我的文章提高分区表连接性能


Eri*_*ing 5

这似乎是由于 SQL Server 2014 中引入的新基数估计器所致。

如果您指示查询使用旧的,您会得到不同的计划和正确的估计。

SELECT a.DateKey, a.Type
FROM A AS a
  JOIN B AS b
    ON b.DateKey = a.DateKey
    AND b.Type = a.Type
WHERE a.DateKey = 20140802
OPTION(USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'));
Run Code Online (Sandbox Code Playgroud)

坚果

有关更多信息,请参阅这些链接: