这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

发布时间:2020-08-04 18:54:27 作者:记录每一次错误
来源:ITPUB博客 阅读:132

线上的生产系统,出现故障是大家都不愿意看到的。

但是当出现故障,造成业务影响,涉及事后追责的时候,

运维与开发,运维各团队之间,很容易出现相互推诿、各说各话的情况,

毕竟,故障责任这个锅,谁也不想背!

 

解决这类问题的最有效途径是什么呢?

答案是根因分析!

通常做到根因分析后,很容易重现并模拟故障,一切都会变得更加容易。

 

y 今天为大家分享这么一个案例 :

一支批量程序跑了好几年了,都好好的。

在没有掉电的情况下,突然某天再也跑不过去了!现象是数据库出现了坏块。

 

这个时候,有点“经验”的运维团队可能不自觉的哀叹,有点想主动背这个锅了。

别着急!问题分析,切忌遇到问题浅尝辄止,或者不要着急说,已经没法往下查了,

y 今天带着你,让我们一起来一次坏块根因分析之旅吧。

 

同时,文章最后会给出一个具体的 潜在的导致坏块的风险提示 ,读完本文,你就会发现,这个隐患可能长期潜伏,择日爆发。 希望大家早了解,早预防,避免踩坑。

 

Oracle 数据库问题,还是找不到原因?

不妨找中亦科技试试,我们将尽最大努力为您找到导致故障和性能问题的根本原因。


问题来了!


日期

ORA-08102: 未找到索引关键字, 对象号  64560, 文件 4, 块 192411 (2)

即索引和表数据不一致


2

常见的 ORA-8102 的原因有哪些呢?


Ø     IO 写丢失导致的表和索引数据不一致

Ø     ORACLE BUG 导致的表和索引不一致


可以删除索引重建么?--第一次头脑风暴


我相信绝大部分专家的解决方法:

都是以表中数据为准,把索引删除再创建!

 

但是在没找到问题原因之前, 删除并重建索引的做法是不负责任的做法

可能无法根本解决问题,以后很可能还会多次复发。

 

举例来说,重建索引是以表数据为准,但是既然表和索引的数据不一致,为什么不能是以索引数据为准来修正表的数据呢?比如当出现“表的写 IO 丢失,但是索引的写 IO 成功”的情况时,就不应该以表为基准重建索引!所以,虽然重建索引可能会解决问题,但是却可能导致业务数据丢失和破坏。

 

因此不可以盲目的按照索引方式来处理该问题,可能会导致数据丢失和破坏! 这个系统存储的是金融数据,我需要更谨慎。在没确定问题原因前,我暂时不会做出任何变更和调整的动作。

 

那么真的是出现了坏块么?我们需要深入分析,了解事情背后的真相!


思考时间 -- 坏块根因分析该如何继续


坏块原因该怎么分析?

到这里,读者朋友们不妨思考一下。

如果是你,你会怎么往下查坏块的成因呢?

 

别着急,多思考个三五分钟,问题原因就在后面,什么时候往下翻,由你决定…

.........

.........

.........

.........

.........

.........

.........

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

根因分析过程


这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

首先获取报错的SQL

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期



查看 alert 日志中出现的 ORA-8102 trace, 搜索 current sql, 可以发现, ORA-8102 错误是咋执行下列 SQL 语句时出现的

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

可以看到,该 SQL 实现的功能很简单:

TAB_XXX 表中满足条件的 BANKID 字段从 旧值 更新为 新值


这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

获取索引的定义

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期


根据报错信息中的对象号,检索 dba_objects, 可知出现问题的索引是 INDEX_TAB_XXX_FUNC , 进一步获取该索引的定义,如下所示:


这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

可以看到 :

这是一个特殊的索引,是一个使用了 用户自定义函数 创建的 函数索引 ,这与我们通常使用到的函数索引 trim to_number 等内置函数的函数索引有点不一样。


这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

取自定义函数的定义

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期



这里可以看到,用到了用户自定义的函数,自定义的函数代码如下:

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

这个函数实现的是 :

通过传入一个机构号,获取 TAB_ANOTHER_XXX 表当中的一级行的机构号。


这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

真相逐渐浮出水面

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期


到这里,小y脑子过了一次,基本上知道了问题的真相。读者朋友们不妨思考一下,问题真相是什么呢?

 

别着急,多思考个三五分钟,问题真相就在后面,什么时候往下翻,由你决定…

……

……

……

……

……

……

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

真相大白——成功反转

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

到这里,小y终于想通了, 这不是坏块,不是数据库的一个BUG,恰恰想法,而是应用程序的一个BUG!


当应用程序要执行下列SQL时

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

需要注意的是 :

报错 SQL 更新的表是 TAB_XXX 表,而函数索引中的自定义函数是取 TAB_ANOTHER_XXX 表的值, 这是两张不同的表,需要引起注意!

 

另外,函数指定了 deterministic 属性,即对于一个固定的 bankid, 函数的返回值, 从业务上要求都是固定的、唯一的

 

如果业务层面发生了变化,对于同一个 bankid, 他的 top 发生了变化,那必然会导致表和索引数据不一致!

 

具体来说,问题发生过程如下

