SQL Server 中统计信息直方图中对于没有覆盖到谓词预估以及预估策略的变化(SQL2012-->SQL2014-->SQL2016)

原文:SQL Server 中统计信息直方图中对于没有覆盖到谓词预估以及预估策略的变化(SQL2012-->SQL2014-->SQL2016)

本文出处:http://www.cnblogs.com/wy123/p/6770258.html

统计信息写过几篇了相关的文章了,感觉还是不过瘾,关于统计信息的问题,最近又踩坑了,该问题虽然不算很常见,但也比较有意思。
相对SQL Server 2012,发现在新的SQL Server版本(2014,2016)中都有一些明显的变化,下文将对此进行粗浅的分析。

SQL Server 2012中(包括之前的版本),因表中数据变化,但统计信息尚未更新的情况下,对于直方图中没有覆盖到的谓词过滤时,sqlserver总是预估为1行
SQL Server 2014和 Server 2016中这种估算方式都有所变化,从表现看,对于对于没有覆盖到的谓词过滤的预估,每个版本都是不同的。
本文简单测试一下此种情况在SQL Server 2012,SQL Server 2014,SQL Server 2016的不同表现,以及该问题可能造成的潜在影响。

下面涉及到的测试环境的数据库版本如下

测试环境准备

首先利用如下脚本,建一张测试表,写入测试数据,下面会解释测试数据的分布

create table A
(
    IdentifierId    int identity(1,1),
    Id1                int,
    Id2                int,
    OtherCol        CHAR(500)
)
GO

begin tran
declare @i int = 1
while @i<=1000000
begin
    insert into A values ((@i/50000)+1,@i,NEWID())
    set @i = @i+1
    if (@i%500000)=0
    begin
        if @@TRANCOUNT>0
        begin
            commit
            begin tran
        end
    end
end
if @@TRANCOUNT>0
begin
    commit
end
GO

插入的测试数据的分布如下,Id1是从1~20,每一个Id1对应50000个不同的Id2

  

统计信息直方图中覆盖到的谓词的预估

  测试:根据直方图中的任何一个Id来做查询,查询之前先创建相关列上的统计信息,发现预估行数是绝对准确的。

  

  查看idx_1上的统计信息,上面预估的绝对准确就归结于统计信息100%的取样统计以及Rang_Hi_key的EQ_Rows,直方图中的Id1的分布是1~21

  

统计信息直方图中未覆盖到的谓词的预估

  继续插入一个与上面Id2都不一样的数据,这里为50,因为此时插入的是50000行数据,同时又不足以触发统计信息更新,因此发生如下写入数据之后,统计信息并不会更新。
  因此这个插入完成之后,统计信息并没有更新。

   

  因为统计信息没有更新,在idx_1的直方图中,是没有Id1=50的信息的,也就说Id1=50不存在于统计信息的直方图中,
  在SQL Server 2012中预估的结果:预估为1行,实际为50000行

  

  重复以上测试代码,分别在SQL Server 2014和SQL Server 2016中测试,不重复截图了

  SQL Server 2014中测试如下:行预估为1024.7,实际为50000,
  这个值是通过什么方式计算出来的?暂时还没查到资料。

  

  SQL Server 2016中测试如下: 预估为49880.8,实际为50000,基本上接近于真实值。
  相对于SQL Server 2012和2014的预估结果,这个预估的准确性看起来还是比较吊的。

  

  为什么SQL Server 2016中预估的如此准确?
  因为在SQL Server 2016中,对于直方图中不存在的过滤谓词,在用这个谓词进行查询的时候,会自动更新相关的统计信息,然后再执行查询,
  这个特性,相对于SQL Server 2012和2014来说,是全新的,也是非常实用的。
  SQL Server 2014这个预估策略虽然在2012的基础上做出了一些改进,但是还是没有解决本质问题,以至于人仍旧要人为地干预统计信息的更新。
  在SQL Server 2016中,即便是当前表中改变的数据行还没有达到触统计信息更新阈值的条件(传统上所谓的阈值,500+rowcount*20%),
  统计信息依然会在查询的驱动下更新,通过索引上的统计信息可以看到,参考下图,直方图中生成了一个50的统计。

  

  下面就是所谓触发统计信息更新阈值的条件(严格说是该规则仅对SQL Server 2016之前的版本有效,不适应于SQL Server 2016)
    1,表格从没有数据变成有大于等于1条数据。
    2,对于数据量小于500行的表格,当统计信息的第一个字段数据累计变化量大于500以后。
    3,对于数据量大于500行的表格,当统计信息的第一个字段数据累计变化量大于500 + (20%×表格数据总量)以后。
  这个说法,对于SQL Server 2016之前的版本是有效的,对于SQL Server 2016之后的版本是不成立的,我想这个还是值得注意的。

