MySQL的深度解析与实践

发表时间: 2021-09-16 13:14

Mysql专题

前言

本次专题,借鉴了我报名的培训机构的视频资料以及13笔记。结合自己所学并提炼知识点。一方面给大家一个资料参考,一方面给自己学习总结。

Mysql如何执行一条sql

​ Mysql执行一条sql,经历了哪些步骤呢?数据库接收到客户端发送的请求,会从数据库自己的连接池中,拿出一个线程来监听这个请求,以及从请求中拿到sql语句。拿到sql语句以后就会去调用sql接口,sql接口就要完成接下来对sql语句的一系列的操作。那么首先,由解析器对sql语句进行解析,解析完成把sql语句交给优化器,优化器会对sql进行各种优化,最终选择一个最优的查询路径。然后交给执行器,执行器调用存储引擎,告诉存储引擎执行一类型的逻辑操作(缓存和磁盘)。经过我这么一说,你对整个执行sql的流程还是很模糊,所以请看图。

Mysql的架构设计

缓存:BufferPool

上面我们提到了缓存的概念,在Mysql中的缓存叫做BufferPool。缓存在数据库很重要,因为存储引擎执行sql的逻辑会直接操作缓存中的数据,而且存储引擎也会读取磁盘中数据放入缓存。在一个事务中,存储引擎会把sql生成的新数据放入缓存中,更新磁盘的工作是由数据库单独的线程去完成。也就是说,在一个事务开始到提交过程中,只会操作缓存中数据,这样的好处是,减小了IO的开销。你看哈,每一次请求,就更新磁盘的数据,假如有1000个请求同时打过来,数据库就要1000次IO操作,这样性能是不是很低。

我们以一个update语句为例。如果一个更新语句,要把name=zhangfei的旧值更新为name=chaowenfei。首先它查询缓存中是否有存在的数据。如果有则更新数据,并把改数据所在的数据页标记为脏数据页。如果没有则从磁盘中加载数据到BufferPool中,然后再执行上诉更新缓存数据的操作。

那么,问题来了,万一更新语句的整个事务因为代码的逻辑报错,导致事务失败,数据库改如何回滚事务呢?undolog就出现了。

undolog

接下来,我们还是以一个update语句为例。(我接下来的讲解,都是在建立在你熟悉sql基本语法,了解事务和锁的基础上,如果你还是小白,我建议先去补充下基础的知识,再来看我的专题。)这条sql必然要经过我上面整个执行的流程。这里有一个问题,万一我这个事务执行过程中失败,导致事务回滚,Mysql是如何设计的?

那么undoLog就登场了,当开始执行sql的时候,会自动开启一个事务,把缓存中的旧值会拷贝一份放入undolog中,如果事务执行失败,会把undolog的旧值重新更新回缓存中。

好,事务失败的问题解决了。那么还有一个很大的问题亟待考虑。如果数据库宕机了,怎么保证那些未执行成功的事务,存放在undoLog的数据和已经执行成功的事务,但是缓存中数据未更新磁盘的数据的丢失呢?不急,你都考虑到了,InnoDB的设计者会想不到吗?redolog继而产生了。

redolog

还是以一个update语句为例,看看redoLog是如何工作的?不废话了,直接上图


可以看出,在innoDB执行过程中,会把内存所作的修改写入到redologBuffer中。这里有个问题,你看哈!如果事务未提交,数据库宕机了,bufferPool和redoLogBuffer中事务的数据都会丢失,没有问题,因为没有提交,丢失就丢失呗。如果一个事务提交了,但是redoLog日志因为还在缓存中(redoLogBuffer),还没有写入到redoLog中,那么数据重启,这条事务数据就丢失了。所幸,我们有一个策略来选择,设置这个参数
innodb_flush_log_at_trx_commit的值。往下看。

  1. 如果innodb_flush_log_at_trx_commit=0,如果事务提交之前,并不会把redoLogBuffer中日志写入reoLog中。
  2. 如果innodb_flush_log_at_trx_commit=1,如果事务提交之前,会马上把redoLog日志写入文件中。
  3. 如果innodb_flush_log_at_trx_commit=2,如果事务提交之前,会把日志写入OS的cache中。

所以我们一般设置为1,来保证数据不丢失。

既然,redoLog存放了数据库内存修改数据的所有操作,那么可不可以用它来做主从同步呢?答案是不行的,因为redoLog有大小的限制,并不会记录所有的数据库操作的日志。binLog可以。

