暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

新建一个索引而已,系统就崩了么?!---技术人生系列第五十九期-我和数据中心的故事

中亦安图 2021-11-17
431



前言










1117日,今天是值得纪念的一天,技术人生系列回来了, 久违了,朋友们!


20188第五十八期停更后,由于工作繁忙的原因,就没有继续更新了。时间过的真快,一晃三年已经过去了!还是熟悉的味道,还是每周一篇贴近实战的文章,不过这次,我们跟随一下潮流,换一种形式,那就是以文章结合每周四晚直播的方式和大家分享,这样也可以有良好的互动,让大家有更多的收获,希望大家理解并喜欢这样的改变哦。

                             



言归正传,本期分享的主题希望能给大家带来下面几个方面或多或少的收获:


1、 “在线新建一个索引有风险么,系统真的会崩么?”

答案是可能会,也可能不会。

深入了解这个case之后,你就知道什么业务场景下会崩,什么场景下不会崩,也就可以完善创建索引的指导规范了。


2、 优化SQL我还可以,但是领导一问,为什么执行计划变了,根本原因是什么,原理是什么,以后怎么预防?总答不上来,也不知道怎么分析这类问题

希望这个case之后,你能掌握正确的方法去分析该类问题了,原因找到了,自然就好预防了 


3、 我的结论说了但是没有说服领导啊

实际上重现出问题往往是最好的说服力,所以大家要不断培养自己重现问题的能力才能不断突破自己的技术瓶颈



1.问题来了

微信响了,是一家资产过万亿的银行客户的DBA负责人发过来的信息。



看完信息,小y一下来了精神,这是一个19C 两节点的RAC。


不过从发送信息的时间看,问题已经暂时得到解决,客户显然更关心背后的原理,以及今后如何避免类似问题出现。


客户紧接着说道:




“出问题的时候,表的统计信息我看了,全局和分区一级的统计信息,都是新的,没问题。

另外,这个系统绑定变量偷窥是关闭的。

最后实在不行,重新收集了一次统计信息后,执行计划就变好了。

领导需要知道背后的原因和原理,为什么新建一个索引,会出现这样的情况?

以后新建索引该怎么做,规范怎么定,如果出现问题的是高频交易的SQL,系统就崩了!!”




在这个行业摸爬滚打这么多年,特别能理解客户此时的心情,也正是客户这样的高要求促使我们不断进步,帮助客户分析每次问题背后的原因,并由点带面去设计规范预防问题、消除隐患,这不就是我们的价值么。


这类问题,如果在可以重现问题的情况下收集一个10053 event,那么对于我们分析为什么SQL走错索引的原因,将有极大的帮助。于是小y和客户提议:




“现在explain plan for sql…还能重现错误的执行计划么?帮我收集个10053吧”




很可惜,故障现象现在已经无法重现了。当前Explain得到的是正确的执行计划。


实际上,这一类典型的事后分析执行计划突变或选错的问题,是DBA和开发人员比较头疼、没有思路的问题,特别是统计信息还比较准确的情况下,原因说不清道不明。


事后分析,并且在无法重现问题的情况下分析问题,这无疑是一个挑战,而挑战不正是人生的魅力所在么。


到这里,大家不妨停一下,思考一下,如果是你,接下来你打算分几步来分析,又需要哪些信息来帮助你找到问题的根本原因呢


 思考时间


 ……


 此处留白



 什么时候往下翻,由你决定




 ……



2.确认问题


客户DBA很专业,随后在微信里贴了相关的信息,包括:


² 完整的SQL语句

² 现有索引的字段列表和字段顺序

² 新创建的索引的字段列表和字段顺序

² 表上过滤字段的选择性

² 以前走的的正确执行计划

² 现在走错了的执行计划



2.1 SQL语句以及列选择性


SQL语句如上图所示。

这里小y对表名多了一些脱敏的处理。

表上相关列的选择性如下:


 

这里大家可以稍微心算一下:


² SQL是单表查询,求count(*),过滤条件有2个条件,分别为法人代码和对账批号

² T1表约有1400万行,是一个分区表,按天范围分区,SQL语句中没有使用分区字段

² 对账批号字段里有800多万的NULL值,列上无直方图,因此平均每个对账批号大概有(1400-800万)/190=3万条左右的记录

² 法人代码只有一个值,因此满足法人代码条件的记录是所有记录,即1400万行


