数据库CPU飙升90%紧急排查:从SQL执行计划到系统资源的系统性诊断

🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度

昨天下午,一个紧急电话打过来,说线上核心业务突然变慢,用户投诉激增。登录服务器一看,数据库CPU直接飙到90%,监控面板一片飘红。团队里最资深的DBA休假了,几个开发同学对着监控图面面相觑,有人提议“重启试试?”,有人开始翻看最近上线的代码。

这场景太典型了。一条昨天还跑得好好的SQL,今天突然成了性能杀手。问题往往不是出在“数据库”这个黑盒本身,而是它周围的一切——数据、查询、资源、乃至一个不经意的配置变更——共同作用的结果。排查这种问题,需要的不是对某个命令的机械记忆,而是一套清晰的、可复现的思考框架。它像侦探破案,先确定案发现场(现象),再寻找线索(日志、监控),最后锁定真凶(根本原因)。

今天,我们就来彻底拆解这个经典面试题背后的实战排查逻辑。它考察的绝不仅仅是几个EXPLAINSHOW PROCESSLIST命令,而是你能否在高压下,用系统化的方法,把“感觉慢了”这种模糊描述,精准定位到一个可解释、可解决的具体问题上。

1. 第一反应:稳住阵脚,确认问题范围

当警报响起,CPU 90%时,最忌讳的就是盲目行动。你的第一组操作,决定了接下来是快速止损,还是让问题雪上加霜。

1.1 立即获取“现场快照”

不要急着去改任何配置或杀进程。首先,用最快的速度获取问题发生时的系统状态快照。这相当于刑侦中的保护现场。

  1. 连接数据库:使用具有足够权限的账号(如root)连接到出问题的数据库实例。如果连接已经非常缓慢,考虑从本地或跳板机使用带-h参数的客户端快速连接。
  2. 查看当前活动会话:执行SHOW FULL PROCESSLIST;(MySQL) 或SELECT * FROM pg_stat_activity WHERE state != 'idle';(PostgreSQL)。这个命令能立刻告诉你:
    • 现在有哪些连接?
    • 每个连接正在执行什么SQL?(Info列)
    • 它们已经运行了多久?(Time列)
    • 处于什么状态?(State列,如Sending data,Locked,Creating sort index等)关键点:立刻寻找那些Time值巨大(比如几百秒)且State不是Sleep的会话。这些就是最可疑的“嫌犯”。把对应的SQL语句完整记录下来。
  3. 确认系统负载:在数据库服务器上,快速运行tophtop命令。确认高CPU消耗的进程确实是数据库服务进程(如mysqld,postgres),而不是其他进程。同时观察%wa(I/O等待)指标,如果也很高,说明可能遇到了磁盘瓶颈。

注意:如果SHOW PROCESSLIST都执行得非常慢,说明系统可能已经严重过载。此时可以考虑使用mysqladmin工具或操作系统级别的kill命令终止几个最耗时的查询来救急,但务必先记录下它们的ID和SQL,以便后续分析。

1.2 区分“症状”与“病因”

CPU 90%是症状,那条跑了5秒的SQL是直接病因。但我们的目标是找到根本病因——为什么同一条SQL,昨天和今天的行为天差地别?

此时,你需要建立一个初步的排查假设清单,按可能性从高到低排序:

  1. 数据量突变:SQL查询的表,数据量是否发生了剧增?(例如,夜间批量任务导入大量数据,或某个条件过滤失效导致扫描全表)。
  2. 执行计划改变:数据库优化器为这条SQL选择的“执行计划”发生了变化,导致走了低效的索引或全表扫描。
  3. 系统资源争用:同一时刻,有其他重型查询或任务在运行,抢占了CPU、内存或I/O资源。
  4. 锁竞争:查询需要访问的数据被其他事务长时间锁定,导致查询挂起等待。
  5. 外部因素:服务器本身资源(CPU、内存、磁盘)出现故障或配置被修改。

带着这些假设,我们进入下一阶段的深度调查。

2. 深度调查:剖析那条“变质”的SQL

