MySQL原理探索——29 如何判断一个数据库是不是出问题了

news2024/12/23 16:31:45

在第25和27篇文章中,介绍了主备切换流程。通过这些内容的讲解,你应该已经很清楚了:在一主一备的双 M 架构里,主备切换只需要把客户端流量切到备库;而在一主多从架构里,主备切换除了要把客户端流量切到备库外,还需要把从库接到新主库上。
主备切换有两种场景,一种是主动切换,一种是被动切换。而其中被动切换,往往是因为主库出问题了,由 HA 系统发起的。
这也就引出了我们今天要讨论的问题:怎么判断一个主库出问题了?
你一定会说,这很简单啊,连上 MySQL,执行个 select 1 就好了。但是 select 1 成功返回了,就表示主库没问题吗?

select 1 判断

实际上,select 1 成功返回,只能说明这个库的进程还在,并不能说明主库没问题。现在,我们来看一下这个场景。

set global innodb_thread_concurrency=3;

CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;

insert into t values(1,1)

我们设置 innodb_thread_concurrency 参数的目的是,控制 InnoDB 的并发线程上限。也就是说,一旦并发线程数达到这个值,InnoDB 在接收到新请求的时候,就会进入等待状态,直到有线程退出。
这里,我把 innodb_thread_concurrency 设置成 3,表示 InnoDB 只允许 3 个线程并行执行。而在我们的例子中,前三个 session 中的 sleep(100),使得这三个语句都处于“执行”状态,以此来模拟大查询。
你看到了, session D 里面,select 1 是能执行成功的,但是查询表 t 的语句会被堵住。也就是说,如果这时候我们用 select 1 来检测实例是否正常的话,是检测不出问题的。
在 InnoDB 中,innodb_thread_concurrency 这个参数的默认值是 0,表示不限制并发线程数量。但是,不限制并发线程数肯定是不行的。因为,一个机器的 CPU 核数有限,线程全冲进来,上下文切换的成本就会太高。
所以,通常情况下,我们建议把 innodb_thread_concurrency 设置为 64~128 之间的值。这时,你一定会有疑问,并发线程上限数设置为 128 够干啥,线上的并发连接数动不动就上千了。
产生这个疑问的原因,是搞混了并发连接和并发查询。
并发连接和并发查询,并不是同一个概念。你在 show processlist 的结果里,看到的几千个连接,指的就是并发连接。而“当前正在执行”的语句,才是我们所说的并发查询。
并发连接数达到几千个影响并不大,就是多占一些内存而已。我们应该关注的是并发查询,因为并发查询太高才是 CPU 杀手。这也是为什么我们需要设置innodb_thread_concurrency 参数的原因。
然后,你可能还会想起我们在第 7 篇文章中讲到的热点更新和死锁检测的时候,如果把innodb_thread_concurrency 设置为 128 的话,那么出现同一行热点更新的问题时,是不是很快就把 128 消耗完了,这样整个系统是不是就挂了呢?
实际上,在线程进入锁等待以后,并发线程的计数会减一,也就是说等行锁(也包括间隙锁)的线程是不算在 128 里面的。
MySQL 这样设计是非常有意义的。因为,进入锁等待的线程已经不吃 CPU 了;更重要的是,必须这么设计,才能避免整个系统锁死。
为什么呢?假设处于锁等待的线程也占并发线程的计数,你可以设想一下这个场景:
1. 线程 1 执行 begin; update t set c=c+1 where id=1, 启动了事务 trx1, 然后保持这个状态。这时候,线程处于空闲状态,不算在并发线程里面。
2. 线程 2 到线程 129 都执行 update t set c=c+1 where id=1; 由于等行锁,进入等待状态。这样就有 128 个线程处于等待状态;
3. 如果处于锁等待状态的线程计数不减一,InnoDB 就会认为线程数用满了,会阻止其他语句进入引擎执行,这样线程 1 不能提交事务。而另外的 128 个线程又处于锁等待状态,整个系统就堵住了。
下图 2 显示的就是这个状态。

 这时候 InnoDB 不能响应任何请求,整个系统被锁死。而且,由于所有线程都处于等待状态,此时占用的 CPU 却是 0,而这明显不合理。所以,我们说 InnoDB 在设计时,遇到进程进入锁等待的情况时,将并发线程的计数减 1 的设计,是合理而且是必要的。
