本文出处:http://www.cnblogs.com/wy123/p/6770258.html 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的不一样表现,以及该问题可能形成的潜在影响。sql
下面涉及到的测试环境的数据库版本以下数据库
测试环境准备缓存
首先利用以下脚本,建一张测试表,写入测试数据,下面会解释测试数据的分布ide
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个不一样的Id2oop
统计信息直方图中覆盖到的谓词的预估sqlserver
测试:根据直方图中的任何一个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,
这个值是经过什么方式计算出来的?暂时还没查到资料。
能够肯定的是,对于相似状况的预估算法,也就是谓词没有包含在统计信息直方图中的状况下(one specifies a value which is out of range of the current statistics)
在sqlserver 2014中,经测试,不一样状况下预估是不同的,不是固定的预估为1行,也不是固定预估为的0.1%,也不是简单的Rows Sampled*All density
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中的一些改进。