《Pro SQL Server Internals, 2nd edition》的CHAPTER 3 Statistics中的Introduction to SQL Server Statistics、Statistics and Execution Plans、Statistics Maintenance(译)

 

《Pro SQL Server Internals》

 

 

作者: Dmitri Korotkevitch 
出版社: Apress
出版年: 2016-12-29
页数: 804
定价: USD 59.99
装帧: Paperback
ISBN: 9781484219638

 

统计

SQL Server查询优化器在为查询选择执行计划时使用基于成本的模型。它估计不同执行计划的成本,并选择成本最低的一个。但是,请记住,SQL Server并不搜索查询可用的最佳执行计划,因为评估所有可能的替代方案在CPU方面都是耗时和昂贵的。查询优化器的目标是找到足够好的执行计划,足够快。

基数估计(在查询执行的每个步骤中需要处理的行数的估计)是查询优化中最重要的因素之一。这个数字会影响连接策略的选择、查询执行所需的内存量(内存授予)以及其他许多事情。

访问数据时要使用的索引的选择就是这些因素之一。正如您将记住的,键和RID查找操作在I/O方面是昂贵的,并且SQL Server在估计将需要大量这些操作时不使用非聚集索引。SQL Server维护有关索引的统计信息,在某些情况下还维护有关列的统计信息,这有助于执行这样的估计。

SQL Server统计介绍

SQL Server统计信息在索引键值中,有时在常规列值中,包含有关数据分布的信息的系统对象。可以在支持比较操作的任何数据类型上创建统计信息,例如>、<、=等。

让我们检查上一章中在清单2-15中创建的dbo.Books表中的IDX_BOOKS_ISBN索引统计信息。可以通过使用DBCC SHOW_STATISTICS('dbo.Books' , IDX_BOOKS_ISBN)命令执行此操作。结果如图3-1所示。

 

3-1DBCC SHOW_STATISTICS输出

如您所见,DBCC SHOW_STATISTICS命令返回三个结果集。第一个包含有关统计信息的一般元数据信息,如名称、更新日期、更新统计信息时索引中的行数等。第一结果集中的Steps列指示直方图中的步骤/值的数量(稍后详细介绍)。密度值不由查询优化器使用,仅出于向后兼容的目的而显示。

第二个结果集,称为密度向量,包含有关密度的信息,用于组合来自统计(索引)的关键值。它是根据1/多个不同值公式计算的,它指示平均每个键值组合有多少行。即使IDX_Books_ISBN索引只定义了一个键列ISBN,它也包括作为索引行的一部分的聚类索引键。我们的表有12500个唯一的ISBN值,ISBN列的密度为1.0/1252500=7.984032E-07。(ISBN,BookId)列的所有组合也是唯一的,并且具有相同的密度。

最后一个结果集称为直方图。直方图中的每条记录(称为直方图步骤)包括统计信息(索引)最左侧列中的示例键值以及关于从前一个值到当前RANGE_HI_KEY值范围内的数据分布的信息。让我们更深入地研究直方图列。

RANGE_HI_KEY列存储密钥的样本值。此值是直方图步骤定义的范围的上限键值。例如,在图3-1的直方图中记录(步骤)#3withRANGE_HI_KEY=&apos;104-0100002488&apos;存储有关从ISBN>&apos;101-0100001796&apos;到ISBN<=&apos;104-0100002488&apos;的间隔的信息。

RANGE_ROWS列估计间隔内的行数。在我们的例子中,记录(步骤)#3定义的间隔有8191行。

EQ_ROWS指示有多少行的键值等于RANGE_HI_KEY上限值。在我们的例子中,只有一行ISBN=&apos;104-0100002488&apos;。

DISTINCT_RANGE_ROWS指示在该间隔内键的不同值有多少。在我们的例子中,键的所有值都是唯一的,所以DISTINCT_RANGE_ROWS=RANGE_ROWS。

AVG_RANGE_ROWS表示间隔中每个不同键值的平均行数。在我们的例子中,键的所有值都是唯一的,因此AVG_RANGE_ROWS=1。