虽然说等锁的线程不算在并发线程计数里,但如果它在真正地执行查询,就比如我们上面例子中前三个事务中的 select sleep(100) from t,还是要算进并发线程的计数的。
在这个例子中,同时在执行的语句超过了设置的 innodb_thread_concurrency 的值,这时候系统其实已经不行了,但是通过 select 1 来检测系统,会认为系统还是正常的。
因此,我们使用 select 1 的判断逻辑要修改一下。

查表判断

为了能够检测 InnoDB 并发线程数过多导致的系统不可用情况,我们需要找一个访问InnoDB 的场景。一般的做法是,在系统库(mysql 库)里创建一个表,比如命名为health_check,里面只放一行数据,然后定期执行:

mysql> select * from mysql.health_check; 

使用这个方法,我们可以检测出由于并发线程过多导致的数据库不可用的情况。
但是,我们马上还会碰到下一个问题,即:空间满了以后,这种方法又会变得不好使。
我们知道,更新事务要写 binlog,而一旦 binlog 所在磁盘的空间占用率达到 100%,那么所有的更新语句和事务提交的 commit 语句就都会被堵住。但是,系统这时候还是可以正常读数据的。
因此,我们还是把这条监控语句再改进一下。接下来,我们就看看把查询语句改成更新语句后的效果。

更新判断

既然要更新,就要放个有意义的字段,常见做法是放一个 timestamp 字段,用来表示最后一次执行检测的时间。这条更新语句类似于:

mysql> update mysql.health_check set t_modified=now();

节点可用性的检测都应该包含主库和备库。如果用更新来检测主库的话,那么备库也要进行更新检测。
但,备库的检测也是要写 binlog 的。由于我们一般会把数据库 A 和 B 的主备关系设计为双 M 结构,所以在备库 B 上执行的检测命令,也要发回给主库 A。
但是,如果主库 A 和备库 B 都用相同的更新命令,就可能出现行冲突,也就是可能会导致主备同步停止。所以,现在看来 mysql.health_check 这个表就不能只有一行数据了。
为了让主备之间的更新不产生冲突,我们可以在 mysql.health_check 表上存入多行数据,并用 A、B 的 server_id 做主键。