这里请记住对账批号和法人代码两个字段的名字,以及字段不同值的个数分别为1901.


看到这里,先来一个小花絮,估计开发的同学和部分DBA已经跃跃欲试了。


并且很快指出了这个SQL的第一个“问题”,




“这个SQL明明是count(*), SQL语句里还有order by,这个写法有问题吧,会导致额外的排序吧…”




实际上,听到这个回答,有喜有悲。

喜的是开发有自己的想法,悲的是,开发还不了解SQL的执行过程,不了解背后的原理。


其实这不是一个问题,因为实际执行时并不会真正发生排序,Oracle数据库的优化器还是非常智能的,其中一个特性叫OBYE order by elimination,中文含义为“order by 清除”,对于不必要的order by,SQL做查询转换的时候自动就去掉了。也就是说,数据库在执行上面SQL的时候,实际上已经改成下面这样的SQL来执行了。




2.2 现有索引的字段列表和顺序



可以看到:


当前存在一个IDX4,即4号索引,顺序分别是 DUIZPHAO,QIARIQI,FARENDMA,其中红色字体的列分别对应SQL语句中的过滤条件即对账批号和法人代码。 


显然SQL语句应该走这个索引IDX4,因为对账批号是第一个字段,不同值有190个。


法人代码虽然是复合索引中的第三列,但对于这个SQL,索引字段中带有法人代码字段,则SQL语句无需回表,在索引中就可以过滤,因此走该索引效率还是不错的。


2.3 新建索引的字段列表和顺序


可以看到:


问题当天,新建了一个IDX5,即5号索引,顺序分别是 YEWUCJDM,QIARIQI,XITONGBS,FARENDMA

其中红色字体的部分对应SQL语句中的过滤条件 ,即法人代码字段。

因为法人代码只有1个不同值,因此无论如何也不应该走该索引。


但出人意外的事情发生了,就在这个索引创建后,对账批量的SQL,立马放弃了更合适的IDX4索引,选择了这个打死都不应该选择的IDX5索引。


2.4 新建索引后选错执行计划


在新创建IDX5这个索引后,数据库居然离谱的选择了这个索引!IDX5索引只能通过法人代码来定位数据,而法人代码只有1个。同时,因为IDX5索引中,过滤字段法人代码不是引导列,而是最后1列,因此是索引跳扫,INDEX SKIP SCAN.


SQL语句走IDX5这个索引,那就太崩溃了。

我们来看看为什么,估算一下走这个索引,SQL要跑多长时间才能完成呢?


因为法人代码是唯一的,则需要先从索引跳扫定位1400万条记录,再回表过滤对账批号(该字段不在复合索引的字段中,该字段的不同值个数为190个),结果约为3万条记录。

1400万的回表,这无疑是灾难,近似等于一条记录一条记录地去访问整张表的所有记录了,发生的IO次数(单块读)最高可能达到1400万次,按照一个IO响应 4毫秒计算,需要的时间是 14,000,000*4/1000=56000秒,超过10个小时(即使按照一个IO 1毫秒计算,也需要2.5小时),这显然是无法接受的!


2.5 以前的正确执行计划



正确的执行计划如上图所示

这里走IDX4索引,是正确的。

这里,我们稍微记忆一下,索引45错,索引45错,索引45错!


IDX4的索引字段顺序如下


IDX4索引先通过对账批号字段来定位3万条数据,再过滤法人代码即可,这里按照一个索引叶子块存200行计算,也只需要访问150个叶子块,即使所有叶子块不在buffer cache内存里缓存,只需要150IO,单个IO按照4毫秒计算,这样总计才需要150*4毫秒=600毫秒左右。


2.6 为什么优化器的选择那么离谱呢



这里,我们稍微总结一下,错误的执行计划和正确的执行计划来,估算下来分别要执行56000秒和600毫秒,按照客户的说法,统计信息基本是正确的。那么数据库优化器为什么会选错呢?




3.思考时间

好的,阶段性的信息已经有了,接下来你会用什么方法论来分析这个问题呢?


……


此处留白


什么时候往下翻,由你决定


……



4.头脑风暴

如果是你,你接下来会怎么来分析这个问题呢?

可能有同学已经跃跃欲试了,我们来听听大家的猜测吧 



4.1 是绑定变量偷窥特性导致的么


答案是NO


客户之前提到,绑定变量偷窥特性是关闭的。