SQL Server 2016中统计信息更新策略相当于之前版本中开启了TraceFlag 2371,参考http://www.cnblogs.com/wy123/p/5748933.html
也即决定统计信息的变化值为动态的,不再拘泥于“数据累计变化量大于500 + (20%×表格数据总量)”这一限制。
除此之外,应该还要其他机制,比如这里的查询所触发的。

造成的问题

为什么微软会在SQL Server 2016中将统计信息的更新策略做出如此的改变,以及为什么笔者会来探究这个问题?
当然在实际业务中被这个问题坑的蛋疼。
问题很明显,类似于测试的场景,在SQL Server 2012(包括之前的版本),这种预估策略存在的严重的缺陷。
比如示例中:
因为没有当前过滤谓词的统计信息(或者说没有收集到当前谓词的统计信息),实际为5000行的情况下,预估为1行。
这种预估策略非常离谱,某种情况下会造成严重的性能问题,估计也很容易猜到,只是遇到的比较少罢了.
下面就简单具体说明,会造成什么问题,以及原因。

  上述问题在什么情况下会造成性能问题,以及影响又多严重,这里仅简单举例说明。下面这个测试是在SQL Server 2012下进行的。
  为演示这个问题,先来做另外一张测试表B,并写入测试数据。

create table B
(
    IdentifierId    int identity(1,1),
    Id2                int,
    OtherCol        char(500)
)
GO

begin tran
declare @i int = 1
while @i<=1000000
begin
    insert into B values (@i,NEWID())
    set @i = @i+1
    if (@i%100000)=0
    begin
        if @@TRANCOUNT>0
        begin
            commit
            begin tran
        end
    end
end
if @@TRANCOUNT>0
begin
    commit
end
GO

create index idx_2 on B(Id2)GO

借助第二张表做一个测试,从而把错误预估行数造成的缺陷给放大,
执行下面两个SQL,分别查询A.Id1 = 5和A.Id1 = 50的信息,
由数据分布可知,查询总的结果总数会完全一样(截图受影响行数),
虽然A.Id1 = 5和A.Id1 = 50的数据量和分布也完全一样,但是后者的逻辑IO远远超出前者。
就是因为直方图中没有A.Id1 = 50的统计信息,A.Id1 = 50被错误地预估为1行造成的。

   

  具体原因就很明了的,了解执行计划的同学应该很清楚。
  因为错误地预估了当前谓词过滤的行数,在A表上,采用索引查找的方式来查询数据,
  事实证明,当前情况下,这是比全表扫描更加低效的一种方式(看逻辑IO),这是其一。
  另外A表查询之后驱动B表的过程中,因为预估为一行,采用了Nested Loop的方式来驱动B表做连接,
  事实上当前情况下Nested Loop并非最好的,可以说是很不好的。
  这里也可以归结为统计信息的直方图中没有过滤谓词上的统计信息,在第一个阶段的预估中错误地估算为1行造成的。

 

  这种问题更蛋疼的地方在于,检查Session或者缓存的执行计划的时候,会发现,表面上看,执行计划挺好的啊,都用到索引了。
  比如第二个SQL的执行计划,看起来似乎没问题,也容易直接忽略这个造成的问题,
  从而把重点转向其他地方,使得问题变得更加难以甄别。其实问题正是出在错误地使用了索引,不该使用索引的地方使用了索引。
  这就是执行计划第一步选择错误,造成后面每一步都错误的情况(一步错,步步错),实际情况中,SQL更加复杂,数据量也更大,造成的影响也更大。
  如果上述示例中在再多几张表join,会出现清一色的Nested Loop方式来驱动表连接,这样的话,SQL执行时间和逻辑IO是非常高的。

  附上一个在SQL Server 2016下的测试截图,可见在默认情况下,执行计划做出了正确的选择。

  

  

 最后: 

