一、自查真正的超时发生在哪层
先排除假性超时。
客户端超时配置过短:JDBC/连接池的 connectTimeout、socketTimeout 或框架的慢查询阈值设得过低(如200ms),此时数据库本身负载很低,单纯是配置和正常响应时间不一致。
网络层问题:应用和数据库之间有防火墙、安全组方法,或网络抖动、丢包、带宽打满。从应用服务器 telnet <db_ip> 3306 或 ping 检查连通性和延迟。
连接池耗尽导致排队:业务线程在等待连接池返回连接,达到配置的 maxWait 时间后抛出“获取连接超时”错误。这时问题不在数据库处理慢,而在连接不够或连接泄漏。
二、数据库诊断
登录数据库主机或云监控确定四大资源是不是被打满。
CPU:危险阈值是 %usr + %sys 大于 85% 并不断。常见原因为慢查询大量思路读/排序、高并发、计算密集型函数。紧急动作是杀掉最慢的查询,并立即开启慢查询日志。
内存:危险阈值是 %used 大于 90% 且有 SWAP 使用。常见原因是缓冲池不足、连接数过多或内存泄漏。紧急动作是检查最大连接数,适当降低除innodb_buffer_pool_size之外的非重要内存占比。
磁盘 IO:危险阈值是 %util 接近 100%,且 await 值剧增。常见原因是大量随机写、日志同步、缺少索引导致的全表扫描。紧急动作可临时关闭binlog/redo log的强刷方法(如危急只读场景下暂时设 sync_binlog=0),并立即优化慢查询。
磁盘空间:危险阈值是数据目录或日志目录使用率超过 95%。常见原因是大事务生成超大undo log、binlog积压或备份文件堆积。紧急动作是清理无用日志,调整binlog_expire_logs_seconds等参数。
常用排查命令MySQL示例:
全局状态:show global status like '%Threads_connected%'; 和 show global status like '%Questions%'; 查看当前连接和QPS。
进程列表:show full processlist; 观察大量处于 Sending data、Locked、Waiting for... 的线程。
InnoDB 状态:SHOW ENGINE INNODB STATUS\G 查看死锁、锁等待、事务历史、IO 吞吐等。
三、问题排查
按照慢查询 -锁冲突 - 连接管理 - 配置不当的顺序排查能解决90%的问题。
1. 慢查询:
开启慢查询日志:
sql
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 0.5; -- 根据业务调整
SET GLOBAL log_queries_not_using_indexes = ON;
分析工具:用 pt-query-digest(Percona Toolkit)或 mysqldumpslow 统计 Top N 最慢 SQL 及执行频率。
定位单条SQL:拿到SQL后,用EXPLAIN或EXPLAIN ANALYZE(8.0.18+) 分析执行计划。
观察:type(ALL 全表扫描)、rows(预估扫描行数极大)、Extra(Using filesort, Using temporary)、是不是使用索引、过滤比。
优化:加复合索引、避免函数操作索引列、减少SELECT * 回表、改写子查询为 JOIN、控制分页深翻。
2. 锁冲突和事务阻塞
行锁/表锁等待:
MySQL 5.7+ 可用 select * from sys.innodb_lock_waits; 查看锁等待关系,谁阻塞了谁。
或手动查:show engine innodb status 的 LATEST DETECTED DEADLOCK 和 TRANSACTIONS 段落。
现象:大量线程处于 updating 或 insert 时卡死,TPS 归零。
元数据锁 (MDL):DDL 操作或未提交事务导致 Waiting for table metadata lock。
死锁:日志会记录,需要检查业务事务顺序是不是一致,是不是使用了 SELECT ... FOR UPDATE 后锁范围过大。
优化方向:
缩短事务:拆大事务为小事务,不在事务中做 RPC 调用、文件 I/O。
调整隔离级别:读多写少可考虑 RC(读已提交)替代 RR,降低间隙锁。
冷热数据分离,避免单行热点更新(库存扣减改用队列串行或 INSERT ... ON DUPLICATE KEY UPDATE 配合缓存)。
3. 连接和线程管理
连接数上限被打满:
show variables like 'max_connections'; 和 show status like 'Threads_connected'; 对比,如果已满,应用报“too many connections”。
原因:连接池配置过大、连接未释放、大量空闲连接。
连接创建开销:高并发短连接频繁 TCP 握手和线程创建,启用连接池并且设置合理的 wait_timeout 回收空闲连接。
线程缓存:Threads_created 增长过快,需加大 thread_cache_size。
4. 配置和缓存命中
InnoDB 缓冲池:
show status like 'Innodb_buffer_pool_read_requests'; 和 Innodb_buffer_pool_reads(物理读),命中率应 > 99%。低了说明内存不够,要扩大 innodb_buffer_pool_size 或优化 SQL。
查询缓存(MySQL 5.7 以前):主流高并发场景下建议禁用(query_cache_size=0),因为其全局锁会引发严重争用。
临时表:如果磁盘临时表比 Created_tmp_disk_tables 增长快,需增大 tmp_table_size 或优化索引避免创建临时表。
双1配置和组提交:
在数据安全性允许时,调整 sync_binlog 和 innodb_flush_log_at_trx_commit 可大幅提升写TPS,但可能丢失部分事务。理解风险后再操作。
四、不同数据库的排查工具
MySQL:充分利用 performance_schema 和 sys schema。sys.statement_analysis 可做全量 SQL 统计,sys.io_global_by_file_by_bytes 能查看磁盘热点文件。
PostgreSQL:pg_stat_statements 扩展用于定位最耗时的查询和读写块命中率;pg_stat_activity 配合 pg_locks 可查看当前活动连接、等待事件(wait_event_type)以及锁持有和等待状态;auto_explain 模块能自动记录超过阈值并包含执行计划的慢查询。
Oracle:AWR/ASH 报告、v$session_wait 是历史和实时性能分析、等待事件定位的重要手段。
MongoDB:currentOp() 和 mongostat 可分别查看当前操作、锁等待以及实时读写吞吐量。
五、排查实战
当接到告警,可以按以下步骤执行:
看监控大盘:CPU/IO/Memory 谁高?高并发下 CPU 高加服务器,IO 高则优化 SQL 或扩容磁盘。
抓当前活动查询:show full processlist 或 pg_stat_activity,找出执行时间 > 5 秒的 SQL,同时观察状态栏(Lock, Sorting, Sending data)。
看锁等待:用工具(sys 库或 pg_locks)查出阻塞者,立刻考虑是不是可kill。
拉慢查询日志:分析最近一段时间的聚合报告,不要只看单条,要看执行频率高但稍慢的积少成多型 SQL。
检查事务:找到长时间未提交的 BEGIN,可能造成undo堆积和锁持有。
检查计划变更:突然卡顿,可能是统计信息过时导致执行计划变差。执行ANALYZE TABLE后看是不是好转。
配置和容量对比:确定连接数、缓冲池、临时表大小等重点配置是不是被意外修改,并和历史健康值对比。
六、非数据库的问题
云数据库主动隔离:云厂商可能对资源限制(如 IOPS、连接数、带宽),超限会被主动限速,表现为规律性性能毛刺。检查云监控中的配额和限流日志。
备份和定时任务:备份(mysqldump, xtrabackup)、统计任务(ANALYZE) 会占用 IO,如果和业务高峰重叠则卡顿。
读写分离延迟:主库写后立刻从库读,读到了旧数据并继续写入导致思路错乱,但表现可能是“锁超时”,需在代码层强制读主或加缓存补偿。
大事务和DDL:一个不经意的大事务(如 UPDATE ... WHERE 未走索引更新了千万行)或在线 DDL 会导致长锁,需审查所有变更。
CPU 高看 SQL 执行,IO 高看 SQL 扫描行数/日志刷盘,连接满看连接池和锁。
在重启/升级配置前,先收集 show engine innodb status 和 show full processlist 的快照,这是查找原因最重要的证据。