binlog

如果说,redoLog是偏向物理性质的日志文件的话,那么binlog就是偏向逻辑性质的日志文件。redolog记录了缓存中对哪个数据页中数据做了修改,做了什么样的值修改。binlog记录了表id=10这一行数据做了修改,更新后的值是什么。你看,两者虽然描述的是同一件事情,但是描述的逻辑却不相同。redolog是innodb独有的,binlog是mysql的service层独有的。

那么binlog是如何运作的呢?上图


我引入了执行器的组件,执行器是Mysql和存储引擎沟通的最重要的组件,也是数据库最核心的组件。binLog刷盘也有一定的策略。可以设置参数
sync_binlog来改变策略。

  1. sync_binlog=0,会把binLog日志写入OS的cache缓存中,并不会立即写入binLog日志文件中。
  2. sync_binlog=1,会立即写入binLog日志文件中。

如果binlog日志写入完成,会把binlog的日志文件名称和更新binlog文件更新的位置写入redolog日志中,并打上一个commit的标记。这么做的原因是为了,保证redolog和binlog的数据的一致性。比如在写入redolog或者binlog过程中,发生宕机,因为事务没有完成提交,也就是说没有commit标记,即使redolog或者binlog有日志,也不会有任何影响。只有在redolog和binlog日志中都有数据,并且已经打上commit标记,才认为整个事务执行完成,并且执行成功了。

还有一个未完成的步骤,就是bufferPool数据刷盘。数据库会开启一个异步IO线程,在某一个时刻,把bufferPool中的脏数据刷新到磁盘中。(思考下,为何数据库要开启一个异步IO线程,而不是每次执行sql的时候,就刷新磁盘?)

总结下整个流程吧。

  1. 从磁盘加载数据到bufferLog缓存中。(如果缓存中已经存在,则这步省略)
  2. 往undoLog写入旧值。
  3. 更新缓存中的数据。----->>>>>>数据库开启IO异步线程对bufferPool脏数据进行刷盘
  4. 把redoLog日志文件写入redoLogBuffer中
  5. 把redoLogBuffer中日志文件刷盘。
  6. 写入binLog日志
  7. 把更新binLog日志文件和更新文件中的位置写入Redolog中,并写入一个commit标记。
  8. 事务提交

那么我就用一张图来结束本章节的内容


以最简短的话和简易的图,把整体的架构讲了一遍,其中涉及到的关键的组件也介绍了一番。这其实只是底层的第一层原理。接下来分别对几大组件进行第二层原理的刨析。

BufferPool

上一章节,我其实留了一个小小的疑问,就是为什么数据库开启一个异步线程,对bufferLog的脏数据进行刷盘。而不是每一次sql语句执行完了,进行刷盘。这是因为磁盘是一个低俗设备,对磁盘进行IO操作是一个很慢的过程,如果一千条sql,一千次IO,那么数据库的吞吐量和并发量是极其低的。所以BufferPool是Mysql其中一个很关键的组件。既然新数据缓存中BufferLog中,我们也担忧数据库宕机导致数据丢失的问题?我们也讲到redeLog的日志保证数据不丢失问题,另外结合undoLog,binlog日志,保证整个数据库应对各种问题,也能顺利工作。

那么,我们这一章节,对BufferLog进行第二层原理的刨析。

BufferPool的内存数据结构

数据页:是Mysql抽象并定义内存存放数据的一个单元。就好像一个走廊,我们首先画好格子,然后让人站在格子里面。那么类比BufferPool,其实就是把缓存划分一格一格的数据页。数据页,Mysql定义大小为16K。这里提一嘴,数据页并不是指表中的某一行数据,它是指表中多行数据。把表中多行数据从磁盘中读取放入数据页,当发现超过16K的时候,会把剩余的放入另外的数据页中。最后总结下,数据页是存放表的多行数据,大小16K。

​ 那么,数据页在BufferPool又是如何运作的呢。还是先看图吧。


可以看到,每一个数据页对应一个数据页的描述。

free链表

