💫《博主主页》:
🔎 CSDN主页: 奈斯DB
🔎 IF Club社区主页: 奈斯、
🔎 微信公众号: 奈斯DB
🔥《擅长领域》:
🗃️ 数据库:阿里云AnalyticDB(云原生分布式数据仓库)、Oracle、MySQL、SQLserver、NoSQL(Redis)
🛠️ 运维平台与工具:Prometheus监控、DataX离线异构同步工具
💖如果觉得文章对你有所帮助,欢迎点赞收藏加关注💖

在数据库运维中,CPU打满 💢、内存耗尽 💣、I/O饱和 ⛓️ 是三类最具代表性的资源型故障。🔍 这三者之间通常存在明显的传导关系:I/O 瓶颈引发内存排队,内存压力加剧 CPU 争抢,最终导致整体性能雪崩——简单说就是“一个拖一个,最后一起炸”。💥
本文结合博主在实际生产环境中遇到的真实案例(有些还挺“社死”的 😅),从 现象识别 → 定位分析 → 根因判断 → 解决方案 四个维度,系统梳理三类“打满”场景下的排查思路与应对策略。
希望这篇偏实战的内容,能为大家在数据库性能调优和故障处置中提供一些可复用的思路与方法。📌 欢迎各位大佬交流指正。
一、内存高、CPU高、IO高的原因和解决办法
1.1 内存高的原因和解决办法
情况一:参考“消耗内存的慢查询”
通常来说,查询峰值内存越大,内存消耗越大。导致查询内存消耗较大的原因通常有如下几种:
- 有GROUP BY操作。GROUP BY字段的值缓存在内存中。如果GROUP BY的字段唯一值较多,就会占用较多内存。
- 有JOIN操作。在使用Hash方式进行Join时,会把某张表的数据缓存到内存中。如果被缓存的表较大,就会占用较多内存。
- 有SORT操作。在执行数据排序时,会把数据缓存到内存中。如果需要排序的数据量较大,就会占用较多内存。
- 有窗口函数操作。在执行窗口函数时,会把数据缓存在内存中。如果需要执行窗口函数的数据量较大,就会占用较多内存。
情况二:连接数和线程数过多,每个MySQL连接和线程都需要一定的内存资源,有大量并发连接请求,会导致内存占用增加。
情况三:备份期间内存使用可能会升高。备份过程中涉及到大量的数据读取和处理,需要使用额外的内存资源来执行备份操作。
解决办法:在夜间低峰期备份或者增加物理内存资源,提高服务器的整体性能。
情况四:数据库缓存,MySQL使用各种缓存来提高查询性能,如查询缓存、InnoDB Buffer Pool、Key Buffer等。如果这些缓存设置过大,可能导致内存占用过高。
解决办法:合理配置缓存区和缓冲区的大小,避免设置过大。
1.2 CPU高的原因和解决办法
情况一:参考“消耗CPU的慢查询”
通常来说,查询的执行耗时(Time Consumed)越长,CPU消耗越大。导致查询CPU消耗较大的原因通常有如下几种:
- 过滤条件没有下推。使用索引进行过滤可以极大减少CPU资源的消耗。
- Join条件中带有过滤操作。如果Join中带有过滤条件,会先对两表执行Join,再对Join后的数据执行过滤,此时的过滤无法使用索引。如果Join后产生的数据量较大,过滤操作就会消耗较大的CPU资源。
- Join时没有指定Join条件。如果没有指定Join条件,会对左右两表执行笛卡尔积运算,产生的数据量行数是左右两表数据行数的乘积,该类操作会导致消耗较大的CPU资源。
情况二:高并发连接,大量并发的连接可以导致CPU使用率上升。
情况三:锁竞争和死锁,多个并发连接同时访问同行数据或表,会导致锁等待和死锁现象,那么就会消耗大量的CPU时间来竞争和管理锁,导致CPU占用率升高。
解决办法:
- 优化查询语句:减少不必要的锁竞争。添加合适的索引、避免全表扫和大事务等来提高查询效率,减少锁的持有时间。
- 事务隔离级别设置:根据应用程序的需求,调整事务隔离级别。如从Repeatable Read降到Read Committed,可以减少锁的竞争,但需注意可能引发的并发性问题。
- 使用合适的锁策略和设置锁超时设置:编写代码时合理选择使用行级锁、表级锁或其他级别的锁,根据业务需求平衡并发性和数据一致性。设置合理的锁超时时间innodb_lock_wait_timeout,避免长时间等待锁的释放。
情况四:配置不当,配置参数设置过高或者过低,都可能导致CPU占用率异常升高。如缓存区和线程池设置过大或过小、缓存未命中率过高等。
解决办法:合理配置MySQL的缓存区和线程池大小,根据实际情况适当调整参数值。
1.3 IO高的原因和解决办法
情况一:参考“消耗磁盘I/O的慢查询”
通常来说,查询的扫描行数(Scanned Rows)和扫描量(Amount of Scanned Data)越多,磁盘I/O消耗越大。导致查询磁盘I/O消耗较大的原因通常有如下几种:
- 过滤条件的数据筛选率较低,导致索引的使用效率不高,需要读取的索引量较大。
- 过滤条件没有下推,导致对源表进行了全表扫描。
- 过滤条件下推,但是过滤条件设置的范围较大,仍然有大量数据被扫描。
- 需要扫描的分区较多。通常情况下,分区越多意味着需要扫描的数据量越大。
情况二:锁等待,当多个并发连接同时访问同一行数据或表时,可能会发生锁等待现象,导致IO等待时间增加。
解决办法:
1)优化查询语句:减少不必要的锁竞争。添加合适的索引、避免全表扫和大事务等来提高查询效率,减少锁的持有时间。
2)事务隔离级别设置:根据应用程序的需求,调整事务隔离级别。如从Repeatable Read降到Read Committed,可以减少锁的竞争,但需注意可能引发的并发性问题。
3)使用合适的锁策略和设置锁超时设置:编写代码时合理选择使用行级锁、表级锁或其他级别的锁,根据业务需求平衡并发性和数据一致性。设置合理的锁超时时间innodb_lock_wait_timeout,避免长时间等待锁的释放。
情况三:磁盘性能限制,所在服务器的磁盘性能较低,无法满足MySQL的读取请求,也会导致IO等待时间增加。
情况四:大量并发写入,如果有大量的并发写入操作,MySQL需要频繁地将数据写入到磁盘,增加IO负载。
解决办法:参考下面“2、DML慢分析”
二、通过命令分析CPU、内存、I/O使用情况
2.1 通过top命令查看CPU使用情况
[root@mha1 ~]# top -help
- -u:指定用户进行的进程
- -c:切换显示命令名称和完整命令行。
top - 15:49:30 up 23:04, 2 user, load average: 0.26, 0.24, 0.16
任务队列信息
- Top:当前时间
- up:系统运行时间,可以得出系统自开机来运行的天数。时间运行太长不建议重新
- User:终端登录数量,目前有2个会话连接到terminal
- Load average:1/5/15分钟内cpu的负载(不是cpu的使用率状况,而是在时间内cpu正在处理以及等待处理的进程个数),3.26/5.24/10.16。值的大小最好不超过load average <=cpu核数*0.7(例如服务器16核,每核只能处理一个进程,一分钟有32个进程,那么一分钟的Load average=2),如果超过查看进程
Tasks: 222 total, 1 running, 221 sleeping, 0 stopped, 0 zombie
进程信息
- Tasks:所有进程任务当前状态,运行、睡眠、停止、无响应
- Total:进程总数
- Running:正在运行的进程
- Sleeping:睡眠的进程
- Stopped:停止的进程
- Zombie:无响应进程,如果有无响应的进程应立即检查
Cpu(s): 6.5%us, 1.9%sy, 0.0%ni, 88.3%id, 3.0%wa, 0.1%hi, 0.1%si, 0.0%st
cpu信息
- %us:用户cpu占用率,建议不能超过70%(如果服务器有16核,有一个进程长时间保持100%,那么%us的使用率为1/16=6.25%;两个进程长时间保持100%,那么%us的使用率为2/16=12.5%,以此类推)
- %sy:内核cpu占用率
- %ni:改变过优先级cpu占用率
- %id:空闲cpu率
- %wa:等待cpu率。%wa(Wait IO)表示 CPU 等待 I/O(输入/输出)完成的时间百分比,通常是CPU 在等磁盘 I/O。使用 iotop / iostat 找出罪魁祸首(或者直接看CPU和IO的监控),针对性优化,详细排查咨询deepseek
- %hi:硬件中断cpu占用率
- %si:软中断cpu占用率
- %st:虚拟机cpu占用路
KiB Mem : 1175404 total, 82012 free, 638220 used, 455172 buff/cache
- Mem:物理内存使用情况
- Total:内存总大小
- Used:内存使用数,使用数包括了程序进程使用、buffers和cached总和的使用量。
- Free:内存空闲数
- Buffers:目录缓冲。用来缓冲目录里的内容、权限等
- Cached:文件缓冲。用来缓冲打开的文件(注:buffers和cached是将暂时不用的内存作为文件和系统的缓冲,用于提高文件系统的读写性能。Buffers和cached是系统total分配的大小,占用到used里面,如果其他进程需要内存时,buffers和cached会自动释放给进程。)
实际程序可用内存算法:free+buffers+cached
实际程序已用内存算法:used-buffers-cached
判断系统物理内存不足:swap的used大于0
KiB Swap: 2228220 total, 2223344 free, 4876 used. 335124 avail Mem
- Swap:交换内存使用情况(交换内存使用的是磁盘的IO,不宜过大,一般为物理内存的2倍)
- Total:交换内存总大小
- Used:交换内存使用数,使用数包括了程序进程使用、buffers和cached总和的使用量。
- Free:交换内存空闲数
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2438 grid 20 0 623m 30m 12m S 2.0 1.5 3:03.55 gipcd.bin
2451 root RT 0 628m 87m 57m S 2.0 4.4 16:23.79 osysmond.bin
进程信息区
- PID(process ID):进程id号
- User:进程所有者
- PR:优先级
- NI:正为高优先级,负为低优先级
- VIRT:swap+RES
- RES:物理内存使用大小
- SHR:共享
- S(status):进程状态。S:sleeping R:running T:stopped z:zombie
- %cpu:单个进程占总cpu使用率
- %MEM:单个进程占物理内存使用率,可以以RES来查看进程物理使用大小
- TIME+:进程运行时间
- COMMAND:进程名称
2.2 free命令可以显示Linux系统中空闲的、已用的物理内存及swap内存
[root@192 ~]# free -m
- Mem:物理内存使用情况
- Swap:交换内存使用情况(交换内存使用的是磁盘的IO,不宜过大,一般为物理内存的2倍)
- Total:内存(交换内存)总大小
- Used:内存(交换内存)使用数,使用数包括了程序进程使用、buffers和cached总和的使用量。
- Free:内存(交换内存)空闲数
- Buffers:目录缓冲。用来缓冲目录里的内容、权限等
- Cached:文件缓冲。用来缓冲打开的文件(注:buffers和cached是将暂时不用的内存作为文件和系统的缓冲,用于提高文件系统的读写性能。Buffers和cached是系统total分配的大小,占用到used里面,如果其他进程需要内存时,buffers和cached会自动释放给进程。)
实际程序可用内存算法:free+buffers+cached
实际程序已用内存算法:used-buffers-cached
判断系统物理内存不足:swap的used大于0
2.3 iotop命令(iotop 类似于 top,但专门用于监控每个线程/进程的磁盘 I/O 使用情况)
作用:iotop 类似于 top,但专门用于监控每个线程/进程的磁盘 I/O 使用情况。
安装 iotop (默认系统没有预装):
# Ubuntu/Debian sudo apt install iotop # CentOS/RHEL sudo yum install iotop # 或者 sudo dnf install iotop
解读iotop命令:
- 查看信息解读,iotop 的界面分为两部分:
- 上部:显示系统的总 I/O 速率和交换区(Swap)的 I/O 速率。
- 下部:动态更新的进程列表,显示每个进程的 I/O 情况。
- 关键列解读:
- IO>:进程的当前 I/O 优先级。
- DISK READ:进程的磁盘读取速率(如 KiB/s, MiB/s)。
- DISK WRITE:进程的磁盘写入速率。
- SWAPIN:进程从交换分区(swap)读取数据的速率。
- TID:线程 ID(如果使用 -P 参数,则显示 PID,也就是PID)。
- 关键操作:
- 按下 o 键:只显示正在发生实际 I/O 的进程,让输出更清晰。
- 按下 r 键:反转排序顺序。
- 按下 p 键:显示或隐藏线程/进程。
- 按下 a 键:显示累积的 I/O(从 iotop 启动开始算起),而不是实时速率。
案例一:模拟Oracle和MySQL磁盘IO读和写,通过iotop命令查看是那个线程/进程在用磁盘IO
一、模拟Oracle磁盘IO读和写:Oracle架构为多进程架构
模拟磁盘IO写:
liu_oracleoltp_ywcs_mh_list表有上千万数据,创建临时表SQL> create table liu_oracleoltp_ywcs_mh_list_bk as select * from liu_oracleoltp_ywcs_mh_list;查看prometheus告警,磁盘在大量的写操作(因为sda和sdb都是Oracle的数据文件目录盘),并且磁盘IO飙升到60%
通过iotop命令查看是那个线程/进程在用磁盘IO
可以看到有Oracle后台进程日志写进程(LGWR),还有非本地连接(LOCAL=NO)。LGWR进程是将重做日志缓冲区条目写入磁盘上的重做日志文件,有数据写入日志写进程也会写入数据。而实际执行SQL语句的是非本地连接(LOCAL=NO),也就是进程号5840。
默认TID列线程 ID(如果使用 -P 参数,则显示 PID,也就是PID)。
查看相关进程的SQL执行情况SELECT a.username, a.machine, a.program, a.sid, a.serial#, a.status, c.piece, c.sql_id, c.sql_text FROM v$session a, v$process b, v$sqltext c WHERE b.spid = '&spid' ---找出top中cpu高的SPID AND b.addr = a.paddr AND a.sql_address = c.address(+) ORDER BY c.piece;
模拟磁盘IO读:
liu_oracleoltp_ywcs_mh_list表有上千万数据,进行全表查询SQL> select * from liu_oracleoltp_ywcs_mh_list_bk;查看prometheus告警,磁盘在大量的读操作(因为sda和sdb都是Oracle的数据文件目录盘),并且磁盘IO飙升到20%
通过iotop命令查看是那个线程/进程在用磁盘IO
默认TID列线程 ID(如果使用 -P 参数,则显示 PID,也就是PID)。
查看相关进程的SQL执行情况SELECT a.username, a.machine, a.program, a.sid, a.serial#, a.status, c.piece, c.sql_id, c.sql_text FROM v$session a, v$process b, v$sqltext c WHERE b.spid = '&spid' ---找出top中cpu高的SPID AND b.addr = a.paddr AND a.sql_address = c.address(+) ORDER BY c.piece;
二、模拟MySQL磁盘IO读和写:MySQL架构为单进程多线程架构
模拟磁盘IO写:
liu_oracleoltp_ywcs_mh_list表有上千万数据,创建临时表SQL> create table liu_oracleoltp_ywcs_mh_list_bk like liu_oracleoltp_ywcs_mh_list; insert into liu_oracleoltp_ywcs_mh_list_bk select * from liu_oracleoltp_ywcs_mh_list;查看prometheus告警,磁盘在大量的写操作,并且磁盘IO飙升到94%
通过iotop命令查看是那个线程/进程在用磁盘IO
默认TID列线程 ID(如果使用 -P 参数,则显示 PID,也就是PID)。
查看相关线程的SQL执行情况select * from information_schema.processlist where Command != 'Sleep' order by Time desc;
查看ID 26的系统后台线程SELECT THREAD_ID, PROCESSLIST_ID, THREAD_OS_ID FROM performance_schema.threads WHERE processlist_id=26; ###这里的$processlist_id就是上面SQL查出来的ID查出系统后台线程为53294,并且iotop命令也看到了53294
模拟磁盘IO读:
liu_oracleoltp_ywcs_mh_list表有上千万数据,进行全表查询(无索引)SQL> select * from liu_oracleoltp_ywcs_mh_list_bk where flow_id='8a8284bd6662ecd3016666fe788c00ed';查看prometheus告警,磁盘在大量的读操作,并且磁盘IO飙升到50%
通过iotop命令查看是那个线程/进程在用磁盘IO
默认TID列线程 ID(如果使用 -P 参数,则显示 PID,也就是PID)。
查看相关线程的SQL执行情况select * from information_schema.processlist where Command != 'Sleep' order by Time desc;
查看ID 28的系统后台线程SELECT THREAD_ID, PROCESSLIST_ID, THREAD_OS_ID FROM performance_schema.threads WHERE processlist_id=28; ###这里的$processlist_id就是上面SQL查出来的ID查出系统后台线程为22230,并且iotop命令也看到了22230
最后留一个小问题给大家思考 🤔:
你遇到过最离谱的“三高”故障是什么?最后是怎么解掉的?
欢迎各位大佬在评论区留下你的故事~~。感谢阅读,下篇见 🚀



















5085

被折叠的 条评论
为什么被折叠?