1,本文不是说索引的,关于索引的就不多说。
2,本文也的场景虽然不是太常见,稍显特殊,但也是实际遇到的,另外可以看出,微软也在从这个方面逐步改进SQL Server优化器更新统计信息的策略。
3,关于此场景下的预估,在不同版本下,还有不少有意思问题没有抛出来,有机会再说。
4,类似问题只有在数据量相对较大的情况下才能发生,如果是十万以下或者几十万的数据量,对数据库来说算是微小型数据量,类似问题对性能的影响完全体现不出来。
5,如果有人根据本文的测试验证的话,请注意一个细节:对于过滤谓词的预估,分如下两种情况,这两种情况在2012和2014(2016)中预估的方式也是不同的
  1,表中确实没有这个谓词的数据,并且统计信息没有更新,比如Id1 = 50的数据为0行的情况下的预估
  2,表中有这个谓词的数据,同样是统计信息没有更新,比如Id1 = 50的数据为50000行的情况下的预估

总结:

SQL Server 的预估对执行计划的生成有着至关重要的影响,而预估又依赖于统计信息,因此统计信息的更新以及准确性就显得尤为重要。鉴于此,SQL Server在每个版本中,对于统计信息的生成以及更新策略都有着比较大的变化,本文仅仅从一个较小的点出发,来验证SQL Server各个版本中统计信息预估以及更新的一些特点,从中发现类似问题可能产生的潜在的影响,以及SQL Server 2016中的一些改进。

时间: 2025-01-05 22:01:58

SQL Server 中统计信息直方图中对于没有覆盖到谓词预估以及预估策略的变化(SQL2012-->SQL2014-->SQL2016)的相关文章

MySQL 8.0 中统计信息直方图的尝试

直方图是表上某个字段在按照一定百分比和规律采样后的数据分布的一种描述,最重要的作用之一就是根据查询条件,预估符合条件的数据量,为sql执行计划的生成提供重要的依据. 在MySQL 8.0之前的版本中,MySQL仅有一个简单的统计信息却没有直方图,没有直方图的统计信息可以说是没有任何意义的. MySQL 8.0新特性之一就是开始支持统计信息的直方图,这个概念很早就提出来了,抽空具体尝试了一下使用方法. 之前写过MSSQL相关统计信息的一点东西,在原理上都是一致的, 照旧,直接上例子,造数据,创建一

SQL Server 查找统计信息的采样时间与采样比例

原文:SQL Server 查找统计信息的采样时间与采样比例 有时候我们会遇到,由于统计信息不准确导致优化器生成了一个错误的执行计划(或者这样表达:一个较差的执行计划),从而引起了系统性能问题.那么如果我们怀疑这个错误的执行计划是由于统计信息不准确引起的.那么我们如何判断统计信息不准确呢?当然首先得去查看实际执行计划中,统计信息的相关数据是否与实际情况有较大的出入,下面我们抛开这个大命题,仅仅从统计信息层面去查看统计信息的更新时间,统计信息的采样行数.采样比例等情况. 1:首先,我们要查查统计信

SQL SERVER的统计信息