​ 既然已经知道了BufferPool的底层数据结构,那么我们就来想象一下,当数据库刚刚启动时候,来了sql语句的执行请求,这时候BufferPool肯定还是空的,那么从磁盘读取数据页数据,如果存储到BufferPool中呢?你可能会说,依次存放就好了,确实可以这么做。以上面的图为例子,那么假设在数据库运行过程中,数据1,2,3中都有数据,当发生了某些情况,数据页2数据不得不刷新回磁盘,那么数据页2所占用的BufferPool内存空间就被长期“遗忘”,所以这种很粗糙的设计显然行不通。

​ 什么是free链表?free链表的每个节点存储的是空闲缓存页的描述信息,描述信息里面存放的是空闲缓存页的地址。每个节点都是双向的。那么free链表就组成了空闲缓存页描述信息的双向链表。数据库运行过程中,当从磁盘加载缓存页到BF中时候,必然要去申请一块空闲缓存页,所以就从free链表中找。

总结下整个加载数据页到缓存中的整个流程,从磁盘加载缓存页,首先从链表中找到一个空闲缓存页的描述信息,然后找到空闲缓存页的对应的地址,把数据页数据放入缓存页中,再把缓存页的信息放入描述信息数据块中,然后移除free链表的空闲缓存页的描述信息。说起来可能有点绕,画个图看下

Flush链表

​ free链表讲完了。数据库正常在运转,我们知道,所有的sql操作都是基于缓存的,这种设计是为了提高数据库整体的性能,这就好像我们java系统为什么要用NO-SQL数据库一样。缓存的速度是极快的,但是缓存并不能作为数据持久化,最终修改的数据在某一个时刻,必须刷新到磁盘中。所以,我们会把缓存中更新过的数据页标记为脏数据页,暂时存放在缓存中,等待数据库异步IO刷新到磁盘中。那么IO线程如何去缓存中寻找脏数据呢,难道是循环遍历一遍缓存中的所有数据页,哇靠,那数据库性能岂不是慢到家了。

​ Flush链表,顾名思义,就是存放脏数据的双向链表。本质跟free链表一摸一样。这里就不画图演示了。通过把所有的脏数据页的描述数据信息组成双向链表,把脏数据页管理起来。这样IO线程就可以直接寻找Flush链表,找到脏数据页了。

为何要引入Lru链表?

​ free链表和flush链表似乎已经足够满足整个BufferPooL整个运行了,但是还是有那么一些问题急需解决。有没有想过,当BufferPool已经快要满了,数据库基于怎样的算法和机制进行处理的?这是每一个缓存设计又一个的难点。就好像redis有它自己的过期和淘汰机制。BufferPool自然也有。

如果让你来设计,那么你的第一个想法肯定是,把那些不经常使用的数据页刷新到磁盘中。自然而然地,我们引入了缓存命中率一词,换而言之,把那些缓存命中率极低的缓存页空出来,给新来的数据页。LRU算法正好符合这样的需求,LRU也叫最近最少使用的意思。你听到这个词肯定不陌生,LRU算法在redis也是其中的一种淘汰策略。基于LRU算法,把缓存命中率高的移动到链表表头,那么缓存命中率低的就移动到了链表尾部。一则,在查询缓存中数据页,那些经常访问的数据页能更快被找到。二则,如果free链表已经没有空闲缓存页,不得不淘汰缓存中数据页给新来的,那么直接淘汰尾部的缓存页即可。你看,如果你熟悉链表的时间复杂度,你就知道这种算法带来的好处有多大了。

Lru链表是不是银弹?

​ 任何方案都不是银弹。导致Lru不完美的原因,是因为Mysql的预读机制。什么是预读机制?

预读机制:当查询缓存中没有对应的数据,就去磁盘中加载,当查询到磁盘中对应的数据页,会把相邻的数据页一起加载到缓存中。即使相邻的缓存页不是你想要的。

好处:Mysql认为你查询到你想要的数据页,那么相邻的数据页在未来可能马上被需要。这样我就不要在需要的时候,再此去磁盘加载。节省了IO。

坏处:相邻的数据页被加载进来,可能一直不被访问,却占用了缓存的内存空间。

另外一种情况更糟糕,那就是全表扫描,大量数据页被加载进缓存,却只有少量的数据页被访问。

LRU的冷热数据分离

冷热数据怎么解决的?先上图


热数据区域自然不必说,冷数据区域是存放第一次被加载进来的缓存页。Mysql设置了这样一个游戏规则,当加载进来超过1秒以后,如果数据页被访问了,那么会把数据页从冷数据区域移到到热数据区域的链表头部。如果没有被访问,就一直存放在冷数据区域。那么当缓存满了,就会从冷数据链表尾部淘汰一个数据页,刷新到磁盘,空出位置给新来的。

