
数据库CPU被打满应该是所有后端开发、DBA以及运维同学都经历过的噩梦。尤其是线上业务报障看到告警群里“MySQL CPU使用率 95%”的红色消息心跳都得漏半拍。很多人在这一步容易慌上来就重启数据库或者直接kill掉一堆进程运气好救回来了运气不好直接把事务打断、数据搞乱然后面对研发和业务的口诛笔伐。其实MySQL CPU使用率异常的排查非常有套路只要掌握了正确的方法论大概率半小时内能定位到根因最长也就一两个小时。这篇文章我结合自己多年处理这类故障的经验从“为什么CPU会高”的本质讲起再把完整的排查链路、核心命令、优化方法以及我踩过的坑全部摊开来讲一遍。不管你是刚入行被拉去救火的初级运维还是已经带了团队的技术负责人这套思路几乎可以直接复制到你自己的故障处理流程里。1. 先搞懂MySQL的CPU被谁吃掉了很多同学一看到CPU高就忙着加内存、加CPU或者开一堆缓存这些东西不能说完全没用但方向大概率是错的。在动任何“治疗手段”之前必须先搞清楚一个根本问题MySQL进程的CPU时间片到底消耗在哪一步1.1 从表象到底层CPU时间片的去向MySQL是一个多线程架构的数据库服务主线程负责接收网络连接然后为每个客户端连接分配一个线程来处理请求。当服务端执行一条SQL时CPU主要消耗在以下几个环节语法解析与优化器计算每一条SQL进入MySQL后都要经过词法解析、语法树生成、预处理、查询优化器计算执行计划等一系列步骤。复杂的SQL、子查询嵌套、大量Join条件都会导致优化器需要计算大量可能的执行路径CPU在这里会被明显消耗。存储引擎层的查找与扫描InnoDB引擎在读取数据时需要遍历B树索引、判断记录是否满足条件、进行行的可见性检查MVCC。当索引失效或者压根没有索引时就是全表扫描一条一条记录读出来比对数据量大时CPU和磁盘IO都会爆掉。排序与临时表操作ORDER BY、GROUP BY、DISTINCT、UNION这类操作如果数据量超过了sort_buffer_size或者临时表内存阈值MySQL会把数据放到磁盘临时表。这里不只是磁盘IO问题排序本身是一个极其消耗CPU的过程。连接线程的上下文切换当并发连接数非常高时操作系统的线程调度会频繁进行上下文切换。每一次切换CPU都要保存当前线程的状态、加载下一个线程的状态几百上千个连接同时活跃大量的CPU时间其实浪费在线程切换上真正执行SQL的时间反而很少。这里可以用一个比较通俗的类比来理解。数据库执行SQL就像一个人在图书馆找书如果你手里有索书号索引直接走到对应书架取出书整个过程很快如果没有索书号就只能从第一排书架开始一本一本翻全表扫描翻到最后一排才找到目标。这个“翻书”的过程就是CPU和IO在疯狂工作。1.2 CPU高并不等于业务量大先给问题定性遇到CPU告警我的习惯是先给问题定性而不是急着查SQL。定性可以分为以下几个维度是持续高还是间歇性飙高持续高通常说明有慢SQL在全时段地跑或者负载打满间歇性飙高往往是某个定时任务、报表查询在固定时间点触发或者连接数达到了某个阈值后产生雪崩。是整个机器CPU高还是MySQL进程单独高如果一个8核的机器CPU整体打满MySQL占700%说明MySQL自身就是元凶如果MySQL只占100%其他进程占了300%那问题可能在别的服务上不要误伤数据库。是从数据库升级后开始的还是新上的SQL导致的这个可以通过查看最近的上线记录、慢查询日志的增长趋势来判断往往能最快缩小范围。以前我遇到过一例非常典型的误判。某次告警显示MySQL所在机器CPU持续100%第一时间去查慢查询日志发现确实有很多慢SQL于是开始优化SQL折腾了一个小时没见效。后来仔细观察top输出才发现一个日志采集Agent在疯狂写文件MySQL进程本身只有不到30%的CPU占用。所以先通过top -Hp查看MySQL进程内部线程情况别上来就甩锅给SQL这是第一个要养成的习惯。2. 核心排查链路从进程到SQL的层层下钻这一章是全文的核心我把整套排查命令整理成了一条从上到下、层层深入的链路。每一次CPU告警我都会按这个顺序执行基本能定位到具体某条SQL或者某个参数。2.1 第一步用top和vmstat判断瓶颈边界登录到数据库服务器后第一步不是进MySQL而是先在操作系统层面做体检。执行top命令重点关注以下几项%Cpu(s)整体的用户态、内核态以及等待IO的比例。如果sy也就是内核态占比很高说明大量CPU时间消耗在系统调用和线程切换上这种情况通常和连接数过高、小事务并发太多有关。load average的值如果大于CPU核数的5到10倍说明系统已经严重过载。进程列表中mysqld进程的CPU占用率确认是不是MySQL自身在消耗CPU。top只是第一眼印象要看得更细可以用vmstat 1 5每秒采样一次、连续采样5次观察r运行队列长度、us用户态CPU占比、sy内核态CPU占比、wa等待IO占比这几个字段。如果wa很高意味着大量时间在等待磁盘IO这时候SQL往往是在做全表扫描或者排序落到磁盘临时表如果us很高说明CPU确实在全力计算比如大量聚合运算、正则匹配、行数统计等如果sy很高就要怀疑连接数过多导致的上下文切换。补充一个我平时很喜欢用的排查工具组合pidstat -t -p $(pgrep mysqld | head -1) 1这个命令可以按线程显示mysqld进程内部的线程CPU占用。如果只有个别线程CPU很高说明是某条SQL或某个特定操作导致的如果所有线程都高则要考虑系统层面的问题比如内存不足导致页交换、磁盘故障等。2.2 第二步连进MySQL用show processlist抓现行操作系统层面确认MySQL是元凶后立刻登录MySQL执行经典命令SHOW FULL PROCESSLIST;这个命令会把当前所有连接到MySQL的会话线程信息列出来重要的字段有Id、User、Host、db、Command、Time、State、Info。排查时要重点看两类线程一是Time很长、State为Sending data、Sorting result、Copying to tmp table的查询线程这些是正在执行慢查询的SQL二是大量Command为Sleep的线程如果这类空闲连接数量几百上千虽然单个不消耗CPU但大量连接本身就会带来调度开销。Info字段展示了当前正在执行的SQL语句不过可能不完整可以在拿到SQL后用下面的方式补全SELECT * FROM information_schema.processlist WHERE id 指定连接ID\G拿到具体的SQL之后不要急着复制出来测试先在脑海里做一个粗略判断这个SQL的查询条件是否命中了索引是否对索引列做了函数计算Join的表数量和顺序是不是合理如果一眼就能看出问题直接进入后面的优化阶段如果看不出问题就把SQL拿到测试环境用EXPLAIN分析。这里要特别提醒一下线上环境执行SHOW FULL PROCESSLIST如果连接数特别多、线程特别多时本身也会产生一定开销但相比直接杀进程这个命令的还是非常温和的。另外如果连不上MySQL比如连接数打满可以临时在配置文件里跳过权限验证重启一下或者通过增加一个保留连接max_connections 1的方式进去这个技巧后面会展开讲。2.3 第三步开启慢查询日志与performance_schema定位历史SQL很多CPU告警是偶发性的你被叫上线的时候那条罪魁祸首SQL可能已经执行完了SHOW FULL PROCESSLIST里看不到任何异常。这时候就需要靠慢查询日志和performance_schema来复盘。快速开启全局慢查询日志的方法SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1; SET GLOBAL log_output TABLE;将long_query_time设置为1秒的意思是所有执行时间超过1秒的SQL都会被记录下来。注意log_output设置为TABLE后日志会写入mysql.slow_log表排查起来比文件方便很多。查询慢SQLSELECT start_time, query_time, rows_examined, rows_sent, db, sql_text FROM mysql.slow_log ORDER BY start_time DESC LIMIT 50;rows_examined是表示这条SQL实际扫描了多少行数据rows_sent表示最终返回了多少行。这两个字段的比值非常关键如果前者是数百万、后者只有几十说明这条SQL在“大海捞针”索引使用情况大概率有问题。performance_schema是更底层的分析工具。比如要看哪类SQL消耗的CPU时间最多MySQL 5.7支持events_statements_summary_by_digest可以执行SELECT SCHEMA_NAME, DIGEST_TEXT, COUNT_STAR, SUM_ROWS_EXAMINED, SUM_ROWS_SENT, SUM_TIMER_WAIT / 1000000000 AS total_sec FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 20;这条查询可以直接列出最消耗时间的SQL模板配合慢查询日志去查看对应的SQL明细。不过要注意performance_schema有一定内存开销如果线上实例本身资源已经吃紧开启之前最好评估一下风险。更稳妥的办法是确认开启performance_schema开关然后重启数据库或者在压力可控的窗口期开启相关消费者。2.4 第四步用pt-query-digest分析慢查询日志如果慢查询日志内容很多人工一条条看会疯掉的。Percona Toolkit里的pt-query-digest是处理这个问题的神器它可以对慢查询日志做聚合分析把相同结构的SQL归为一类统计出每类SQL的执行次数、平均耗时、总耗时、扫描行数等。假设慢查询日志文件在/var/lib/mysql/slow.log分析命令pt-query-digest /var/lib/mysql/slow.log slow_analysis.txt打开生成的slow_analysis.txt重点看两个部分。第一部分是“Profile”它会把所有SQL按照总耗时降序排列最前面的就是最大瓶颈。如果某类SQL的总耗时占比超过50%基本可以锁定主要矛盾。第二部分是每个SQL的详细统计包括Rows examine平均扫描行数、Rows send平均返回行数、Query time distribution执行时间分布。如果一个SQL平均扫描100万行却只返回10行那它的索引设计基本是无效的优化空间巨大。我习惯在定位到问题SQL后顺手做一个复盘记录记下当时的CPU使用率、扫描行数、执行时间优化之后再进行一次对比。这种方式既方便自我总结也能在向领导汇报时提供直观的数字支撑。3. 实战案例拆解SQL索引失效如何把CPU打爆这一章用一个真实场景还原整个排查过程。为了便于理解我对表结构和数据量做了一些简化但排查思路和线上完全一致。3.1 简化表结构与问题描述假设有一张订单表t_orderCREATE TABLE t_order ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(64) NOT NULL, user_id int NOT NULL, status tinyint NOT NULL DEFAULT 0, amount decimal(10,2) NOT NULL, created_at datetime NOT NULL, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB;表里有大约800万行数据。某个工作日10点业务方反馈后台订单查询页面打开非常慢紧接着告警系统发来MySQL CPU持续95%以上的通知。登录服务器先做观察top中mysqld占CPU 600%8核机器vmstat里us非常高wa一般说明CPU是在拼命计算而非等待磁盘。进入MySQL执行SHOW FULL PROCESSLIST发现有好几条类似下面的查询在跑有些已经执行了30多秒SELECT order_no, user_id, amount, created_at FROM t_order WHERE DATE(created_at) 2024-06-01 ORDER BY amount DESC LIMIT 50;3.2 用EXPLAIN看清执行计划的真相将这条SQL在测试环境执行EXPLAINEXPLAIN SELECT order_no, user_id, amount, created_at FROM t_order WHERE DATE(created_at) 2024-06-01 ORDER BY amount DESC LIMIT 50;结果让人一眼看出问题列名值typeALLkeyNULLrows8000000filtered100.00ExtraUsing where; Using filesorttype ALL代表全表扫描key NULL代表没有使用任何索引rows 8000000说明优化器预估需要读取全表800万行Extra里的Using filesort表示需要对结果进行文件排序也就是说在内存中或磁盘上做ORDER BY排序。这条SQL有两个致命点。第一个致命点是WHERE DATE(created_at) 2024-06-01在created_at字段上套了DATE()函数导致原本可能在这个字段上建立的索引完全失效。MySQL的优化器对于索引列上套了函数或表达式的查询条件基本无法直接使用B树索引进行范围匹配只能退化为全表扫描做逐行计算后筛选。第二个致命点是ORDER BY amount DESC排序字段和查询条件字段不一致即使WHERE条件命中了索引排序还是要另外单独处理当数据量巨大时排序过程会产生临时文件进一步消耗CPU和磁盘IO。3.3 优化方案先改SQL再补索引针对这条慢SQL最优的解决思路分两步走。第一步SQL改写避免在索引列上使用函数。既然created_at是datetime类型如果业务上要查询某一天的数据合理的写法应该是SELECT order_no, user_id, amount, created_at FROM t_order WHERE created_at 2024-06-01 00:00:00 AND created_at 2024-06-02 00:00:00 ORDER BY amount DESC LIMIT 50;改写后优化器可以对created_at做范围查询如果有合适的索引就能用上。但依然有一个问题排序字段amount和范围查询字段created_at不一致需要额外排序。第二步根据实际业务场景建立联合索引。如果这类的查询是高频的可以创建一个(created_at, amount)的联合索引让MySQL在通过时间范围过滤数据时直接按amount顺序读取省去filesortALTER TABLE t_order ADD INDEX idx_created_at_amount (created_at, amount);这里补充一个联合索引的底层原理。B树的索引结构是按照索引列的顺序逐层排序的(created_at, amount)这个索引会先按created_at排序同一created_at下再按amount排序。因此当WHERE条件限定了created_at的范围时索引内部已经天然按照amount排好了序优化器直接顺序扫描索引就能得到有序结果不需要额外的排序步骤。加完索引之后再次EXPLAIN结果变为列名值typerangekeyidx_created_at_amountrows12000ExtraUsing index condition从全表扫描800万行锐减为范围扫描1.2万行filesort也消失了。这条SQL在优化前的执行时间大约是35秒优化后只有0.05秒CPU使用率肉眼可见地降了下来。3.4 从一次优化引出参数调优问题SQL处理完CPU已经从95%降到了50%左右但仍然偏高。继续观察发现还有一些并发很高的简单查询在占用CPU比如按照user_id查询这个用户最近的订单SELECT * FROM t_order WHERE user_id 123456 ORDER BY id DESC LIMIT 20;这个SQL本身走了idx_user_id索引看着没啥问题。问题在于SELECT *意味着查到索引后还要根据主键回表查询其他列的数据一次查询回表20行如果这个SQL的QPS很高回表操作积累起来就是大量的随机读和CPU开销。优化方式是覆盖索引也就是把要查询的列都放到索引里ALTER TABLE t_order ADD INDEX idx_user_id_created_at (user_id, created_at, amount, order_no);这样查询条件user_id、排序字段id这里如果要完全覆盖也建议加id或者直接改成created_at排序以及需要返回的列都包含在索引中MySQL可以直接在索引中完成查询完全不需要回表。Extra会出现Using index这是代表“完全覆盖索引扫描”的标志性能提升非常明显。4. 连接数异常高涨导致的CPU耗尽除了SQL本身的问题另一种非常常见的情况是单条SQL都不慢但连接数高到吓人CPU被线程调度消耗殆尽。4.1 为什么连接数高会导致CPU高MySQL每接收一个客户端连接就要创建一个线程或者从线程池中分配一个线程来处理该连接上的请求。每个线程都有自己的栈空间操作系统为了让所有线程公平地使用CPU会不断进行上下文切换。打个比方一个餐厅只有一个厨师CPU核心正常情况下同时只有5桌客人点菜厨师可以连贯地做菜。但如果一下子涌进来200桌客人每桌都在催菜厨师不得不每道菜只做五分钟就放下去处理另一桌的需求结果大部分时间浪费在“换菜谱”和“找锅铲”上真正做菜的时间少得可怜。数据库也是一样当活跃连接数远超CPU核心数时大量的CPU时间片消耗在线程切换上真正执行SQL的时间被压缩导致所有SQL都变慢而变慢的SQL又进一步延长连接占用时间形成恶性循环。4.2 排查连接数问题的核心命令确认是否存在连接数异常主要看两个指标当前连接数和最大连接数。执行SHOW GLOBAL STATUS LIKE Threads_connected; SHOW GLOBAL STATUS LIKE Threads_running; SHOW GLOBAL STATUS LIKE Max_used_connections; SHOW VARIABLES LIKE max_connections;Threads_connected代表当前连接数Threads_running代表正在执行SQL的活跃连接数Max_used_connections是历史最大连接数。如果Threads_connected长期接近max_connections说明系统一直在连接数打满的边缘徘徊。如果Threads_running经常超过CPU核心数说明有很多请求在同时执行CPU必然过载。如果连接数确实打满了还需要继续确认是谁在发起这么多连接。可以从进程列表里看到SELECT user, host, db, command, count(*) FROM information_schema.processlist GROUP BY user, host, db, command ORDER BY count(*) DESC;实际业务中连接数暴涨最常见的原因有这么几类应用侧的数据库连接池配置过大比如Spring Boot的maximum-pool-size设置100甚至更高每个应用实例再部署多个节点慢查询导致应用等待时间变长为保持并发吞吐连接池不断创建新连接某段时间流量突增比如秒杀、活动预热还有可能是负载均衡层面出现健康检查异常导致流量集中路由到某一台数据库实例上。4.3 紧急处置方法和长远的参数调整如果连接数已经打满正常的连接建立不进去了可以尝试通过预留的额外连接强制登录。MySQL在max_connections之外预留了一个连接数用于管理员在紧急情况下登录这个参数叫super_read_only无关真实的关键参数是max_connections1但默认情况下有个extra_max_connections的逻辑。方便的做法是提前在配置中设置[mysqld] max_connections 500同时通过skip-networking0保持网络连接。实际遇过极端场景连接数把端口占满、无法新建连接这种情况下如果允许可以在配置文件里调整max_connections为更大的值并重启数据库但重启会影响所有现有连接需要和业务方确认可维护时间窗口。从长远来看更需要做的是限制应用侧的连接池大小。以Java的HikariCP为例标准的配置建议spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000maximum-pool-size 20对于大多数业务系统已经足够了。连接池大小并不是越大越好理论上连接数最好和CPU核心数保持在一个合理的比例太多连接反而造成调度浪费。一个经验公式是核心业务数据库的活跃连接数控制在CPU核心数的2到4倍以内比较合适读多写少场景适当放宽全站统一入口的场景把连接数控制在CPU核心数2倍以内避免大量连接在锁等待上互相消耗。如果应用侧短时间无法调整MySQL侧也有一些缓解手段。比如开启线程池MariaDB或Percona分支支持官方MySQL 8.0不支持线程池但可以通过extra_port等方式做一些应急限制或者使用连接复用和SQL限流工具来做保护。线上环境最好加上连接数监控以及活跃连接数监控一旦超过阈值立即告警避免等用户反馈才发现问题。5. 配置层面的隐患不只是SQL的锅当你确认所有SQL都很快、连接数也不高但CPU依然高的时候问题很可能出在MySQL或操作系统的配置上。5.1 InnoDB缓冲池与临时表配置的关系InnoDB缓冲池innodb_buffer_pool_size是MySQL最重要的内存区域它用来缓存数据页和索引页。如果这个值设置得太小MySQL就必须频繁地把数据从磁盘读入内存也会频繁把脏页刷到磁盘这会产生大量IO操作。CPU在等待磁盘IO的空档期虽然处于等待状态但调度和重试机制本身会带来额外开销。更关键的是当内存不足时排序和临时表操作会被迫落到磁盘排序过程又回归到CPU的负担上。一个比较通用的建议是innodb_buffer_pool_size设置为服务器物理内存的60%到75%左右具体取决于服务器是专机专用还是混合部署。如果是专机专用、服务器内存为64GB可以考虑设置为40GB到48GB。设置过大会导致操作系统本身几乎没有内存可缓存文件甚至触发swap反而不如小一点稳。临时表的配置同样值得关注。MySQL执行大批量GROUP BY、ORDER BY、DISTINCT时需要创建临时表。如果tmp_table_size和max_heap_table_size都太小临时表很快就从内存转为磁盘临时表使用MyISAM或InnoDB on-disk临时表。磁盘临时表的读写速度比内存慢几个数量级而且写入临时表时数据是逐步累积的每一行数据的写入都可能触发CPU参与缓冲区管理和索引维护。查看临时表使用情况SHOW GLOBAL STATUS LIKE Created_tmp_disk_tables; SHOW GLOBAL STATUS LIKE Created_tmp_tables;如果Created_tmp_disk_tables / Created_tmp_tables的比例超过25%说明内存临时表配置大概率不够可以考虑适当调大tmp_table_size和max_heap_table_size。5.2 注意操作系统层面的大页和多核调度问题Linux操作系统层面也有两个容易踩的坑。第一个坑是vm.swappiness设置过高默认值是60。这个参数控制操作系统将内存页交换到swap的倾向如果MySQL所在机器内存充足但swappiness过高操作系统可能把一些不太活跃的MySQL内存页换到swap中之后访问这些页时又发生缺页中断导致性能骤降。DBA一般建议将其调整为10甚至0并通过sysctl -w vm.swappiness10临时生效持久化需要写入/etc/sysctl.conf。第二个坑是NUMA非统一内存访问架构问题。在多路CPU的服务器上每个CPU有自己的本地内存MySQL线程可能被调度到远端CPU上访问内存这会增加访问延迟。有些场景分配MySQL的CPU核数和内存节点不对齐就会导致大量远端内存访问CPU和内存都在高压力下。此时可以考虑关闭NUMA、或者用numactl --interleaveall让内存访问均匀分布但要测试确认没有副作用后再上生产。这些系统层的调优往往会在高并发场景下带来惊喜但在业务量小的环境里不容易看出差异所以经常被忽视。我个人建议在每次新服务器上线前就完成这些基础配置的初始化而不是等到出事后才去排查。6. 常见问题速查表和我的避坑经验这一章把平时排查中常遇到的场景、原因和解决方法整理成表格方便大家以后直接对照。每个问题后面我也会补一句自己踩过的坑或者总结的经验。6.1 CPU排查常见问题速查表现象特征常见原因首选排查命令解决方向CPU持续高进程列表中大量Sending dataSQL全表扫描/索引失效SHOW FULL PROCESSLIST; EXPLAIN优化SQL、加联合索引或覆盖索引CPU周期性飙高定时任务、报表查询未走索引慢查询日志、events_statements_summary_by_digest错峰执行、拆分查询、优化SQL连接数打满CPU高但SQL不慢连接池配置过大、流量突增information_schema.processlist分组统计调整连接池限制最大连接数必要时重启并发不高但是排序操作很多排序字段无索引filesort严重EXPLAIN看Extra的Using filesort建立合适的联合索引内存和CPU同时告警缓冲池设置过小、临时表落磁盘SHOW GLOBAL STATUS LIKE Created_tmp_disk_tables调大buffer pool和tmp_table_size磁盘等待高但CPU也让高慢SQL和业务形成恶性循环vmstat观察wa和us优先优化IO慢SQL可能涉及SSD升级或拆分数据量CPU高但mysqld占用不高其他进程干扰top检查其他进程排查对应进程不要盲目优化数据库CPU高伴随大量锁等待锁竞争、死锁引起事务堆积SHOW ENGINE INNODB STATUS; 锁监控优化事务大小、加索引覆盖锁范围、减少长事务6.2 我踩过的一些坑第一别在CPU高的时候贸然重启数据库。重启看似能一秒清空所有状态但重启后缓冲池被清空需要预热瞬间大量SQL全部走磁盘读取CPU和IO压力不降反升。除非是连接数彻底打满无法登录、且没有任何其他方式恢复的绝境否则不建议走这条回头路。第二别被一条“看起来很正常的SQL”骗了。有时候EXPLAIN结果显示key是主键、type是ref看着没什么问题但实际执行时因为使用了OR条件、IN列表过长、或者ORDER BY和WHERE字段不匹配导致优化器选择了临时表和文件排序。这些都要在慢查询日志中结合rows_examined才能真正发现问题。第三SHOW FULL PROCESSLIST看到的SQL可能不是根源。比如你看到一个UPDATE在执行看起来很慢但真正原因可能是它之前长长的事务一直没提交锁被占用导致这个UPDATE卡在等待锁上。所以看到State里面有updating、locked时要结合information_schema.innodb_trx看事务状态。第四不要忽略自增主键频繁插入带来的页分裂开销。很多同学在优化时只关注SELECT但写密集的批量插入、频繁删除操作也会导致索引页分裂、B树结构调整这个过程CPU消耗很大。排查的时候可以把SHOW GLOBAL STATUS LIKE Handler_write和Innodb_pages_written拉出来看看判断是否有异常高的写放大。6.3 最后的几条建议说一下我自己的处理节奏。接到CPU告警后我会先花两分钟回答三个问题数据库是否还能登录CPU是稳态高还是抖动高有没有明显的时间关联然后按照查进程、查SQL、查配置的顺序走千万不要跳过操作系统层面的判断直接去看SQL也不要看到一条慢SQL就急着kill。补充一个实用小技巧给MySQL配置一个“应急账号”权限只包含登录和查询用于CPU告警时快速进入数据库查看状态避免使用主账号引发安全问题。还可以把常用的排查SQL写成SQL脚本或者Shell脚本告警时一键执行节省救火时间。从实际经验来看MySQL CPU使用率高的问题百分之八十以上都是慢SQL导致的剩下的百分之二十分布在连接数、配置、系统层面。只要把排查链路练熟了形成了自己的“肌肉记忆”下次再遇到告警就能从容很多。希望这篇分享能帮你少走一些我走过的弯路。