《MySQL排错指南》——1.5 追踪数据中的错误

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介:

本节书摘来自异步社区出版社《MySQL排错指南》一书中的第1章,第1.5节,作者:【美】Sveta Smirnova(斯维特 斯米尔诺娃),更多章节内容可以访问云栖社区“异步社区”公众号查看。

1.5 追踪数据中的错误

如果你严格地检查查询和更新的结果,就会发现很多问题,这些问题可能持续数周而未被察觉,然后慢慢变得越来越严重,直到最后无可避免地引发很多让人苦恼的问题。然而,问题确实在慢慢地接近你。有时,SELECT查询突然开始返回错误的结果,但是你对该查询的经验使你确信它没有什么问题。

在这种情况下,你应该反向模拟用户操作,直到发现错误的根源。如果幸运,你会一步就发现问题的原因。不过通常你会进行多步操作,有时甚至消耗很长的时间。

大部分这种问题是由于复制环境中主从节点的数据不一致造成的。一个常见的错误情形是期望唯一值的时候出现了重复值(例如,如果用户使用INSERT ON DUPLICATE KEY UPDATE语句,但是主从服务器中的表结构是不同的)。在这样的环境设置下,用户往往直到从节点执行SELECT语句的时候才会发现问题,而不会在INSERT发生时就注意到问题。在循环复制时情况会更糟糕。

为了说明这个问题,我们将使用一个存储过程从保存其他查询结果的临时表向另一个表插入数据。这是另一个常用技巧,用于当用户想要处理大表中的数据,同时担心意外修改数据的风险,或者担心在使用这些大表时对其他应用造成堵塞的情形。

我们来创建表并填充临时数据。在实际应用中,临时表会用于保存等待存入主表的计算结果集:

image

现在向临时表中插入数据:

image

存储例程将临时表中的数据移入主表。它在迁移前会先确认数据在临时表中。我们的版本如下:

image

在调用该存储例程时,如果指定的临时表不存在则会创建新的临时表。这样做可以避免由于临时表不存在而产生问题,但同时也会带来新问题。

提示 提示

该示例使用MAX函数检查表中是否至少存在一行记录。推荐用MAX计数,因为InnoDB表不会保存记录的行数,而是在每次调用COUNT函数的时候现进行计算。因此,MAX(indexed_field)函数比COUNT快。
如果从服务器在第一个插入之后,存储过程调用之前重启,那么从服务器中的临时表将会是空的并且从服务器上的主表没有任何数据。在这种情况下,我们访问主节点会得到:
image

与此同时,在从服务器上得到:

image

更糟的是,如果我们在存储过程调用后向t1表中插入数据,从服务器中的数据将会完全混乱。

假设我们注意到应用程序中主从表读取数据时的错误。现在我们应该弄清数据是怎么插入从表的:是直接更新从服务器还是从主节点复制的数据?

警告 警告

MySQL复制不会帮你检查数据一致性,因此对同一个对象,同时使用SQL复制线程和从节点上的用户线程更新会使数据与主服务器不同,这会导致随后的复制事件失败。
因为我们在示例中模拟这种情形,所以我们知道发生数据损坏问题的关键点:从服务器在第一次插入之后,存储过程调用之前重启了。在实际场景中,问题一般会在用户执行下面查询的时候被发现:

image

当你从SELECT查询中获得非预期结果时,你需要找出该问题发生的原因,是由于查询本身的问题,还是由于早些时候的一些错误引起的。刚才展示的插入非常简单,除非表损坏了,否则它不可能产生错误,因此我们必须回头检查一下表是如何修改的。

通常的示例是在建立在从服务器只读的复制环境下,因此我们可以确保错误产生有两种可能的原因:要么是主服务器插入了错误的数据,要么是数据在复制时损坏。

所以,首先检查主服务器的数据是否有错误:

image

主服务器数据正常,因此问题的原因在于复制层。然而,这是怎么发生的?复制看起来运行正常[3],因此我们猜想是主节点有逻辑错误。当发现了这个可能的原因的时候,你应该去分析存储过程并在主节点上调用以寻找修复方案。

如前所述,在向临时表中插入数据完成复制并清空临时表的事件之后,且在调用查询并向主表插入数据的存储过程之前,重启服务器。因此,从服务器仅是重新创建一个空的临时表并且没有插入任何数据。

在这种情况下,可以选择转换成基于行的复制或者重写存储过程,使其不依赖于已经存在的临时表。另一种方法是清空然后重新填充表,这样突然重启不会导致从服务器数据丢失。

有人可能觉得这个示例太过人为了,你不可能预知服务器何时会突然重启。没错,但是重启确实每时每刻都有可能发生。因此,你需要考虑这样的错误。