小小的优化

热数据区域会根据缓存的访问频率,移动数据页的位置。当然频繁的移动也会影响性能。那么mysql也制定了一个规则。前1/4的热数据区域不会移动,后3/4的热数据因为LRU算法,才有机会移动到链表头部。这样折中的办法就解决了频繁移动链表导致的额外的开销。

数据库性能抖动的案例分析

既然学习完了以上的部分知识点,那么有必要学习一个案例来验证下学习的成果。首先,我们知道一个事务的执行过程,包换磁盘读取数据页,更新free链表,flush链表,lru链表,另外还有写入redolog,undolog,binlog。那么在此期间,哪些场景会导致数据库会发生性能的抖动呢?

第一种情况,我们知道在bf已经满了的情况,会把那些标记为脏页的数据,刷新到磁盘,来为新的数据页腾出足够的空间。那如果发生了一次大的查询,导致大量的数据页放入缓存中,bf通过lru链表要把大量的数据页刷新到磁盘中才能腾出足够多的空间来存放。那么就发生一次性能的抖动。

第二种情况,redolog日志是先写入redologBuffer中,在刷新回redolog中的。那么假如redologbuffer已经达到容量上限,开始刷新到redolog,发现redolog已经满了,需要覆盖写,但是又发现那些需要覆盖的redolog对应的缓存中的数据页还没有刷新到磁盘中。如果覆盖写,一旦宕机,那么无法从redolog恢复数据。但是事务提交之前又必须写入redolog。当发生大量更新语句的时候,数据库就会发生类似卡死的情况,数据库不得不把覆盖写redolog日志对应缓存中的脏页,刷新到磁盘,然后写入redolog日志,才会提交事务。这种情况,数据库性能页会发生抖动的情况。

说完两种情况,总结下,第一种情况是因为bf满了,第二种情况是因为redolog满了,要覆写。最后都要发生一次长时间的刷盘过程,向磁盘这种低速设备写入过程是相当耗时和损耗性能的。那么,我们有两种办法,要么扩大bf和redolog日志文件的大小,要么加快写入磁盘的速度。第一种方法,我们可以去调整容量大小,一方面,我们无法预测和控制大小,另一方面,容量被占满也是迟早得事情。第二种方法,倒是有办法,就是用SSD来加快写入得速度。

当然这种亡羊补牢得方法,最好不要发生。数据库在平时也会开启一个定时任务,定期得把脏页刷新到磁盘中。

你看,数据库整个流程就是,一边把数据页放入缓存中,更新free链表,lru链表,flush链表,写入undolog,redolog,binlog,一边异步把脏页刷新到磁盘中。

Mysql事务隔离级别

Mysql的四种隔离级别分别是:读未提交,读已提交,可重复读,串行化。

**读未提交:**事务A,事务B,当事务A执行过程中,查询到了事务B还未提交的最新的数据。

**读已经提交:**事务A,事务B。当事务A执行过程中,能查询到事务B已经提交的最新的数据,未提交的数据查询不到。

**可重复读:**当事务A在执行过程中,查询到的数据比如name=A。此后事务B去修改了name=B,并提交。事务A还未结束,再此去查询数据name,发现还是name=A。

**可串行化:**对同一个数据进行查询和更新操作,只能串行,不能并行。

为什么引入事务隔离级别?思考一下,假如多个事务对同一个数据进行更新时候,你会去怎么设计?你马上回答,加锁,当然加锁能解决,但是数据库性能岂不是慢到姥姥家了。

MVCC多版本

​ undolog用版本链和readview来实现各个事务之间的隔离,又能提升数据库的并发性能。

版本链类似一个版本的单向链表,链表的每一个节点就是一个版本,版本中记录了事务ID和roll-pointer,roll-point又指向了前一个版本。

readview存放了四个东西:

​ 一个数组,数组中存放的是未提交事务的id,

​ 版本链中最大的事务ID

​ 版本链中最小的事务ID

​ 创建版本链的事务ID

这里就不画图来说明,因为这部分可以作为了解。

再谈undolog回滚机制

如果是多个事务去执行操作同一数据,那么每个事务都会创建一个版本,组成版本链,每个事务都会创建自己的readview。

读已提交隔离级别的实现