也就是说,在选择执行计划的时候,实际上不会按照变量传入值的具体分布来确定执行计划。


以下是display_cursor执行计划的outline部分,显示了bind peeking特性确实是关闭的,因此也就不存在数据倾斜的传入值将执行计划带偏的情况了。



4.2 是新建索引没有及时收集统计信息么


答案是NO


创建索引的时候,虽然执行的是类似下面的命令,



create index idx_xx on xx_tab(xx_col) online;




实际上,数据库会在命令后面加上compute statistics的默认选项,即数据库执行的SQL



create index idx_xx on xx_tab(xx_col) online compute statistics;




因此,创建索引后也就完成了同时收集了索引的统计信息。


4.3 容我想想,还有什么其他可能呢…


可能还有同学有各种猜测,是否准确呢?

实际上,解决经验范围之外的未知问题,我们需要的是方法论,方法和思路远比纯粹的猜测更有意义。


暂时想不出来,没关系的,小y在找到这个问题的原因后,已经做了重现案例。


为了更好的互动,我们将在直播上验证大家的猜测或方法论,这也就是我们说到的图文加直播互动结合的形式,让大家更好的提升


……


此处留白

不放再思考思考

什么时候往下翻,由你决定


……




5.跟小y一起踏入方法论分析之旅…


5.1 方法论是什么?


SQL为什么走错执行计划呢?


实际上,数据库是基于成本的优化器,数据库的优化器将计算各种可能执行计划的成本COST,从中取成本最低的执行计划。这个就是背后的逻辑。


所以,要么是错误的执行计划算低了成本,要么是正确的执行计划算高了成本。


接下来我们将按照这个思路往下分析即可


5.2 是错误的执行计划成本算低了么?


回想以下,执行计划,走索引,45错。4是对账批号的索引,5是法人代码的索引。


走IDX5即法人代码字段上的索引,显然是错误的。如下


 

按照方法论,这个执行计划到底是成本算低了还是算高了呢?


注意,ID=3ID=2的步骤,根据法人代码定位到1400万记录,回表后成本只有826-26=800。这显然是算低了!


我们之前提到,1400万记录回表的时间按照4毫秒计算,在数据高度离散的情况下,可能需要56000秒,超过10个小时。成本怎么可能只有800个单块IO

5.3 是正确的执行计划成本算高了么?


执行计划,走索引,45错。

再来回顾一下SQL语句和正确的执行计划以及正确索引IDX4的字段列表



这里走IDX4索引,是正确的。IDX4的索引字段顺序如下


也就是说:


这里实际上是不需要回表的,因为是SELECT COUNT(*),并且2个过滤条件已经在索引中全部包含,索引先按照对账批号定位数据,在不回表的情况下就可以过滤法人代码,并且ORDER BY sanfriqi这里也会做order by清除,不需要回表拿到sanfriqi 字段的值因此没有必要回表。但实际情况是回表了!这导致成本多计算了5218-168=5050,显然是一个优化器的缺陷/BUG

正确的成本应该是168左右,而不是现在的5218!!

168的成本将远小于错误执行计划的826



5.4 哪个更为关键呢


两个都很重要。如果只单独考虑这一个SQL和这2个索引,解决任意一个问题都可以解决该问题。但从严重程度来讲, 1400W记录回表访问,COST只有800,显然是严重算低了!IDX5高成本算低,影响的业务场景将会更多,也更关键!



5.5 为什么错误的执行计划的成本严重算低了?



综上,在表和索引统计信息都相对准确的情况下,

为什么走IDX5定位1400W条记录,回表的成本COST只有800呢?

错误的执行计划,成本COST本优化器严重算低了!


如果在没有10053 event的情况下,该怎么继续往下分析呢?

什么业务场景下会导致我们的系统崩塌呢



6. 真相即将揭晓!!!




关注上图中的视频号,并预约直播, 11月18日即明晚(周四)八点,小y将用半个小时为大家揭晓真相,用正确的方法论来查明问题真相。记得叫上你家开发同学一起哦,索引他们建的最多了,其中风险还是建议了解下!!



更多实战分享和风险提示,请关注公众号:“中亦安图”和“中亦安图服务号”以及视频号:“小y-黄远邦-中亦”和“中亦安图”!也可以加小y微信,shadow-huang-bj,进微信群探讨技术。喜欢就转发吧,您的转发是我们持续分享的动力!



文章转载自中亦安图,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论