奇异的密度导致抽样统计


8

通过采样与全扫描进行估算时,NC指数获得完全不同的统计分布;采样的样本具有奇异的密度向量。这导致执行计划不佳。


我有一个约2700万行的表,并且非聚集索引支持一个非null的FK列。该表聚集在其主键上。两列都是varchar。

FK列的全扫描统计信息更新给出了正常外观的密度矢量:

All density Average Length  Columns
6,181983E-08    45,99747    INSTANCEELEMENTID
3,615442E-08    95,26874    INSTANCEELEMENTID, ID

也就是说,我们希望与之INSTANCELEMENTID连接的每个不重复读取约1.7行。

直方图中的典型bin如下所示:

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
FOOBAR          133053      10      71366               1,679318

但是,如果我们进行了抽样更新(使用此表的默认样本数为23万行),那么情况就变得奇怪了:

4,773657E-06    45,99596    INSTANCEELEMENTID
3,702179E-08    95,30183    INSTANCEELEMENTID, ID

现在的密度INSTANCEELEMENTID要大两个数量级。(但是,两根色谱柱的密度均已估算为可以接受的值)。

现在,直方图中的典型bin如下所示:

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS     DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
FOOBAR          143870,4    766,2573    1247                115,3596
ZOTZOT          131560,7    1           969                 135,7092

这是完全不同的分布。请注意,INSTANCEELEMENTID与关联的IDs 的最大数目为12,最常见的数字为1。有些bin的EQ_ROWS = 1也很奇怪,这大约占bin的10%。

没有“不幸”的奇怪行吸引人。

我是否正确读取直方图?看起来抽样是否以某种方式错误地缩放了EQ_ROWS,DISTINCT_RANGE_ROWS和AVG_RANGE_ROWS?

据我所知,桌子没有歪斜。我试图通过自己估算值来模仿采样器tablesample。以正常方式对这些结果进行计数得出的结果与全扫描版本(而不是采样器)一致。

此外,我无法在聚集索引上重现此行为。


我将范围缩小到可以复制:

CREATE TABLE F_VAL (
    id varchar(100) primary key,
    num_l_val int not null
)

set nocount on

declare @rowlimit integer = 20000000;

该表必须足够大才能被观察到。我已经看到了uniqueidentifervarchar(100)但没有int

declare @i integer = 1;

declare @r float = rand()

while @i < @rowlimit
begin
set @r = rand()
insert f_val (id,num_l_val)
values (
   cast(@i as varchar(100)) + REPLICATE('f', 40 - len(@i)),
   case when @r > 0.8 then 4 when @r > 0.5 then 3 when @r > 0.4 then 2 else 1 end
)
  set @i = @i + 1

end

create table k_val (
 id int identity primary key,
 f_val varchar(100) not null,
)

insert into k_val(f_val)
select id from F_VAL
union all select id from f_val where num_l_val - 1 = 1
union all select id from f_val where num_l_val - 2 = 1
union all select id from f_val where num_l_val - 3 = 1
order by id

create nonclustered index IX_K_VAL_F_VAL  ON K_VAL (F_VAL)

update statistics K_VAL(IX_K_VAL_F_VAL) 
dbcc show_statistics (k_val,IX_k_VAL_F_VAL)

update statistics K_VAL(IX_K_VAL_F_VAL) WITH FULLSCAN
dbcc show_statistics (k_val,IX_k_VAL_F_VAL)

比较两个统计数据;现在,带有采样的那个表示总的不同密度矢量,并且直方图箱已关闭。请注意该表不倾斜。

使用intas数据类型不会导致此问题,使用SQL Server时是否不检查整个数据点varchar

值得一提的是,问题似乎在扩大,增加采样率会有所帮助。

Answers:


3

我已经在我可以访问的最大数据库的某些非聚集索引上看到了相同的密度问题。首先,我将从对直方图和密度计算所做的一些观察开始:

  • SQL Server能够使用表上的主键来推断有关两列密度的信息。这意味着包括PK列的密度通常会非常准确。
  • 统计信息中第一列的密度计算与直方图一致。如果直方图不能很好地对数据建模,则密度可能会关闭。
  • 为了创建直方图,该StatMan函数对丢失的数据进行推断。该行为可以更改,具体取决于列的数据类型。

解决问题的一种方法是,假设您从10000行表中采样了100行,并获得100个不同的值。对表中其余数据的猜测是,有10000个唯一值。另一个猜测是有100个不同的值,但每个值都重复了100次。第二个猜测对您来说似乎不合理,我会同意。但是,当采样数据返回的分布不均匀时,如何平衡这两种方法?StatMan函数中包含Microsoft为此目的开发的一组算法。该算法可能不适用于所有数据中断和所有样本级别。