事实上,从服务器一个接一个地复制二进制日志事件,当数据在一个原子事件(例如,一个事务或者存储过程调用)中产生时,从服务器不会受上述情况的影响。不过回过头来说,这个示例仅仅是为了说明现实生活中发生的事件背后的原理。

当你在明知是正确的情况下遇到了一个问题时,请检查在看到这个问题之前你的应用程序的运行情况。
关于复制错误的更多详细信息会在第5章进行介绍。

单服务器示例

我曾经处理过一个存储由不同的切割系统产生的度量数据的Web应用程序。用户可以添加一个系统,然后编辑保存度量数据的规则。

我第一次遇到错误的时候,我测试了一个含有系统列表的Web页面:
image

  

该列表不该包含重复的系统,因为描述同样的规则两次是没有意义的。因此我非常惊奇地看到有很多同名的条目。

输出数据的代码使用的是对象,并且,我无法仅通过阅读代码查看发送到MySQL服务器的语句是什么样的:

image
  

我通过日志获取了真实的查询,它看来是正确的:
image

  

接下来,我检查了表的内容:
image

  

SELCET语句准确地返回了表中存在的数据集。我转而检查更新表的代码:

image
  

我再次通过日志获取真实的请求语句:
image

  

该语句也是正确的!id似乎是自增的字段,因此会自动设置。

不过同时,该语句也暴露了潜在的问题:它一定在没有检查唯一性的情况下重复执行了。带着这种假设,我决定检查一下表的定义:

image
  

问题的源头很显然了:name字段没有定义成UNIQUE(唯一的)。当创建表的时候,我通常使用id作为唯一标识符,但是我也会使用MySQL的特性在INSERT时去给id生成一个自增的值,没有什么使我避免重复使用同一个name。

为了解决这个问题,我手动删除了多余的行并且增加了唯一(UNIQUE)索引。

image
  
我们已经介绍完了与错误结果相关的问题,接下来将介绍其他一些经常发生的问题。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
27天前
|
关系型数据库 MySQL Java
【MySQL+java+jpa】MySQL数据返回项目的感悟
【MySQL+java+jpa】MySQL数据返回项目的感悟
39 1
|
28天前
|
SQL 监控 关系型数据库
MySQL怎么全局把一张表的数据回滚
MySQL怎么全局把一张表的数据回滚
72 2
|
28天前
|
存储 SQL 关系型数据库
MySQL批量添加数据并取外表的某个字段值
MySQL批量添加数据并取外表的某个字段值
58 1
|
12天前
|
安全 关系型数据库 MySQL
如何将数据从MySQL同步到其他系统
【10月更文挑战第17天】如何将数据从MySQL同步到其他系统
78 0
|
18天前
|
SQL 前端开发 关系型数据库
全表数据核对 ,行数据核对,列数据核对,Mysql 8.0 实例(sample database classicmodels _No.3 )
全表数据核对 ,行数据核对,列数据核对,Mysql 8.0 实例(sample database classicmodels _No.3 )
34 0
全表数据核对 ,行数据核对,列数据核对,Mysql 8.0 实例(sample database classicmodels _No.3 )
|
23天前
|
关系型数据库 MySQL 数据库
mysql 里创建表并插入数据
【10月更文挑战第5天】
98 1
|
25天前
|
分布式计算 关系型数据库 MySQL
大数据-88 Spark 集群 案例学习 Spark Scala 案例 SuperWordCount 计算结果数据写入MySQL
大数据-88 Spark 集群 案例学习 Spark Scala 案例 SuperWordCount 计算结果数据写入MySQL
45 3
|
8天前
|
SQL Java 关系型数据库
java连接mysql查询数据(基础版,无框架)
【10月更文挑战第12天】该示例展示了如何使用Java通过JDBC连接MySQL数据库并查询数据。首先在项目中引入`mysql-connector-java`依赖,然后通过`JdbcUtil`类中的`main`方法实现数据库连接、执行SQL查询及结果处理,最后关闭相关资源。
|
5天前
|
SQL 关系型数据库 MySQL
定时任务频繁插入数据导致锁表问题 -> 查询mysql进程
定时任务频繁插入数据导致锁表问题 -> 查询mysql进程
15 1
|
6天前
|
SQL 关系型数据库 MySQL
mysql数据误删后的数据回滚
【11月更文挑战第1天】本文介绍了四种恢复误删数据的方法:1. 使用事务回滚,通过 `pymysql` 库在 Python 中实现;2. 使用备份恢复,通过 `mysqldump` 命令备份和恢复数据;3. 使用二进制日志恢复,通过 `mysqlbinlog` 工具恢复特定位置的事件;4. 使用延迟复制从副本恢复,通过停止和重启从库复制来恢复数据。每种方法都有详细的步骤和示例代码。

推荐镜像

更多