我已经在我可以访问的最大数据库的某些非聚集索引上看到了相同的密度问题。首先,我将从对直方图和密度计算所做的一些观察开始:
- 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
与某些示例更新相比,更新可以更快地完成。
tablesample