可以看到,统计信息分为三部分内容,头信息,数据字段选择性及直方图. 2.1 头信息 列名 说明 Name 统计信息的名称. Updated 上次更新统计信息的日期何时间 Rows 预估表中的行数,不一定是精确的 Rows Sampled 统计信息的抽样行数,如果小于Rows,则说明直方图和密度结果是更加抽样行估计的 Steps 直方图中的梯级数.Number of steps in the histogram.每个梯级都跨越一个列值范围,后跟上限列值. 直方图梯级是根据统计信息中的第一个键列定义

Sql Server 2005 统计信息用途

1, 什么是统计信息 以下是官方的对统计信息的描述: 按照默认设置,如果表中的某列没有索引,则SQL Server会自动为该列创建统计.然后,查询优化器评估该列中数据分布范围的统计信息,以选择一个更为有效的查询处理方案.分辨自动创建的统计很简单,在SQL Server 7.0和SQL Server 2000中,自动创建的统计的前缀为_WA_Sys. 个人感想: 以前对_WA_Sys开头的统计信息一直不知道有什么用,在学习了oracle时的CBO和三个算法(嵌套循环,合并连接和hash连接)才发现

SQL Server 2014新功能 -- 内存中OLTP(In-Memory OLTP)

SQL Server 2014新功能 -- 内存中OLTP(In-Memory OLTP) 概述 内存中OLTP(项目"Hekaton")是一个全新的.完全集成到SQL Server的数据库引擎组件. 对OLTP工作负载访问中在内存中的数据进行了优化.内存中OLTP能够帮助OLTP工作负载实现显著的性能改善,并减少处理时间.表能被视为"内存优化",提升内存中的OLTP功能.内存优化表是完全可事务的.并可以使用Transact-SQL进行访问.Transact-SQL

SQL Server 监控统计阻塞脚本信息

原文:SQL Server 监控统计阻塞脚本信息 数据库产生阻塞(Blocking)的本质原因 :SQL语句连续持有锁的时间过长 ,数目过多, 粒度过大.阻塞是事务隔离带来的副作用,它是不可避免的,而且是一个数据库系统常见的现象. 但是阻塞的时间和出现频率要控制在一定的范围内,阻塞持续的时间过长或阻塞出现过多(过于频繁),就会对数据库性能产生严重的影响. 很多时候,DBA需要知道数据库在出现性能问题时,有没有发生阻塞? 什么时候开始的?发生在那个数据库上? 阻塞发生在那些SQL语句之间? 阻塞的

10-SQLServer中统计信息的使用

一.总结 1.网址https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-stats-transact-sql?view=sql-server-2017(sys.stats)2.什么是统计信息? 统计信息描述了表格或者索引视图中的某些列的值的分布情况,属于数据库对象. 3.根据统计信息,查询优化器就能评估查询过程中需要读取的行数以及结果集情况,同时也能创建高质量的查询计划.其实统计信息就

SQL Server 在多个数据库中创建同一个存储过程(Create Same Stored Procedure in All Databases)

原文:SQL Server 在多个数据库中创建同一个存储过程(Create Same Stored Procedure in All Databases) 一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 遇到的问题(Problems) 实现代码(SQL Codes) 方法一:拼接SQL: 方法二:调用模板存储过程创建存储过程: 总结 扩展阅读 参考文献(References) 二.背景(Contexts) 在我的数据库服务器上,同一个实例

Sql Server 高频,高并发访问中的键查找死锁解析

死锁对于DBA或是数据库开发人员而言并不陌生,它的引发多种多样,一般而言,数据库应用的开发者在设计时都会有一定的考量进而尽量避免死锁的产生.但有时因为一些特殊应用场景如高频查询,高并发查询下由于数据库设计的潜在问题,一些不易捕捉的死锁可能出现从而影响业务.这里为大家介绍由于设计问题引起的键查找死锁及相关的解决办法. 这里我们在测试的同时开启trace profiler跟踪死锁视图(locks:deadlock graph).(当然也可以开启跟踪标记,或者应用扩展事件(xevents)等捕捉死锁)