由于要更新bankid字段,而该字段上存在索引,因此ORACLE除了要更新数据BLOCK中的bankid字段外,由于bankid字段被更新,而bankid字段上又存在一个用户自定义函数索引,因此需要更新函数索引中的键值。

 

为此,oracle首先要找到bankid=:b1在函数索引中的位置,即找到keyvalue= FUNC_GET_TOP(:b1),并且该索引条目中的rowid=被修改的数据块中的行。

 

如果此时运行FUNC_GET_TOP(:b1)函数的结果与创建索引时运行FUNC_GET_TOP(:b1)后计算的结果不一致,那么是肯定找不到满足索引数据的。 即函数索引中的函数值是不确定的,那么显然ORACLE是找不到” keyvalue= FUNC_GET_TOP(:b1)&索引entry中的rowid=被修改的数据块中的行”的。那么就会报ORA-08102:未找到索引关键字。

 

这里FUNC_GET_TOP函数是在另外一张TAB_ANOTHER_XXX表中根据bankid取top_bank_id字段的值,如果bankid=:b1的top_bank_id值发生了改变,那么FUNC_GET_TOP(:b1)的结果就发生了改变,那么对于一个传入的bankid,两次计算出来的结果是不确定的,即索引键值是不固定的。就不满足函数索引中对自定义函数要求deterministic属性(对于每个输入,函数的输出结果是固定唯一的)。


这就是这个故障的本质原因


这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

为什么以前不出现??

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期


下属某市级行原本属于某省行(一级行),但是由于该市级行业务量大,后来成为了一级行,意味着网点机构的一级行从省级行变成了市级行。这是在年初时候就调整了的。意味着表 TAB_ANOTHER_XXX发生了变化,但是TAB_XXX表还没有变化

 

在故障日(半年后)中,市级行的下属行第一次进行了机构撤并,即修改 bank_id, 此时 TAB_XXX 表开始变化 ,因此需要同步维护索引,即修改函数索引中的对应键值,也就触发了这个问题。年初调整了 bankid top_bank_id, 但那时还不需要维护索引,因此年初调整机构不会引发问题。过了半年多,才出现问题。


这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

问题模拟重现

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期

原理解释:

当把 tb1 表的 id=1 更新为 id=12 时,数据块上已经完成了更新(执行计划为全表扫描, id 字段上无索引),此时还需要更新 id 字段上的函数索引 fc1(id)

 

而要更新 id 字段上的函数索引,需要先执行 fc1(1) 做为键值去查找,结果显然是 111( 新的 top_id), 此时索引中显然找不到“键值 =111 rowid 指向数据库 slot 的一个 entry ”,因此报 ORA-8102 ,未找到索引关键字。表现上是数据与索引不一致, 实则是业务上无法保证自定义函数索引返回固定值导致。


问题解决


综上所述,因为了解的整个问题的发生过程,那么重建索引是安全的! 不会导致数据的丢失和破坏!

 

在与应用团队以及开发项目组反馈了问题的本质原因后,应用团队组织人员在测试环境进行了测试,临时修改了某些机构的顶级机构号,再次重提批量,问题得到重现,报错 ORA-8102

 

由于短期不会再有更新一级行的变更,因此临时解决方式是重建索引。

后面如果还有类似修改一级行的需求,则从流程上再次重建索引。技术上,去掉该函数索引,使用子查询的方式代替即可。

 

最后,应用团队提交了重建索引的变更,批量顺利完成 , 问题得到圆满解决 , 并且找到了根因。


风险提示


风险提示:

使用自定义函数索引时,需要保证,对于自定义函数的一个确定的输入值 I_1 ,他的输出值 O_1 从业务上必须保证是唯一的。否则会导致索引和数据不一致, ORA-8102 的错误。该类问题比较隐蔽,可能会潜伏一段时间,当对于自定义函数的一个确定的输入值 I_1 ,他的输出值从 O_1   变为 O_2 时,问题出现。

 

因此,我们建议:

1)         对使用用户自定义函数索引的情况进行排查,并与应用团队一起,从业务上确定对于一个传入值,自定义函数是否可以返回固定的、唯一的值。

2)         将自定义函数索引的风险传递到开发中心,进行普及,在开发阶段杜绝该类问题。

 

如何检查自己的系统中是否存在类似问题呢


1

首先检查所有应用程序的函数索引


这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期


输出结果如下所示 , 其中的 DATA_DEFAULT 字段就是函数索引的定义,对 trim/to_number/to_char 等自定义函数进行排除后,就是自定义函数索引

这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期


2

然后使用下列命令来获取函数定义


这个锅,我们运维,不背! 开发和运维都该看看--技术人生系列第四十三期


执行时,需将上述命令中的函数名和用户替换为上述查询分析结果中的函数和用户。


3

最后对自定义函数的内容进行梳理


与应用团队确认该函数是否可以保证对于每个输入,函数的返回值是固定的、唯一的。如果无法确认,则需要进行整改,避免后续业务变化导致的故障,影响业务连续性。



推荐阅读:
  1. 不做背锅侠,运维工作要点指南
  2. 让运维不再背锅的利器jumpserver堡垒机

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

三期 不背 人生

上一篇:C++--模板的概念和意义、深入理解函数模板、类模板的概念和意义

下一篇:详述Linux系统中Nginx虚拟主机的配置

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》