让我们来看一个相对简单的示例。我将使用VARCHAR表中的列来查看某些相同的行为。但是,我只向表添加一个偏斜值。我正在针对SQL Server 2016 SP1进行测试。从100k行开始,该列具有100k唯一值FK

DROP TABLE IF EXISTS X_STATS_SMALL;

CREATE TABLE X_STATS_SMALL (
ID VARCHAR(10) NOT NULL, 
FK VARCHAR(10) NOT NULL,
PADDING VARCHAR(900) NOT NULL,
PRIMARY KEY (ID)
);
-- insert 100k rows
INSERT INTO X_STATS_SMALL WITH (TABLOCK)
SELECT N, N, REPLICATE('Z', 900)
FROM dbo.GetNums(100000);

CREATE INDEX IX_X_STATS_SMALL ON X_STATS_SMALL (FK);

-- get sampled stats
UPDATE STATISTICS X_STATS_SMALL IX_X_STATS_SMALL;

以下是统计数据中的一些示例:

╔═════════════╦════════════════╦═════════╗
 All density  Average Length  Columns 
╠═════════════╬════════════════╬═════════╣
 1.00001E-05  4.888205        FK      
 1.00001E-05  9.77641         FK, ID  
╚═════════════╩════════════════╩═════════╝

╔══════════════╦════════════╦═════════╦═════════════════════╦════════════════╗
 RANGE_HI_KEY  RANGE_ROWS  EQ_ROWS  DISTINCT_RANGE_ROWS  AVG_RANGE_ROWS 
╠══════════════╬════════════╬═════════╬═════════════════════╬════════════════╣
 1005          0           1        0                    1              
 10648         665.0898    1        664                  1.002173       
 10968         431.6008    1        432                  1              
 11182         290.0924    1        290                  1              
 1207          445.7517    1        446                  1              
 ...           ...         ...      ...                  ...            
 99989         318.3941    1        318                  1              
╚══════════════╩════════════╩═════════╩═════════════════════╩════════════════╝

对于每行只有一个唯一值的均匀分布的数据,即使具有VARCHAR直方图列和14294行的样本量,我们也可以获得准确的密度。

现在,我们添加一个偏斜的值并再次更新统计信息:

-- add 70k rows with a FK value of '35000'
INSERT INTO X_STATS_SMALL WITH (TABLOCK)
SELECT N + 100000 , '35000',  REPLICATE('Z', 900)
FROM dbo.GetNums(70000);

UPDATE STATISTICS X_STATS_SMALL IX_X_STATS_SMALL;

样本大小为17010行时,第一列的密度小于应有的值:

╔══════════════╦════════════════╦═════════╗
 All density   Average Length  Columns 
╠══════════════╬════════════════╬═════════╣
 6.811061E-05  4.935802        FK      
 5.882353E-06  10.28007        FK, ID  
╚══════════════╩════════════════╩═════════╝

╔══════════════╦════════════╦══════════╦═════════════════════╦════════════════╗
 RANGE_HI_KEY  RANGE_ROWS  EQ_ROWS   DISTINCT_RANGE_ROWS  AVG_RANGE_ROWS 
╠══════════════╬════════════╬══════════╬═════════════════════╬════════════════╣
 10039         0           1         0                    1              
 10978         956.9945    1         138                  6.954391       
 11472         621.0283    1         89                   6.941863       
 1179          315.6046    1         46                   6.907561       
 11909         91.62713    1         14                   6.74198        
 ...           ...         ...       ...                  ...            
 35000         376.6893    69195.05  54                   6.918834       
 ...           ...         ...       ...                  ...            
 99966         325.7854    1         47                   6.909731       
╚══════════════╩════════════╩══════════╩═════════════════════╩════════════════╝

令人惊讶的AVG_RANGE_ROWS是,所有步骤在6.9左右都非常均匀,即使对于示例无法找到重复值的键桶也是如此。我不知道为什么会这样。最可能的解释是,用于猜测缺失页面的算法在此数据分布和样本大小上效果不佳。

如前所述,可以使用直方图计算FK列的密度。DISTINCT_RANGE_ROWS所有步骤的值总和为14497。共有179个直方图步骤,因此密度应约为1 /(179 + 14497)= 0.00006813845,这与报告的值非常接近。

用更大的表进行测试可以显示随着表的增大问题如何变得更糟。这次我们将从一百万行开始:

DROP TABLE IF EXISTS X_STATS_LARGE;

