30款热门AI模型一站整合DeepSeek/GLM/Qwen 随心用限时 5 折。 点击领海量免费额度最近在团队内部做了一次线上问题排查的复盘一个典型的场景引发了大家的讨论一条平时运行良好的SQL昨天执行时间还是50毫秒今天突然飙升到5秒连带数据库服务器的CPU使用率直接冲到90%以上。这种性能的断崖式下跌往往意味着线上服务即将出现响应超时甚至雪崩。作为开发者或DBA面对这种紧急情况如何快速、系统地进行问题定位和解决是一项至关重要的实战能力。本文将以这个经典面试题为引结合MySQL/PostgreSQL等常见关系型数据库梳理一套从现象到根因的完整排查思路与实操命令涵盖监控、SQL分析、索引、锁、参数等多个维度帮助你在实际工作中从容应对。1. 问题现象与初步分析当收到“SQL变慢CPU飙升”的告警时我们首先需要将模糊的问题具体化并建立初步的排查方向。1.1 核心问题拆解“SQL变慢”和“CPU飙升”通常是同一个问题的两种表现其内在逻辑是SQL执行效率降低 → 需要处理的数据量或计算复杂度剧增 → 数据库服务器需要更多的CPU资源来完成查询 → CPU使用率上升。因此排查的核心目标是找出导致SQL执行计划改变或执行效率降低的根本原因。常见原因可以归纳为以下几个方面执行计划改变这是最常见的原因。数据库优化器为SQL选择了非最优的执行计划例如本该走索引的查询变成了全表扫描。系统资源争用其他进程或查询占用了大量CPU、内存或I/O资源导致该SQL执行缓慢。锁竞争SQL需要访问的数据被其他事务长时间锁定行锁、表锁等导致其必须等待。数据量突变查询涉及的表数据量在短时间内暴增例如定时任务灌入大量数据或者统计信息过时优化器对数据分布判断失误。数据库参数或状态异常例如缓冲区命中率骤降、临时表空间不足、日志文件切换频繁等。1.2 紧急信息收集清单在开始深入排查前应快速收集以下信息以便后续分析数据库类型与版本MySQL 5.7/8.0 PostgreSQL 13/14 不同数据库的命令和视图有差异。具体的SQL语句获取完整的、正在变慢的SQL文本。执行时间线何时开始变慢是持续性的还是间歇性的影响范围是单条SQL慢还是整个应用或某个功能模块都慢近期变更是否有过代码发布、配置更改、数据迁移、索引变更等操作2. 环境与工具准备工欲善其事必先利其器。一套趁手的监控和诊断工具是高效排查的基础。2.1 系统级监控工具首先从操作系统层面观察数据库服务器的整体状态。top/htop快速查看CPU、内存使用情况定位是哪个进程通常是mysqld或postgres消耗了大量CPU。top -c -p $(pgrep -f mysqld) # 监控MySQL进程vmstat/mpstat查看CPU各核心使用率、上下文切换、中断等详细情况。mpstat -P ALL 2 5 # 每2秒采样一次共5次查看所有CPU核心状态iostat查看磁盘I/O状况排除因磁盘瓶颈导致的等待。iostat -x 2 5 # 每2秒采样一次查看扩展I/O统计2.2 数据库内置诊断工具数据库自身提供了最强大的诊断视图和命令。对于MySQLSHOW PROCESSLIST;/SHOW FULL PROCESSLIST;查看当前所有连接正在执行的SQL快速定位慢查询和阻塞状态。performance_schema/sys库MySQL 5.6 提供了强大的性能模式。sys库是基于performance_schema的视图更易读。-- 查看当前消耗资源最多的SQL SELECT * FROM sys.session WHERE command ! Sleep ORDER BY cpu_time DESC LIMIT 5; -- 查看等待事件 SELECT * FROM sys.io_global_by_file_by_bytes LIMIT 10;EXPLAIN/EXPLAIN ANALYZE(MySQL 8.0.18)分析SQL执行计划的神器。慢查询日志 (slow_query_log)需要提前开启用于记录超过long_query_time阈值的SQL。对于PostgreSQLpg_stat_activity相当于MySQL的PROCESSLIST查看当前活动会话和查询。SELECT pid, usename, application_name, client_addr, state, query, query_start FROM pg_stat_activity WHERE state ! idle ORDER BY query_start;EXPLAIN/EXPLAIN (ANALYZE, BUFFERS)分析执行计划ANALYZE会实际执行并给出更准确信息。pg_stat_statements需要安装扩展用于追踪所有SQL的执行统计信息调用次数、总耗时、平均耗时等是定位“今天突然变慢”SQL的利器。-- 查询平均执行时间增长最多的SQL SELECT query, calls, total_exec_time, mean_exec_time, (mean_exec_time - lag(mean_exec_time) OVER (ORDER BY mean_exec_time)) as growth FROM pg_stat_statements ORDER BY growth DESC NULLS LAST LIMIT 10;3. 系统性排查流程与实战命令有了工具我们按照由外到内、由现象到根因的顺序进行排查。3.1 第一步定位问题SQL与进程首先确认是哪个具体的SQL和数据库进程导致了高CPU。MySQL操作连接数据库查看当前活跃的非睡眠进程。SHOW FULL PROCESSLIST;重点关注State列不是Sleep的进程查看Time执行时间、InfoSQL语句。通常CPU高的SQL其Time值会不断增长Info中可能是复杂的查询或全表扫描操作。使用sys库精确定位。-- 查看当前按CPU时间排序的会话 SELECT thd_id, conn_id, user, db, command, current_statement, statement_latency, cpu_time, rows_examined, rows_sent FROM sys.session WHERE command ! Sleep ORDER BY cpu_time DESC LIMIT 10;rows_examined检查的行数远大于rows_sent返回的行数是潜在的全表扫描信号。PostgreSQL操作查看当前活动查询。SELECT pid, usename, datname, application_name, client_addr, backend_start, state, query FROM pg_stat_activity WHERE state ! idle AND backend_type client backend ORDER BY backend_start;利用pg_stat_statements需提前安装并配置shared_preload_libraries找到历史性能变差的SQL。-- 重置统计信息谨慎操作仅用于测试环境或特定时段分析 -- SELECT pg_stat_statements_reset(); -- 查询总耗时或平均耗时异常的SQL SELECT queryid, query, calls, total_exec_time, min_exec_time, max_exec_time, mean_exec_time, stddev_exec_time, rows, shared_blks_hit, shared_blks_read FROM pg_stat_statements ORDER BY total_exec_time DESC -- 或 mean_exec_time DESC LIMIT 20;shared_blks_read高可能意味着缓存命中率低需要大量物理读。3.2 第二步分析SQL执行计划找到可疑SQL后使用EXPLAIN深入分析其执行计划这是排查的最关键步骤。MySQL示例假设问题SQL是SELECT * FROM orders WHERE user_id 100 AND create_date ‘2023-10-01’;-- 普通EXPLAIN EXPLAIN SELECT * FROM orders WHERE user_id 100 AND create_date ‘2023-10-01’; -- MySQL 8.0.18 可以使用EXPLAIN ANALYZE获取实际执行信息 EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id 100 AND create_date ‘2023-10-01’;解读输出关键列type: 访问类型。ALL全表扫描最差index全索引扫描次之range范围扫描、ref/eq_ref索引查找较好const常量最好。看到ALL就要警惕。key: 实际使用的索引。如果为NULL说明没用到索引。rows: 预估需要扫描的行数。这个值异常大是性能问题的直接指示。Extra: 额外信息。出现Using filesort文件排序或Using temporary使用临时表通常意味着性能开销大。PostgreSQL示例EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE user_id 100 AND create_date ‘2023-10-01’;解读要点查看计划树顶部的总成本cost和实际时间。观察扫描方式Seq Scan顺序扫描/全表扫描 vsIndex Scan索引扫描 vsBitmap Heap Scan位图索引扫描。BUFFERS选项可以显示共享块命中数hit和读取数readread多说明缓存未命中。ANALYZE会实际执行SQL给出更精确的时间注意在生产环境谨慎使用避免影响业务。3.3 第三步检查索引与统计信息如果EXPLAIN显示没有使用预期索引或扫描行数估算严重失准问题可能出在索引或统计信息上。1. 检查索引是否存在及是否有效-- MySQL SHOW INDEX FROM orders; -- 或使用更详细的information_schema SELECT * FROM information_schema.statistics WHERE table_name ‘orders’; -- PostgreSQL \di orders_* -- 在psql中 -- 或使用SQL查询 SELECT indexname, indexdef FROM pg_indexes WHERE tablename ‘orders’;确认在user_id和create_date字段上有合适的单列或复合索引。对于示例SQL复合索引(user_id, create_date)可能是最优的。2. 检查统计信息是否过时数据库优化器依赖统计信息如数据分布、唯一值数量等来选择执行计划。如果统计信息过时优化器可能做出错误判断。MySQLInnoDB表的统计信息是估算的。可以手动更新ANALYZE TABLE orders; -- 更新表的统计信息PostgreSQL同样使用ANALYZE。ANALYZE orders; -- 更新统计信息 VACUUM ANALYZE orders; -- 清理并更新统计信息可以检查pg_stat_all_tables的last_analyze和last_autoanalyze字段看统计信息是否近期有更新。3. 索引失效的常见原因隐式类型转换WHERE user_id ‘100’user_id是整数却用字符串比较可能导致索引失效。对索引列使用函数或运算WHERE DATE(create_date) ‘2023-10-01’会使create_date上的索引失效。不满足最左前缀原则对于复合索引(a, b, c)查询条件WHERE b 1 AND c 2无法有效利用该索引。3.4 第四步排查锁竞争如果SQL状态长时间处于Waiting for table metadata lockMySQL或waitingPostgreSQL可能是被锁阻塞。MySQL锁排查-- 查看当前锁信息 (MySQL 5.7需开启performance_schema) SELECT * FROM performance_schema.data_locks; SELECT * FROM performance_schema.data_lock_waits; -- 使用sys库视图更直观 SELECT * FROM sys.innodb_lock_waits;PostgreSQL锁排查-- 查看锁等待情况 SELECT blocked_locks.pid AS blocked_pid, blocked_activity.usename AS blocked_user, blocking_locks.pid AS blocking_pid, blocking_activity.usename AS blocking_user, blocked_activity.query AS blocked_statement, blocking_activity.query AS blocking_statement FROM pg_catalog.pg_locks blocked_locks JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid blocked_locks.pid JOIN pg_catalog.pg_locks blocking_locks ON blocking_locks.locktype blocked_locks.locktype AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid AND blocking_locks.pid ! blocked_locks.pid JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid blocking_locks.pid WHERE NOT blocked_locks.granted;找到阻塞源后需要评估是否可以终止阻塞事务KILL process_id或者优化业务逻辑减少锁持有时间。3.5 第五步检查数据库状态与参数数据库本身的健康状态和参数设置也会影响性能。1. 关键性能指标缓冲池命中率MySQL InnoDB Buffer Pool Hit Rate低于99%可能意味着内存不足导致大量物理磁盘读。-- MySQL SHOW GLOBAL STATUS LIKE ‘Innodb_buffer_pool_read%’; -- 计算命中率 (1 - Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests) * 100%连接数检查是否达到最大连接数限制max_connections。SHOW GLOBAL STATUS LIKE ‘Threads_connected’; SHOW VARIABLES LIKE ‘max_connections’;2. 可能相关的参数tmp_table_size/max_heap_table_size(MySQL)如果复杂查询创建的临时表超过此大小会在磁盘上创建临时表速度极慢。检查SHOW GLOBAL STATUS LIKE ‘Created_tmp_disk_tables’;是否激增。work_mem(PostgreSQL)用于排序和哈希操作的内存。如果设置过低会导致大量磁盘临时文件操作。检查EXPLAIN ANALYZE输出中是否有Disk:字样。4. 常见根因与解决方案基于以上排查我们可以归纳出几种典型场景及其应对策略。4.1 场景一统计信息过时导致执行计划退化现象SQL突然变慢EXPLAIN显示rows估算严重偏离实际例如实际扫描100万行估算只有100行本该走索引的查询变成了全表扫描。根因表经过大量INSERT/UPDATE/DELETE后数据库的自动统计信息收集任务如autoanalyze未及时触发或采样率不够导致优化器对数据分布判断错误。解决方案立即手动更新统计信息ANALYZE TABLE your_table;(MySQL) 或ANALYZE your_table;(PostgreSQL)。考虑调整自动分析autovacuum/autoanalyze的阈值、比例或调度频率。对于数据变化有规律的场景可以在业务低峰期设置定时任务手动分析大表。4.2 场景二索引失效或未使用现象EXPLAIN的key列为NULLtype为ALL。根因索引因上述原因函数、运算、类型转换失效。查询条件选择性差优化器认为全表扫描比索引回表更快。复合索引字段顺序与查询条件不匹配。解决方案重写SQL避免在索引列上使用函数或计算。创建更合适的索引考虑字段顺序、覆盖索引。使用FORCE INDEXMySQL或pg_hint_plan扩展PostgreSQL强制使用索引此为临时方案需谨慎长期应优化索引。4.3 场景三锁等待现象SQL状态长时间为“等待锁”CPU可能不高但响应时间极长。根因当前事务要访问的行或表被其他未提交的长事务锁定。解决方案使用上述锁排查命令找到并KILL阻塞源头确保业务允许。优化业务逻辑将长事务拆短尽快提交或回滚。检查事务隔离级别是否可以使用READ COMMITTED等更低级别。对于UPDATE/DELETE尽量使用索引条件缩小锁定范围。4.4 场景四资源瓶颈现象系统监控显示磁盘I/O等待高、内存交换swap频繁。根因物理内存不足缓冲池无法缓存热点数据。其他非数据库进程消耗大量资源。磁盘性能达到瓶颈。解决方案扩容服务器资源内存、更高速磁盘。优化数据库配置如增加innodb_buffer_pool_sizeMySQL、shared_buffersPostgreSQL。隔离数据库服务避免与其他资源密集型服务混部。4.5 场景五SQL本身或数据量问题现象SQL写法导致必然的低效如SELECT *、多表关联缺少条件、滥用子查询。根因随着数据量自然增长低效SQL的缺点被放大。解决方案SQL重写只取需要的列、优化JOIN顺序和条件、将子查询改为JOIN、使用分页避免一次性拉取大量数据。数据归档将历史冷数据迁移到其他存储减少主表数据量。读写分离将分析类慢查询路由到只读副本。5. 预防与最佳实践排查解决一次问题很重要但建立预防机制更重要。建立完善的监控告警体系监控数据库服务器的CPU、内存、磁盘I/O、连接数。监控慢查询数量、长事务数量、锁等待数量。设置阈值告警做到早发现、早处理。规范SQL开发与审核所有上线的SQL都必须经过EXPLAIN审查。建立索引创建、变更的审核流程。避免在循环中执行SQL使用批量操作。定期健康检查定期如每周检查关键表的索引使用情况和统计信息新鲜度。定期使用pt-query-digestMySQL或pgBadgerPostgreSQL分析慢查询日志找出潜在优化点。容量规划与架构优化根据业务增长趋势提前进行数据库容量规划。考虑引入缓存如Redis降低数据库压力。对于复杂查询考虑使用物化视图PostgreSQL或查询缓存谨慎使用。变更管理任何数据库结构变更DDL、索引变更、重要参数调整都应在低峰期进行并做好回滚预案。上线后密切观察一段时间内的数据库性能指标。面对“SQL昨天快今天慢CPU飙升”这类典型的生产环境性能问题切忌慌乱。按照定位进程 - 分析计划 - 检查索引/统计信息 - 排查锁/资源的系统化流程结合数据库提供的强大工具绝大多数问题都能被快速定位。真正的挑战往往在于对数据库原理如优化器逻辑、索引结构、锁机制的深入理解以及将排查经验固化为团队规范和预防措施的能力。掌握这套方法论不仅能让你在面试中游刃有余更能成为保障线上系统稳定性的中坚力量。 30款热门AI模型一站整合DeepSeek/GLM/Qwen 随心用限时 5 折。 点击领海量免费额度