日常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);
}
......
}
总结
线上环境是错综复杂的,改表等运维操作也会导致出现意料之外的结果,很多组件的特性在一些特殊的情况下会被打破,所以防御式编程就显得尤其重要了。

日常Bug排查-改表时读数据不一致的更多相关文章
- 日常Bug排查-抛异常不回滚
日常Bug排查-抛异常不回滚 前言 日常Bug排查系列都是一些简单Bug排查,笔者将在这里介绍一些排查Bug的简单技巧,同时顺便积累素材_. Bug现场 最近有人反映java应用操作数据库的时候,抛异 ...
- 日常Bug排查-系统失去响应-Redis使用不当
日常Bug排查-系统失去响应-Redis使用不当 前言 日常Bug排查系列都是一些简单Bug排查,笔者将在这里介绍一些排查Bug的简单技巧,同时顺便积累素材_. Bug现场 开发反应线上系统出现失去响 ...
- 日常Bug排查-消息不消费
日常Bug排查-消息不消费 前言 日常Bug排查系列都是一些简单Bug排查,笔者将在这里介绍一些排查Bug的简单技巧,同时顺便积累素材_. Bug现场 某天下午,在笔者研究某个问题正high的时候.开 ...
- 日常Bug排查-Nginx重复请求?
日常Bug排查-Nginx重复请求? 前言 日常Bug排查系列都是一些简单Bug排查,笔者将在这里介绍一些排查Bug的简单技巧,其中不乏一些看起来很低级但很容易犯的问题. 问题现场 有一天运维突然找到 ...
- wordpress插件bug排查后记(记一次由于开启memecached引起的插件bug)
这篇文章是写给自己的. 周三的时候我在维护公司的一个wordpress项目页面时发现了一个非常奇怪的情况:当我尝试更新网站上的一个页面后,在wordpress后台的编辑器中发现其内容并没有按我预期的将 ...
- pt-osc改表导致数据不一致案例分析
2016-06-10 李丹 dba流浪猫 我们平时除了解决自己问题外,有时候也会协助圈内人士,进行一些故障排查,此案例就是帮某公司DBA进行的故障分析,因为比较典型,特分享一下,但仅仅是分享发生的过程 ...
- MySQL的数据类型,MySQL增删改--添加主外键、添加属性、删除主外键、改表名、获取系统当前时间等
ls /etc/rc.d/init.d/mysql56service mysql56 start ps aux |grep "mysql"|grep "socket=&q ...
- EF 外键不显示、如何让外键显示!增、删、改 操作时,外键不显示,只显示导航属性!
一.问题描述:EF 外键不显示.如何让外键显示!增.删.改 操作时,外键不显示,只显示导航属性! EF 添加.增加.插入数据时,外键不显示! 二.解决方案:在根据数据库生成模型的时候,选中“在模型中” ...
- mysql创建表时,设置timestamp DEFAULT NULL报错1067 - Invalid default value for 'updated_at'
问题背景: 线上的linux服务器上的mysql服务器中导出数据库的结构.想要在本地创建一个测试版本 导出后再本地mysql上运行却报错 1067 - Invalid default value ...
- 解决MySQL联表时出现字符集不一样
mysql 建表时都会设置表的字符集和排序规则,通常是 utf8,不过我这边习惯建表的字符集是 utf8mb4,排序规则是 utf8mb4_unicode_ci.有些 utf8mb4 的表默认排序规则 ...
随机推荐
- vue项目上线前优化(路由懒加载的使用,外部CDN的使用)
引 当使用vue做完项目后,接下来当然是要进行线上部署了.但是在上线之前还是可以做很多方面优化的,可以让项目上线后的体验更加哦. 若是使用了vue-cli的话,可以从面板界面直观的看到各项数据,控制台 ...
- 如何在 Linux 上部署 RabbitMQ
如何在 Linux 上部署 RabbitMQ 目录 如何在 Linux 上部署 RabbitMQ 安装 Erlang 从预构建的二进制包安装 从源代码编译 Erlang RabbitMQ 的安装 使用 ...
- 优秀的 Modbus 主站(主机、客户端)仿真器、串口调试工具
目录 优秀的 Modbus 主站(主机.客户端)仿真器.串口调试工具 主要功能 软件截图 优秀的 Modbus 主站(主机.客户端)仿真器.串口调试工具 modbus master,modbus,串口 ...
- 理解FPGA内部的同步信号、异步信号和亚稳态
FPGA(Field-Programmable Gate Array),即现场可编程门阵列.主要是利用内部的可编程逻辑实现设计者想要的功能.FPGA属于数字逻辑芯片,其中也有可能会集成一部分模拟电路的 ...
- .NET Emit 入门教程:第七部分:实战项目1:将 DbDataReader 转实体
前言: 经过前面几个部分学习,相信学过的同学已经能够掌握 .NET Emit 这种中间语言,并能使得它来编写一些应用,以提高程序的性能. 随着 IL 指令篇的结束,本系列也已经接近尾声,在这接近结束的 ...
- Tomcat+web测试环境配置
一.JDK安装完成 二.Tomcat安装配置 1:官网下载tomact,地址为:https://tomcat.apache.org/download-80.cgi 2:将下载后的文件解压 3:进入To ...
- element Tree 树形控件
文档地址 https://element.eleme.cn/#/zh-CN/component/tree 代码地址 https://gitee.com/wBekvam/vue-shop-admin/b ...
- 机器学习策略:详解什么时候该改变开发/测试集和指标?(When to change dev/test sets and metrics)
什么时候该改变开发/测试集和指标? 有时候在项目进行途中,可能意识到,目标的位置放错了.这种情况下,应该移动的目标. 来看一个例子,假设在构建一个猫分类器,试图找到很多猫的照片,向的爱猫人士用户展示, ...
- UnityShader数学基础篇
Mathf Mathf和Math 1.Math是C#中封装好的用于数学计算的工具类,位于System命名空间中. 2.Mathf是Unity中封装好的用于数学计算的工具结构体,位于UnityEngin ...
- MySQL之横纵表转换
sql行列转换(纵表和横表间的转换) 纵表 横表 纵转横 需要的知识点: group by,姓名有重复,使用这个按姓名进行分组 case...when...then...else...end 相当于s ...