事务A在查询时候生成一个readView,从版本链挑选可见的版本,当前版本事务ID,如果在readView中,则通过rollpoint跳到下一个版本,直到找到的版本的事务ID不在readView,并且小于当前版本的事务ID,则就是要查询的版本。

可重复读隔离级别(RR)的实现跟读已提交隔离级别(RC)最主要的区别就是,RR只会在第一次select生成一个readview,RC会在每一次select生成一个readview。

这里简单的介绍一下,因为这块知识点,稍微比较绕,而且不是我们研究的重点。我会把重心放在索引章节,大篇幅地讲解索引地原理。

Mysql索引

小试牛刀

​ 假如你给数据库几个字段加上了联合索引。那么我们来看看几种情况。

  1. 索引字段都用上了,而且是等值匹配。这种情况,索引基本是都能用上的。这个不用担心,即使where条件的顺序跟索引的顺序不一致也没有关系。
  2. 最左匹配原则。联合索引index(name,address),假设sql语句:select * from user where name=xx and address=xx 那肯定会用上索引。假设sql语句:select * from user where address=xx那么是不会用上索引的。因为必须先找到最左边的索引,才能依次使用索引。
  3. 最左匹前缀匹配原则。这是对模糊查询来说的。其实也是一样的道理。like '1%'是能用到索引的,如果like ‘%1’。那么是不能用到索引的。
  4. 范围查询。我们知道索引页是有序排列的,所以范围查询也是可以用到索引的。无非是定位到某一段范围的索引页。
  5. 等值查询+范围查询。那更不必说,通过等值匹配查询出一波数据,这段数据肯定也是按照有序排列,然后利用索引进行范围查询出结果即可。

排序和分组

​ 我认为排序和分组的是同一种思路,都是利用索引树的有序性。有序性可以利用二分法来查找,而且有序就已经排序好了,直接拿就行了。节省了把数据拿到缓存或者临时文件中,再进行排序的时间。假设建立了联合索引index(A,B,C)。那么order by A,B,C或者order by A desc,B desc,C desc,都能利用到索引。如果order by A desc,B就不会走到索引。大概你也猜到了,利用多个字段排序,如果一会正序一会反序,都无法利用索引有序一次性解决问题。必须要把数据读到缓存或者临时文件中再次排序。分组也是同样的道理,我这里就不一一描述。

设计索引

​ 当你刚刚接手一个项目的时候,你肯定要设计超多的业务表,你会不会也要顺便在建表的时候,思考索引的建立。你肯定会讲,我也不知道给哪些字段建立索引。是的,你不知道未来的sql语句到底写成什么样子。别急,先不考虑这个问题,继续开发,当业务功能开发已经差不多,代码也写的有模有样了,sql语句也出来了。那时候你再去对比sql语句,sql语句经常使用的条件有哪些,然后再去建立索引。

  1. 联合索引肯定比单个字段索引更有价值。首先索引并不是越多越好,你要知道每建立一个索引,就产生一个B+树。随着时间推移,树节点会越来越多。每次增删,都要去更新树节点。这种执行sql以外带来额外的开销也不容忽视。那么相比给一个字段建立索引,还不如给多个字段建立联合索引,性价比来的更高一点,你说是吗?
  2. 我们建立的索引并不能覆盖所有的功能的执行的sql,只能对那些频繁执行,复杂的sql语句有效。只要能覆盖到百分之80的业务,那已经算是很成功。百分之20是偶尔,奇葩,sql简单的就只能忽视。这种取舍是没有问题的。
  3. 索引选择是建立在最左,排序字段,分组字段上的,尽量保证能走索引。
  4. 索引字段尽量选择那些字段大小比较小的。
  5. 选择主键最好不要用UUID,UUID的随机性是很烦人的,UUID并不是自增的,所以在聚簇索引叶子节点写入一行数据,可能会引发频繁的页分裂,因为随机写带来了,主键重新排序的问题。随机写的性能往往比顺序写慢很低。另外一个原因,是因为UUID太长了,主键占用空间太大,查询的性能也必然受到影响。

透析explain执行计划