让我们用清单3-1所示的代码将一组重复的ISBN值插入索引中。列表3-1.将重复的ISBN值插入索引。

;with Prefix(Prefix)

as ( select Num from (values(104),(104),(104),(104),(104)) Num(Num) )

,Postfix(Postfix)

as

(

    select 100000001

    union all

    select Postfix + 1 from Postfix where Postfix < 100002500

)

insert into dbo.Books(ISBN, Title)

    select

        convert(char(3), Prefix) + '-0' + convert(char(9),Postfix)

        ,'Title for ISBN' + convert(char(3), Prefix) + '-0' + convert(char(9),Postfix)

    from Prefix cross join Postfix

option (maxrecursion 0);

--更新统计数据

update statistics dbo.Books IDX_ Books_ISBN with fullscan ;

 

现在,如果再次运行DBCC SHOW_STATISTICS(“dbo.Books”,IDX_BOOKS_ISBN)命令,您将看到图3-2所示的结果。

 

3-2DBCCSHOW_STATISTICS输出

 

带有前缀104的ISBN值现在有重复,这影响直方图。值得一提的是,第二结果集中的密度信息也发生了变化。具有重复值的ISBN的密度高于(ISBN,BookId)列的组合,这仍然是唯一的。

让我们运行SELECT BookId,Title FROM dbo.Books WHERE ISBN LIKE&apos;114%&apos;语句并检查执行计划,如图3-3所示。

 

 

3-3。查询的执行计划

大多数执行计划操作符有两个重要的属性。实际行数指示在操作符执行期间处理了多少行。估计行数指示SQL Server在查询优化阶段为该操作符估计的行数。在我们的例子中,SQL Server估计有2625行,ISBN从114开始。如果查看图3-2所示的直方图,您将看到,步骤10存储了关于ISBN间隔的数据分布的信息,其中包括您正在选择的值。即使使用线性近似,也可以估计接近SQL Server所确定的行数。

关于统计学,有两件事情需要牢记。

1.  直方图仅存储最左侧统计(索引)列的数据分布信息。统计学中有关于键值的多列密度的信息,但就是这样。直方图中的所有其他信息仅涉及最左边的统计列的数据分布。

2.  SQL Server最多保留直方图中的200个步骤,而不管表的大小以及表是否被分区。每个直方图步骤所覆盖的间隔随着表的增长而增加。这导致对于大型表的统计信息不那么准确。

在复合索引的情况下,当索引中的所有列都用作所有查询中的谓词时,将具有较低密度/较高唯一值百分比的列定义为索引的最左侧列是有益的。这将允许SQL Server更好地利用统计数据中的数据分布信息。但是,您应该考虑谓词的SARGa.。例如,如果所有查询都在where子句中使用FirstName=@FirstName和LastName=@LastName谓词,那么最好将LastName作为索引中最左边的列。然而,对于像FirstName=@FirstName和LastName<>@LastName这样的谓词,情况并非如此,其中LastName不可SARGable。

统计和执行计划

默认情况下,SQL Server自动创建和更新统计数据。在数据库级别上有两个选项控制这种行为:

1.自动创建统计控制优化器是否自动创建列级统计信息。此选项不影响始终创建的索引级统计数据。默认情况下启用了自动创建统计数据库选项。

2.当启用“自动更新统计数据库”选项时,SQL Server检查统计信息是否在每次编译或执行查询时过时,并在需要时对其进行更新。默认情况下,还启用了自动更新统计数据库选项。

提示通过使用STATISTICS_NORECOMPUTE索引选项,可以在索引级别控制统计信息的自动更新行为。默认情况下,此选项设置为OFF,这意味着统计信息会自动更新。在索引或表级别更改自动更新行为的另一种方法是使用sp_autostats系统存储过程。

SQL Server基于影响统计列的INSERT、UPDATE、DELETE和MERGE语句执行的更改数量来确定统计信息是否过时。SQL Server计算统计列更改了多少次,而不是更改的行数。例如,如果同一行更改了100次,那么将计数为100次更改,而不是1次更改。

有三种不同的场景,称为统计更新阈值,有时也称为统计重新编译阈值,其中SQL Server将统计标记为过时。