CREATE TABLE X_STATS_LARGE (
ID VARCHAR(10) NOT NULL,
FK VARCHAR(10) NOT NULL,
PADDING VARCHAR(900) NOT NULL,
PRIMARY KEY (ID));

INSERT INTO X_STATS_LARGE WITH (TABLOCK)
SELECT N, N, REPLICATE('Z', 900)
FROM dbo.Getnums(1000000);

CREATE INDEX IX_X_STATS_LARGE ON X_STATS_LARGE (FK);

-- get sampled stats
UPDATE STATISTICS X_STATS_LARGE IX_X_STATS_LARGE;

统计对象还不有趣。的密度FK是1.025289E-06,接近精确的(1.0E-06)。

现在,我们添加一个偏斜的值并再次更新统计信息:

INSERT INTO X_STATS_LARGE WITH (TABLOCK)
SELECT N + 1000000 , '350000',  REPLICATE('Z', 900)
FROM dbo.Getnums(700000);

UPDATE STATISTICS X_STATS_LARGE IX_X_STATS_LARGE;

对于45627行的样本量,第一列的密度比以前差:

╔══════════════╦════════════════╦═════════╗
 All density   Average Length  Columns 
╠══════════════╬════════════════╬═════════╣
 2.60051E-05   5.93563         FK      
 5.932542E-07  12.28485        FK, ID  
╚══════════════╩════════════════╩═════════╝

╔══════════════╦════════════╦═════════╦═════════════════════╦════════════════╗
 RANGE_HI_KEY  RANGE_ROWS  EQ_ROWS  DISTINCT_RANGE_ROWS  AVG_RANGE_ROWS 
╠══════════════╬════════════╬═════════╬═════════════════════╬════════════════╣
 100023        0           1        0                    1              
 107142        8008.354    1        306                  26.17787       
 110529        4361.357    1        168                  26.02392       
 114558        3722.193    1        143                  26.01217       
 116696        2556.658    1        98                   25.97568       
 ...           ...         ...      ...                  ...            
 350000        5000.522    700435   192                  26.03268       
 ...           ...         ...      ...                  ...            
 999956        2406.266    1        93                   25.96841       
╚══════════════╩════════════╩═════════╩═════════════════════╩════════════════╝

AVG_RANGE_ROWS最多可以达到26个。有趣的是,如果我将样本大小更改为170100行(另一张表的10倍),则平均值AVG_RANGE_ROWS将再次接近6.9。随着表的增大,SQL Server将选择较小的样本大小,这意味着它需要对表中更大比例的页面进行猜测。这会夸大某些数据偏斜的统计问题。

总之,重要的是要记住,SQL Server不会像这样计算密度:

SELECT COUNT(DISTINCT FK) * 1700000. / COUNT(*) -- 1071198.9 distinct values for one run
FROM X_STATS_LARGE TABLESAMPLE (45627 ROWS);

对于某些数据分布而言,这将是非常准确的。相反,它使用未公开的算法。在您的问题中,您说过您的数据没有偏斜,但是INSTANCEELEMENTID关联ID数量最多的值是12,最常见的数字是1 Statman

那时,除了收集具有更高采样率的统计信息外,您无能为力。一种常见的策略是使用FULLSCAN和收集统计信息NORECOMPUTE。您可以在适合您的数据更改速率的任何时间间隔内通过作业刷新统计信息。以我的经验,FULLSCAN更新并不像大多数人想象的那么糟糕,尤其是对索引而言。SQL Server可以只扫描整个索引而不是整个表(就像针对未索引列的行存储表一样)。此外,在SQL Serer 2014中,仅FULLSCAN统计信息更新是并行完成的,因此,FULLSCAN与某些示例更新相比,更新可以更快地完成。


感谢您的回复,乔!这似乎是一个错误或功能差距;请记住,当您使用基于INT的值时,不会发生此行为。在INT上,系统工作得更好,并且您可以更好地估算出接近实际分布的统计分布。虽然StatMan显然可以进行一些平滑/启发式操作;我要说的是很令人不安的是,您可以直接计算直方图来获得更好的结果,而仍然使用与使用tablesample

@JohanBenumEvensberget IMO对于INT列来说,表现不同并不是很合理的。使用INT时,您缺少的域将更加有限。对于字符串,它实际上可以是最大长度限制。当我们无法获得良好的直方图时,可能会令人不安,但在大多数情况下它的效果都很好。由于代码是秘密的,因此我们无法真正确定其是否按预期工作。如果您认为此问题应由MS解决,则可以考虑在此处发表文章:connect.microsoft.com/SQLServer/Feedback
Joe Obbish
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.