拿到了可疑SQL语句,接下来就要像法医一样对它进行解剖。核心工具是数据库提供的执行计划分析命令。

2.1 获取并解读执行计划

以MySQL为例,在测试环境或从线上拉取的慢查询日志中,对那条慢SQL执行EXPLAIN(或EXPLAIN FORMAT=JSON获取更详细信息)。

EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND create_date > '2023-10-01';

你需要重点关注EXPLAIN输出中的这几个关键列:

列名含义可能的问题信号
type访问类型ALL(全表扫描)是最大的危险信号。理想情况应是const,eq_ref,ref,rangeindex(全索引扫描)有时也低效。
key实际使用的索引如果这一列为NULL,说明没有使用索引。检查是否与你期望的索引一致。
rows预估需要扫描的行数这个数字是否巨大?比如从几十激增到几百万。这直接关联CPU消耗。
Extra额外信息Using filesort(文件排序),Using temporary(使用临时表)通常意味着高开销操作。

对比分析是灵魂:光看今天的执行计划不够。你需要对比昨天的正常执行计划今天的异常执行计划。如果公司有慢查询监控平台(如Archery, Yearning, pt-query-digest),可以从中历史记录。如果没有,这提醒了我们建立基线(baseline)的重要性。

假设你发现,昨天typeref(使用了索引),今天却变成了ALL(全表扫描)。那么问题就聚焦了:为什么优化器不用索引了?

2.2 追问“为什么不用索引?”

索引失效是导致SQL“变质”最常见的原因之一。你需要沿着以下线索排查:

  1. 索引是否被误删?:检查表结构,确认预期的索引(比如idx_user_id)是否还存在。SHOW INDEX FROM orders;
  2. 字段类型是否匹配?WHERE user_id = '123'(字符串)和WHERE user_id = 123(整数)可能导致索引失效,如果user_id是整型的话。
  3. 是否对索引列做了计算或函数操作?WHERE YEAR(create_date) = 2023会导致无法使用create_date上的索引。
  4. 数据分布是否巨变?:这是最隐蔽的原因。优化器会根据统计信息(如索引的区分度、数据分布直方图)来判断使用索引是否划算。如果user_id=123的记录,昨天只有10条,今天因为数据导入变成了10万条,优化器可能认为“既然要返回这么多数据,不如直接全表扫描更快”,从而抛弃索引。
    • 检查表统计信息ANALYZE TABLE orders;(更新统计信息),然后再次查看执行计划。
  5. 是否使用了OR!=等导致索引失效的条件?

通过这一步,你很可能已经能将问题范围从“SQL慢了”缩小到“因为某个原因,索引没被用上”。

3. 扩大搜索:检查系统与环境的“不在场证明”

如果SQL本身的执行计划看起来“合理”,或者变更不足以解释5秒的差距,那么就要怀疑是否是环境出了问题。你需要为其他可能性寻找“不在场证明”或确认其“犯罪事实”。

3.1 资源争用与锁

  1. 检查锁信息
    • MySQL:SHOW ENGINE INNODB STATUS\G,查看LATEST DETECTED DEADLOCKTRANSACTIONS部分。
    • 更直接地,查询information_schema.INNODB_LOCKSINNODB_LOCK_WAITS表。
    • 如果发现你的慢查询正在等待某个锁(Lock wait timeout),那就要找到持有锁的会话和SQL。
  2. 检查系统资源
    • 磁盘I/O:使用iostat -x 1查看%utilawait。如果磁盘利用率持续100%或等待时间很长,说明磁盘是瓶颈。
    • 内存:使用free -hvmstat 1。观察是否发生了大量交换(si/so),交换会导致性能急剧下降。
    • 网络:虽然不常见,但网络抖动也可能影响分布式数据库或应用连接。

3.2 外部变更与配置