1.     当表为空时,SQL Server会在向表中添加数据时更新统计数据。

2.  当一个表的行数少于500行时,SQL Server在每500个统计列更改之后更新统计信息。

3.  SQL Server 2016之前和在数据库兼容级别<130SQL Server 2016中:当一个表有500行或更多行时,SQL Server在每500+(表中行总数的20%)统计列发生更改后更新统计信息。


在具有数据库兼容级别=130SQL Server 2016中:大型表的统计信息更新阈值是动态的,并且取决于表的大小。表中的行数越多,阈值就越低。在具有数百万甚至数十亿行的大型表中,统计更新阈值可能只是表中总行数的一小部分。这个行为也可以在SQL Server 2008R2 SP1和上面的跟踪标志T2371中启用。

表3-1总结了不同版本的SQL Server中的统计更新阈值行为。

3-1。统计更新阈值和SQL Server版本

 

SQL Server 2016之前

数据库兼容级别<130SQL Server 2016

具有数据库兼容级别=130SQL Server 2016

默认行为

T2371

静态(~20%)阈值

SQL Server 2008R2 SP1中的动态阈值及其以上

静态(~20%)阈值动态阈值

动态阈值

动态阈值(忽略跟踪标志)

这引出了一个非常重要的结论。使用静态统计信息更新阈值,触发统计信息更新所需的统计信息列的更改数量与表大小成比例。表越大,自动更新统计信息的频率就越低。例如,对于具有10亿行的表,需要对统计列执行大约2亿次更改以使统计信息过时。如果可能,建议使用动态更新阈值。

让我们看看这种行为如何影响我们的系统和执行计划。此时,表dbo.Books有1265000行。让我们用前缀999向表中添加250000行,如列表3-5所示。在这个示例中,我使用的SQL Server 2012没有启用T2371。如果启用了动态统计更新阈值,则可以看到不同的结果。此外,在SQL Server 2014中引入的新基数估计器也可以改变行为。我们将在本章后面讨论。

列表3-5。向dbo.Books添加行

;with Postfix(Postfix)

as

(

    select 100000001

    union all

    select Postfix + 1

    from Postfix

    where Postfix < 100250000

)

insert into dbo.Books(ISBN, Title)

    select

        '999-0' + convert(char(9),Postfix)

        ,'Title for ISBN 999-0' + convert(char(9),Postfix)

    from Postfix

option (maxrecursion 0);

 

现在,让我们运行SELECT*FROM dbo.Books WHERE ISBN LIKE&apos;999%&apos;查询,该查询选择具有此类前缀的所有行。

如果检查查询的执行计划,如图3-7所示,您将看到非聚集索引查找和键查找操作,即使它们在需要从表中选择将近20%的行的情况下效率很低。

 

3-7。使用999前缀选择行的执行计划

您还会注意到,在图3-7中,Index Seek操作符的估计行数和实际行数之间存在巨大的差异。SQL Server估计表中只有31.4行具有前缀999,即使有250000行具有这样的前缀。结果,生成了高度低效的计划。

让我们通过运行DBCC SHOW_STATISTICS('dbo.Books',IDX_BOOKS_ISBN)命令来查看IDX_BOOKS_ISBN统计数据。输出如图3-8所示。如您所见,尽管我们向表中插入了250000行,但是统计信息没有更新,并且前缀999的直方图中没有数据。第一个结果集中的行数对应于上一次统计更新期间表中的行数。它不包括刚刚插入的250000行。

 

3-8IDX_BOOKS_ISBN统计

现在,让我们使用UPDATE STATISTICS dbo.Books IDX_Books_ISBN WITH FULLSCAN命令更新统计数据,然后再次运行SELECT * FROM dbo.Books WHERE ISBN LIKE '999%'查询。查询的执行计划如图3-9所示。现在估计的行数是正确的,并且SQL Server最终得到一个更高效的执行计划,该执行计划使用聚集索引扫描,其I/O读数比以前减少了约17倍。

 

3-9。统计信息更新后选择具有999前缀的行的查询的执行计划

