日常Bug排查-改表时读数据不一致
日常Bug排查-改表时读数据不一致
前言
日常Bug排查系列都是一些简单Bug的排查。笔者将在这里介绍一些排查Bug的简单技巧,同时顺便积累素材。
Bug现场
线上连续两天出现NP异常,而且都是凌晨低峰期才出现,在凌晨的流量远没有白天高峰期大。而出问题的接口又是通常的业务请求。于是,很自然的,我们就想凌晨有什么特殊的运维动作,翻了下时间线。发现,每天凌晨都会进行改表,而修改的这张表恰好就是出现NP异常的表。如下图所示:
在此解释下业务的相关场景。A表是主表,B表是子表,两者都是严格保证在一个事务内一块插入和更新的,在该表时刻确出现了在一个事务内查询,能查到A确查不到B的现象。
思路
数据库的一个核心特性就是原子性,看上去这个场景破坏了原子性。但是由于是和改表强相关,其它时间没有类似错误。那么很明显的,思路就会指向该表这个动作会短暂的破坏原子性。由于线上使用的ghost进行改表,于是笔者就看了下ghost改表的原理:
ghost会创建一个影子表,在影子表上完成alter改表,然后分批将全量数据应用到新表。 同时在处理增量数据的时候,通过解析binLog事件,将任务期间的新增数据应用到新表。 最后一步,通过Rename语句使新表替换老表
从这个原理中可以推断,最后一步Rename的时候才会对当前的SQL产生影响,是不是刚好这个这个Rename操作短暂的使读数据不一致了呢?看了下DBA那边的改表日志,发现Rename那个时刻和NP异常出现时刻完全吻合。看来它就是罪魁祸首了。
为什么Rename会导致读数据不一致?
笔者稍加思索就明白了原因。首先,线上库的隔离级别是RR的,也就是可重复读。而Alter表的时候势必会有一张旧表B和新表BNew。业务的事务保证是操作在A和B上的,而读数据不一致应该是A和BNew上,所以无法保证A和BNew的一致。只能通过binLog的重放保证最终一致。 那么最终导致问题的原因就很明显了,如下所示:
BNew新表通过ghost的binlog重放将原B表中相关的binLog重放到BNew表中。但是在事务T2开始的时候BNew这张表中新纪录B还没有被重放。在事务T2开始的时候首先查询了A表建立了MVCC视图,这时候的数据库实际快照就是A表有A,BNew表没有B。尽管在Rename表的时候MySQL会对B和BNew都进行锁表,这时候所有对于这两张表的访问都会等锁表的结束。但是由于RR的原因,这个事务内后续读BNew表的时候始终就是A表有A,BNew表没有B这样的现象。在后续的查询中select B查询的实际上是BNew表,进而产生了数据不一致,进而导致了NullPointerException。
测试复现实验的一个小问题
还有一个小问题,就是笔者在线下设计相关实验复现问题的时候。这个复现的实验看上去是比较容易的,模拟一下事务顺序,新建一张BNew表然后Rename下,看看现象是否一致就可以了,如下图所示:
但笔者发现,在Rename的时候,模拟的请求2在做select 新B表的时候始终会出现
Table definition has changed,please retry transaction
这个报错。于是笔者看了下MySQL的源代码,要想让Rename不报错,必须在模拟的请求2事务开始之前就创建这个BNew表,否则请求2在查询BNew表的时候就会由于找不到UndoHistory导致报错。MySQL源代码如下所示:
row_vers_old_has_index_entry(......){ ...... /* If purge can't see the record then we can't rely on the UNDO log record. */ bool purge_sees = trx_undo_prev_version_build( rec, mtr, version, index, *offsets, heap, &prev_version, NULL, vrow, 0); // 在这边,如果找不到这张表在t1前的undo history的话,则会报"Table definition has changed, please retry transaction" err = (purge_sees) ? DB_SUCCESS : DB_MISSING_HISTORY; if (prev_heap != NULL) { mem_heap_free(prev_heap); } ...... }
总结
线上环境是错综复杂的,改表等运维操作也会导致出现意料之外的结果,很多组件的特性在一些特殊的情况下会被打破,所以防御式编程就显得尤其重要了。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
HDC 2024 | 华为云云原生开源+OpenHarmony,加速开发者应用创新
6月21日-23日,华为开发者大会2024在东莞松山湖顺利举办。大会期间,华为云开源立足开源视角、联动应用前端、微服务、中间件、数据库、云原生、基础设施、端云协同等多个技术领域,邀请内外部专家通过专题论坛进行深度技术解读和实践案例分享,结合生动有趣的实操活动、1V1产品体验与交流,汇聚全国各地的开发者与伙伴,共同探讨技术开源的持续创新与生态发展。 全栈开源,加速开发者应用创新 近年来,随着云原生技术的迅速发展和社区生态的不断完善,开源已经成为了企业和开发者实现应用和业务创新的基石。在本次HDC 2024期间,华为云开源专题论坛从开源策略、技术与生态、客户实践以及基础设施四大维度向参会者进行了解读。 邓明昆,华为云开源业务总经理 华为云开源业务总经理邓明昆在论坛中提出了应用全面现代化的重要性,同时也向外界传递了华为云开源全栈开源的长期策略:华为云将坚持开源开放、协同创新,基于华为云多年的技术积累和软件工程经验,逐步覆盖应用框架、中间件、数据库、云原生基础设施、研发工具等,最终实现云原生全栈技术开源和整合能力,使能云原生应用的全生命周期,助力开发者快速实现应用和业务的创新,加速企业数字化转...
- 下一篇
Percona Toolkit 神器全攻略(配置类)
Percona Toolkit 神器全攻略(配置类) Percona Toolkit 神器全攻略系列共八篇,前文回顾: 前文回顾 Percona Toolkit 神器全攻略 Percona Toolkit 神器全攻略(实用类) 全文约定:$为命令提示符、greatsql>为GreatSQL数据库提示符。在后续阅读中,依据此约定进行理解与操作 配置类 在Percona Toolkit中配置类共有以下工具 pt-config-diff:比较数据库配置文件和参数 pt-mysql-summary:对GreatSQL/MySQL配置和status进行汇总 pt-variable-advisor:分析参数,并提出建议 pt-config-diff 概要 比较 GreatSQL/MySQL 配置文件和服务器变量 用法 pt-config-diff [OPTIONS] CONFIG CONFIG [CONFIG...] 选项 该工具所有选项如下 参数 含义 --ask-pass 连接 GreatSQL/MySQL 时提示输入密码 --charset 字符集 --config 读取这个逗号分隔的...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS关闭SELinux安全模块
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- 2048小游戏-低调大师作品
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Docker安装Oracle12C,快速搭建Oracle学习环境
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果