​ 当你学会分析Mysql的执行计划,那么sql调优就已经学会一半了。我常常把看执行计划,当作破案。假设你拿到一段慢查询,你肯定会先去看下执行计划,侦破它慢的原因,分析执行计划的各个参数,探究底层执行sql发生的问题,把你分析得到的证据一份份拼接成真相的拼图,最终破案。破案还不够,我们既然知道凶手到底如何行凶,那么我们是否有其他的方法,避开凶手。那么有意思的调优也就开始了。当你把一段执行几秒的sql,调优成几十毫秒甚至几毫秒,那你得到的是满满的成就感。

初识执行计划

​ 首先我们得去了解下type:const,ref,rang,index,all几个词在什么场景下出现,各自代表得含义是什么?

  • const,是一般代表这个sql语句,查询性能相当快。一般是发生在聚簇索引B+树,发生在唯一索引上。这代表是一个常量查询,比如a = xx。没有关联表字段,是具体的某一个值,所以速度很块。
  • ref,但是我们在写sql的时候并不能一定用到主键和唯一索引。绝大可能用到了二级索引。当只能通过二级索引通过二分法快速查询索引页的情况,那么就是ref
  • rang,当然在使用二级索引,也会有这样的sql,比如 a>10 and a<1的情况发生。那么就是rang,速度也可以接受。因为索引页都是按照有序排列,即使范围查询,也只是把某一段的排好序的索引页查询出来而已。
  • index,最糟糕的情况就是你自认为用上了索引,但是毫无效果。比如 在a上建立一个二级索引,a is null 或者a is not null。这种情况你不得不遍历所有的索引页去判断,才能无一遗漏。
  • all,虽然遍历索引页已经相当慢了,但是总比全表扫描快很多,因为索引页只存放了索引的值和主键,判断起来相对简单。聚簇索引的叶子节点存放的数据页放的是表中一行一行数据。好比一小块石子和一块巨石的差距。你仔细掂量都知道哪个重了。

执行计划进阶篇

然后,我们再去了解下select_type,select_type主要有这几种,

PRAMARY:根据主键查询

DEPENDENT SUBQUERY : 子查询

DEPENDENT UNION:并集

DERIVED:把子查询的结果物化成一张临时表,然后根据关联字段进行内连接。

接着,完美来看下extra主要有几种?

Using index :告诉你,此次查询利用了二级索引,没有发生回表

Using where :此次查询除了利用索引以外,还用到了非索引字段去查询数据

Using where,Using join Buffer:当发生关联查询,利用joinBuffer,减少扫描的次数。

Using filesort:当发生一些排序的时候,基于磁盘或者临时文件进行排序。

几个重要的参数已经讲解完了,当在平时SQL优化过程中,大部分的场景只需要依靠这些参数信息就能知道,sql底层是如何执行的了,其他一些参数可以在平时遇到问题再查阅相关资料。其实,在平时调优过程中,最主要的还是要分析哪些地方发生了全表扫描或扫描行数过多,尽量用索引来优化,让执行计划的每一步都能利用到索引,避免全表扫描和走扫描行数少的方案。

面试题:执行一段sql很慢,如何去分析?

因为本人精力有限,只能提炼出一些结论性的东西进行分享,无法很详细的分析每一个知识点,有些边缘的知识点都是一句未提或者一句带过。所以看这篇文章的小伙伴,欢迎找我讨论或者查阅资料去补充你的知识网络体系。最后我分享一个面试官经常会问到的一个面试题。

我们第一反应肯定是看sql语句,看看执行计划。是不是因为扫描行数过多或者全表扫描,导致sql执行时间太长了。那么优化的思路也就明确了,重写sql语句,条件尽量利用索引,避免全表扫描或者减少扫描数。其次,我们看完sql语句和执行计划,发现sql语句本身是没有问题的,那么我们的目光可能要放在sql语句之外的地方。

Mysql的随机IO是否太高。因为一些场景导致占用了Mysql磁盘的随机IO,可能达到上限。在执行sql语句过程中,导致本身的磁盘IO必须等待或者速度很慢。最终影响sql语句的执行。

Mysql的网络负载过高。Mysql本身的连接数是有上限的。如果因为一些sql的慢查询导致连接数被一直占用,而客户端的请求还源源不断地过来,导致网络带宽被打满,连接数也被打满。客户端再此发起请求,只能阻塞。

Mysql地CPU负载过高。因为CPU在忙着其他事情,没工夫处理sql地执行。

比如,在白天业务繁忙地时候,某一个时刻,大量地数据灌入到数据库,导致数据库负载一段时间内持续飙高。那么,执行sql可能会很慢。