如您所见,不正确的基数估计可能导致高度低效的执行计划。过时的统计数据可能是导致基数估计不正确的最常见原因之一。您可以通过检查执行计划中估计的和实际的行数来精确地指出其中的一些情况。这两个值之间的巨大差异常常表明统计是不正确的。更新统计数据可以解决这个问题,并生成更有效的执行计划。

 

统计维护

如前所述,SQL Server默认情况下自动更新统计数据。这种行为通常对于小表是可接受的;但是,对于具有数百万或数十亿行的大表,除非使用数据库兼容级别为130或启用了跟踪标志T2371的SQL Server 2016,否则不应该依赖自动统计更新。为了触发20%的统计数据更新阈值的统计数据更新所需的更改数量将非常高,因此,更新不会被足够频繁地触发。

在这种情况下,建议您手动更新统计数据。在选择最佳统计维护策略时,必须分析表的大小、数据修改模式和系统可用性。例如,如果系统在营业时间之外没有沉重的负载,则可以决定每晚更新关键表的统计信息。不要忘记统计和/或索引维护给SQL Server增加了额外的负载。您必须分析它如何影响同一服务器和/或磁盘阵列上的其他数据库。

在设计统计维护策略时要考虑的另一个重要因素是如何修改数据。对于键值不断增加或减少的索引,您需要更频繁地更新统计信息,例如当索引中最左边的列被定义为identity或被序列对象填充时。如您所见,如果特定的键值在直方图之外,SQL Server会极大地低估行的数量。这种行为在SQL Server 2014到2016中可能有所不同,我们将在本章后面看到。

可以使用UPDATE STATISTICS命令更新统计数据。当SQL Server更新统计信息时,它读取数据的示例而不是扫描整个索引。您可以通过使用FULLSCAN选项来更改该行为,该选项强制SQL Server从索引读取并分析所有数据。正如您可能猜到的,该选项提供了最准确的结果,尽管在大表的情况下,它可以引入大量的I/O活动。

注释SQL Server在重建索引时更新统计信息。我们将在第6章“索引分段”中更详细地讨论索引维护。

可以使用sp_updatestats系统存储过程更新数据库中的所有统计信息。建议您使用此存储过程,并在将数据库升级到新版本的SQL Server之后更新数据库中的所有统计数据。您应该与DBCC UPDATEUSAGE存储过程一起运行它,该存储过程纠正目录视图中不正确的页和行计数信息。

有一个sys.dm_db_stats_properties DMV,它显示自上次统计更新以来对统计列所做的修改数量。利用DMV的代码如清单3-9所示。

列表3-9dm_db_stats_properties

select

    s.stats_id as [Stat ID], sc.name + '.' + t.name as [Table], s.name as [Statistics]

    ,p.last_updated, p.rows, p.rows_sampled, p.modification_counter as [Mod Count]

from

    sys.stats s join sys.tables t on

        s.object_id = t.object_id

    join sys.schemas sc on

        t.schema_id = sc.schema_id

    outer apply

       sys.dm_db_stats_properties(t.object_id,s.stats_id) p

where

    sc.name = 'dbo' and t.name = 'Books';

 

查询的结果(如图3-11所示)表明自上次统计更新以来,对统计列进行了250000次修改。您可以构建一个统计维护例程,该例程定期检查sys.dm_db_stats_properties DMV,并用大的修改后的计数重新构建统计信息。

 

3-11sys.dm_db_stats_properties输出

另一个与统计数据相关的数据库选项是自动异步更新统计数据。默认情况下,当SQL Server检测到统计信息过期时,它将暂停查询执行,同步更新统计信息,并在统计信息更新完成后生成新的执行计划。通过异步统计信息更新,SQL Server使用旧的执行计划执行查询,该执行计划基于过时的统计信息,同时异步更新后台中的统计信息。建议您保持同步统计信息更新,除非系统具有非常短的查询超时,在这种情况下,同步统计信息更新可以超时查询。

最后,在创建新索引时,SQL Server不会自动删除列级统计信息。您应该手动删除冗余的列级统计对象。

 

posted @ 2018-12-19 23:30  陈XY  阅读(274)  评论(0编辑  收藏  举报