mysql> CREATE TABLE `health_check` (
`id` int(11) NOT NULL,
`t_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;

/* 检测命令 */
insert into  mysql.health_check(id, t_modified) values (@@server_id, now()) on duplicate  key update t_modified=now();

由于 MySQL 规定了主库和备库的 server_id 必须不同(否则创建主备关系的时候就会报错),这样就可以保证主、备库各自的检测命令不会发生冲突。
更新判断是一个相对比较常用的方案了,不过依然存在一些问题。其中,“判定慢”一直是让 DBA 头疼的问题。
你一定会疑惑,更新语句,如果失败或者超时,就可以发起主备切换了,为什么还会有判定慢的问题呢?
其实,这里涉及到的是服务器 IO 资源分配的问题。
首先,所有的检测逻辑都需要一个超时时间 N。执行一条 update 语句,超过 N 秒后还不返回,就认为系统不可用。
你可以设想一个日志盘的 IO 利用率已经是 100% 的场景。这时候,整个系统响应非常慢,已经需要做主备切换了。
但是你要知道,IO 利用率 100% 表示系统的 IO 是在工作的,每个请求都有机会获得 IO资源,执行自己的任务。而我们的检测使用的 update 命令,需要的资源很少,所以可能在拿到 IO 资源的时候就可以提交成功,并且在超时时间 N 秒未到达之前就返回给了检测系统。
检测系统一看,update 命令没有超时,于是就得到了“系统正常”的结论。
也就是说,这时候在业务系统上正常的 SQL 语句已经执行得很慢了,但是 DBA 上去一看,HA 系统还在正常工作,并且认为主库现在处于可用状态。
之所以会出现这个现象,根本原因是我们上面说的所有方法,都是基于外部检测的。外部检测天然有一个问题,就是随机性。
因为,外部检测都需要定时轮询,所以系统可能已经出问题了,但是却需要等到下一个检测发起执行语句的时候,我们才有可能发现问题。而且,如果你的运气不够好的话,可能第一次轮询还不能发现,这就会导致切换慢的问题。
所以,接下来我要再和你介绍一种在 MySQL 内部发现数据库问题的方法。

内部统计

针对磁盘利用率这个问题,如果 MySQL 可以告诉我们,内部每一次 IO 请求的时间,那我们判断数据库是否出问题的方法就可靠得多了。
其实,MySQL 5.6 版本以后提供的 performance_schema 库,就在file_summary_by_event_name 表里统计了每次 IO 请求的时间。
file_summary_by_event_name 表里有很多行数据,我们先来看看event_name='wait/io/file/innodb/innodb_log_file'这一行。

图中这一行表示统计的是 redo log 的写入时间,第一列 EVENT_NAME 表示统计的类型。
接下来的三组数据,显示的是 redo log 操作的时间统计。
第一组五列,是所有 IO 类型的统计。其中,COUNT_STAR 是所有 IO 的总次数,接下来四列是具体的统计项, 单位是皮秒;前缀 SUM、MIN、AVG、MAX,顾名思义指的就是总和、最小值、平均值和最大值。
第二组六列,是读操作的统计。最后一列 SUM_NUMBER_OF_BYTES_READ 统计的是,总共从 redo log 里读了多少个字节。
第三组六列,统计的是写操作。
最后的第四组数据,是对其他类型数据的统计。在 redo log 里,你可以认为它们就是对fsync 的统计。
在 performance_schema 库的 file_summary_by_event_name 表里,binlog 对应的是event_name = "wait/io/file/sql/binlog"这一行。各个字段的统计逻辑,与 redo log 的各个字段完全相同。这里,我就不再赘述了。
因为我们每一次操作数据库,performance_schema 都需要额外地统计这些信息,所以我们打开这个统计功能是有性能损耗的。
我的测试结果是,如果打开所有的 performance_schema 项,性能大概会下降 10% 左右。所以,我建议你只打开自己需要的项进行统计。你可以通过下面的方法打开或者关闭某个具体项的统计。
如果要打开 redo log 的时间监控,你可以执行这个语句:

mysql>update setup_instruments set ENABLED='YES', Timed='YES' where name like '%wait/io/file/innodb/innodb_log_file%';

假设,现在你已经开启了 redo log 和 binlog 这两个统计信息,那要怎么把这个信息用在实例状态诊断上呢?
很简单,你可以通过 MAX_TIMER 的值来判断数据库是否出问题了。比如,你可以设定阈值,单次 IO 请求时间超过 200 毫秒属于异常,然后使用类似下面这条语句作为检测逻辑。

mysql> select  event_name,MAX_TIMER_WAIT  FROM performance_schema.file_summary_by_event_name where event_name in  ('wait/io/file/innodb/innodb_log_file','wait/io/file/sql/binlog') and  MAX_TIMER_WAIT>200*1000000000;

 发现异常后,取到你需要的信息,再通过下面这条语句:

mysql> truncate table performance_schema.file_summary_by_event_name;

把之前的统计信息清空。这样如果后面的监控中,再次出现这个异常,就可以加入监控累积值了。

小结

今天,介绍了检测一个 MySQL 实例健康状态的几种方法,以及各种方法存在的问题和演进的逻辑。
你看完后可能会觉得,select 1 这样的方法是不是已经被淘汰了呢,但实际上使用非常广泛的 MHA(Master High Availability),默认使用的就是这个方法。
MHA 中的另一个可选方法是只做连接,就是 “如果连接成功就认为主库没问题”。不过据我所知,选择这个方法的很少。
其实,每个改进的方案,都会增加额外损耗,并不能用“对错”做直接判断,需要你根据业务实际情况去做权衡。
我个人比较倾向的方案,是优先考虑 update 系统表,然后再配合增加检测performance_schema 的信息。


补充:
如果使用 GTID 等位点的方案做读写分离,在对大表做 DDL 的时候会怎么样?
假设,这条语句在主库上要执行 10 分钟,提交后传到备库就要 10 分钟(典型的大事务)。那么,在主库 DDL 之后再提交的事务的 GTID,去备库查的时候,就会等 10 分钟才出现。
这样,这个读写分离机制在这 10 分钟之内都会超时,然后走主库。
这种预期内的操作,应该在业务低峰期的时候,确保主库能够支持所有业务查询,然后把读请求都切到主库,再在主库上做 DDL。等备库延迟追上以后,再把读请求切回备库。
通过这个思考题,我主要想让关注的是,大事务对等位点方案的影响。
当然了,使用 gh-ost 方案来解决这个问题也是不错的选择。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/746018.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux:rsync+inotify实时同步

首先要客户机向服务器单次下载 而实时同步是向服务器实时上传 首先要实现单次下载,本章基于下面这章的续作 Linux:rsync_鲍海超-GNUBHCkalitarro的博客-CSDN博客 准备一个inotify-tools源码包 服务器配置 vim /etc/rsyncd.conf read only no setfa…

msfconsole

msfconsole 文章目录 msfconsole安装使用 msfconsole Msfconsole是Metasploit框架的主要控制台界面。它提供了一个命令行界面来与Metasploit框架进行交互,并允许用户执行各种渗透测试任务。Msfconsole是Metasploit的核心组件之一,它充当了一个交互式命令…

基于霍夫变换的航迹起始算法研究(Matlab代码实现)

目录 💥1 概述 📚2 运行结果 🎉3 参考文献 👨‍💻4 Matlab代码 💥1 概述 一、设计内容 利用Hough变换处理量测得到的含杂波的二维坐标,解决多目标航迹起始问题。使用Matlab进行仿真&#x…

设计模式详解(一):工厂方法——Factory Method

目录导航 工厂方法及其作用工厂方法的好处工厂方法的实现关系图实现步骤 工厂方法的适用场景工厂方法举例 工厂方法及其作用 工厂方法是一种创建型设计模式。所谓创建型设计模式是说针对创建对象方面的设计模式。在面向对象的编程语言里,我们通过对象间的相互协作&…

【聚类算法】密度峰值聚类算法DPC(Density Peak Clustering Algorithm)

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 密度峰值聚类算法(Density Peak Clustering Algorithm),能够自动发现数据中的密度峰值点,并根据峰值点将数…

Java:创建文件夹并输出内容到文件中

前言 在实际开发当中我们往往会遇到需要用 Java 代码来进行在我们项目的部署的服务器上创建一个文件并向里面写入想要的数据,下面就是具体的实现方式,希望对小伙伴们有用。 代码 这里我把这个操作封装成了一个工具类,方便根据不同业务场景…

中国品牌在海外做独立站有什么优势?

从人口红利驱动转向消费升级驱动——品牌出海行业的变局正在上演,低价不再是适用于所有出海企业的策略。从产品出海走向真正意义上的品牌出海,正在成为一部分中国商家的选择。 无论是做品牌还是做全球化,都不容易。消费者购买的不止产品&…

从零开始 verilog 以太网交换机(六)帧处理单元设计与实现

从零开始 verilog 以太网交换机(六)帧处理单元设计与实现 🔈声明: 😃博主主页:王_嘻嘻的CSDN主页 🧨 从零开始 verilog 以太网交换机系列专栏:点击这里 🔑未经作者允许&a…

Android Java代码与JNI交互 JNI访问Java构造方法(九)

🔥 Android Studio 版本 🔥 🔥 创建包含JNI的类 JNIConstructorClass.java 🔥 package com.cmake.ndk1.jni;import com.cmake.ndk1.model.Animal;public class JNIConstructorClass {static {System.loadLibrary("constructor-class-lib");}public native…

深度学习——CNN卷积神经网络

基本概念 概述 卷积神经网络(Convolutional Neural Network,CNN)是一种深度学习中常用于处理具有网格结构数据的神经网络模型。它在计算机视觉领域广泛应用于图像分类、目标检测、图像生成等任务。 核心思想 CNN 的核心思想是通过利用局部…

环形链表(快慢指针)

给你一个链表的头节点 head ,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索…

C++ | 继承

目录 前言 一、继承的基本概念与使用 1、继承的概念 2、继承的定义 3、继承的访问限定符与继承方式 二、基类与派生类之间的赋值转换(切片) 三、继承中的作用域 1、继承中的作用域 2、隐藏(重定义) 四、派生类的默认构…

知识付费小程序怎么做

知识付费小程序是一种通过在线平台提供知识和教育内容的应用程序。下面将详细介绍其功能: 1. 音频视频课程: 知识付费小程序提供了丰富的音频和视频课程,在这些课程中,用户可以通过观看或听取专业讲师的讲解来学习各种知识领域。…

【文章系列解读】Nerf

1. Nerf NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis 2020年8月3日 (0)总结 NeRF工作的过程可以分成两部分:三维重建和渲染。(1)三维重建部分本质上是一个2D到3D的建模过程&#xff…

Java习题之实现平方根(sqrt)函数

目录 前言 二分查找 牛顿迭代法 总结 🎁博主介绍:博客名为tq02,已学C语言、JavaSE,目前学了MySQL和JavaWed 🎥学习专栏: C语言 JavaSE MySQL基础 🎄博主链接:tq02的…

【第四章 flutter-初识flutter】

文章目录 一、目录结构二、创建一个flutter项目三、创建自定义组件四、Container组件 就是divalignment 内容对齐方式decoration 类似border 为BoxDecoration的类 五、Text属性六、image组件总结、 一、目录结构 android、ios各自平台的资源文件 lib 项目目录 linux macos PC平…

Linux宝塔Mysql读写分离配置,两台服务器,服务器存在多个库

Linux宝塔Mysql读写分离配置,两台服务器,服务器存在多个库 一、主库操作 #登录数据库,用root登录方便,用其他账号会提示权限不足,需要登录root给予权限 mysql -u root -p 密码#创建一个账号,供从库用该账…

电商企业需要部署WMS仓储管理系统吗

随着电子商务行业的迅速发展,电商企业面临着日益增长的订单量和复杂的物流流程。为了提高仓储管理的效率和准确性,许多电商企业开始考虑部署WMS仓储管理系统。然而,是否真的需要部署WMS仓储管理系统,仍然是一个值得探讨的问题。本…

vLLM大模型推理加速方案原理(PagedAttention)

一、vLLM 简介 vLLM 用于大模型并行推理加速,核心是 PagedAttention 算法,官网为:https://vllm.ai/。 vLLM 主要特性: 先进的服务吞吐量通过 PagedAttention 对注意力 key 和 value 进行内存管理对传入请求的批处理针对 CUDA 内…

纯代码和低代码的本质区别

一、前言 纯代码和低代码是现代软件开发中两种不同的方法。 纯代码需要专业的编程技能,掌握编程语言、算法和数据结构等专业知识。而低代码则是一种新兴的开发方法,它大大降低了对编程技能的要求,让非技术人员也能够创建应用程序。随着低代码…