这是“昨天好今天坏”问题的关键排查方向。你需要像一个变更管理员一样提问:

  1. 数据库是否有变更?
    • 是否进行了版本升级/小版本更新?
    • 是否调整了关键参数?如innodb_buffer_pool_size(缓冲池)、query_cache_*(查询缓存,MySQL 8.0已移除)、optimizer_switch(优化器开关)等。对比昨天和今天的参数文件。
  2. 应用是否有变更?
    • 是否发布了新版本?新代码是否传入了不同的查询参数?(例如,昨天查user_id=123,今天查user_id=NULL)。
    • 连接池配置是否被修改?是否有连接泄漏导致并发量激增?
  3. 数据是否有变更?
    • 除了数据量,数据结构是否变化?增加了新字段?字段类型改变?
    • 是否运行了大的UPDATE/DELETE语句,产生了大量碎片或死元组(对于PostgreSQL,需要VACUUM)?
  4. 硬件/底层是否有变更?
    • 云数据库实例是否被迁移或底层硬件降配?
    • 是否从本地SSD盘被迁移到了网络存储?

4. 构建你的长效防御体系:从救火到防火

一次成功的故障排查,价值不仅在于解决了当下问题,更在于如何避免重蹈覆辙。你需要把这次紧急排查的经验,沉淀为团队可复用的监控和防御流程。

4.1 建立监控与告警基线

不能等CPU到90%才行动。应该建立更前置的指标监控:

  1. 慢查询监控:启用并定期分析慢查询日志(slow_query_log)。设置合理的long_query_time(如1秒)。使用工具(如pt-query-digest)对慢SQL进行聚合分析,找到最耗资源的查询模式。
  2. 性能指标监控
    • QPS/TPS:每秒查询/事务数,突增可能预示问题。
    • 连接数:监控活跃连接数和最大连接数使用率。
    • 缓冲池命中率:对于InnoDB,缓冲池命中率应接近100%。低于95%可能意味着内存不足或扫描了太多数据。
    • 锁等待:监控平均锁等待时间。
  3. 设置智能告警:不要只对CPU使用率告警。对“慢查询数量突增”、“连接数暴增”、“缓冲池命中率下降”等指标设置告警,能在问题影响用户体验前提前发现。

4.2 制定标准排查清单(Runbook)

将本次的排查步骤固化下来,形成团队的“数据库性能问题标准排查清单”:

1. 症状确认: - [ ] 确认CPU高消耗进程是否为数据库。 - [ ] 使用 `SHOW PROCESSLIST` 定位可疑会话,记录SQL。 2. SQL分析: - [ ] 获取当前慢SQL的 `EXPLAIN` 执行计划。 - [ ] 对比历史正常执行计划(从监控平台或备份计划获取)。 - [ ] 检查索引是否存在、是否被使用、数据分布是否变化。 3. 环境检查: - [ ] 检查锁信息 (`SHOW ENGINE INNODB STATUS`, 锁表查询)。 - [ ] 检查系统资源 (iostat, vmstat, top)。 - [ ] 回顾最近变更 (数据库配置、应用发布、数据迁移)。 4. 应急与根治: - [ ] 紧急情况:考虑终止问题会话(先记录!)。 - [ ] 根本解决:根据分析结果,选择优化SQL、调整索引、更新统计信息、修改配置或扩容资源。 - [ ] 事后:将案例录入知识库,优化监控告警规则。

4.3 推行预防性实践

  • SQL审核:在上线前,对所有新增或变更的SQL进行执行计划审核,避免携带性能问题的代码进入生产环境。
  • 定期健康检查:每周或每月例行检查数据库的索引有效性、统计信息准确性、表碎片情况。
  • 容量规划与压测:对核心业务SQL进行压力测试,了解其性能边界,提前规划扩容。

回到开头的场景,那条从50毫秒“变质”到5秒的SQL,就像一面镜子,照出的往往不是单一的技术漏洞,而是从开发习惯到运维体系的一系列缝隙。真正的资深,不是背熟了所有的命令,而是在警报响起的那一瞬间,脑子里能立刻浮现出一张清晰的排查地图,知道第一步该看哪里,每一个异常信号可能指向何种根源,以及如何在解决问题的同时,把这次教训变成团队未来的一道防火墙。这个过程,远比单纯给出一个“优化索引”的答案要复杂和深刻得多。

🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度