监控Mysql
📌 Mysql监控配置
改配置文件后重启服务: systemctl restart mysqld
log_output = table # 将慢查询日志保存到表中
slow_query_log = 1 # 开启慢查询
long_query_time = 1 # 慢查询超时时间为1秒
max_connections = 512 # 最大连接数,要求适中
innodb_print_all_deadlocks = ON # 开启死锁日志记录
临时修改环境变量
在mysql里执行: set global max_connections=1000;
🚁 相关变量查询语句
# 查看数据库中与慢查询相关的变量
show variables like '%slow_query%';
# 长查询的执行时间阈值,超过该时间的查询被记录为慢查询
show variables like '%long_query%';
# 查询数据库中的最大连接数
show variables like '%max_connections%';
# 查询数据库中的连接数历史峰值
show variables like '%max_used_connections%';
📌 需要关注的指标
Current QPS - 每秒处理查询数
🚁 Connections
连接数;常见的连接池被打满的原因(按发生概率排列):
-
代码层的连接泄漏: 因代码缺陷或异常处理不当,未能正确将连接归还给连接池。如
finally中无close(),异常导致事务未回滚且连接未释放;或流式处理未及时关闭。特征:系统刚重启时正常,随着运行时间推移,连接数呈线性上升,最终在某次业务高峰瞬间被打满。
-
DB瓶颈: 慢SQL与长事务,持续占用连接。
- 僵尸连接: 未开启
keepalive或检测间隔太长,遇到网络抖动或数据库重启时,连接池未能及时感知,导致僵尸连接占用名额。 - 配置不合理: 最大连接数太小。
-
瞬时流量: 极少数情况,在没有代码缺陷、SQL性能优良、网络正常的前提下,纯粹的流量暴增(如秒杀瞬间)导致并发请求数超过连接池容量。
特征:流量回落后连接数迅速下降,且报错集中在峰值的那几秒钟,日常运行完全正常。
结合现象分析
| 现象特征 (池满) | 核心原因 | 排查命令与工具 | 分析技巧与特别关注 |
|---|---|---|---|
| 高CPU + 低内存 | 计算密集型:复杂算法、死循环、频繁GC(GC线程也会消耗CPU) | 1. thread -n 3:找出CPU占用最高的前N个线程。2. trace:追踪热点方法调用栈,定位耗时方法。3. profiler:生成CPU火焰图,看哪些方法占据了“平顶”。 |
特别关注:thread -n显示的是CPU时间占比,要结合nid(十六进制线程ID)与系统top -H -p命令对应。如果是GC线程高频,应用jvm命令查看GC详情。 |
| 高CPU + 高内存 | STW(Stop-The-World) 或 内存泄漏:频繁Full GC导致业务线程暂停,CPU耗在GC上;或内存泄漏加剧GC压力 | 1. jvm:查看GC频率和耗时,确认是否频繁Full GC。2. profiler:生成CPU火焰图,看“平顶”是否在GC相关方法(如G1YoungGen)。3. heapdump:导出堆转储,用MAT分析泄漏对象。 |
特别关注:若CPU火焰图显示GC占用大量CPU,先解决内存泄漏问题;否则CPU和内存会互相恶化。 |
| 低CPU + 低内存 | I/O或锁阻塞:线程等待数据库、RPC、文件IO、Redis等外部资源,或等待锁释放 | 1. thread -b:检测死锁和锁阻塞。2. trace:追踪方法调用,查看耗时是否卡在外部调用上。3. logger / ognl:动态调整日志级别,验证是否为同步日志输出导致的IO阻塞。 |
特别关注:查看dashboard中WAITING/TIMED_WAITING状态的线程数。可以使用thread -n -1查看所有线程状态分布,快速定位大量阻塞线程。 |
| 低CPU + 高内存 | 死锁/阻塞导致的内存泄漏:线程因死锁或等待无限资源而停滞,未完成任务在队列中积压,任务对象占用内存 | 1. thread -b:首选命令,检测死锁或找出阻塞其他线程的锁持有者。2. jvm:查看GC情况,确认内存增长是否因对象无法回收。3. heapdump:导出堆转储,分析堆积的对象类型(通常是Runnable或任务对象)。 |
特别关注:thread -b输出为空时,考虑排查GC线程导致的假性阻塞(即STW)。使用jvm命令查看GC耗时,若远高于阈值,则内存问题由GC引发,而非死锁。 |
🔧 补充:通用排查思路
无论哪种场景,建议遵循以下顺序:
dashboard -i 5000:全局观察线程、内存、GC的实时趋势。thread:根据CPU/状态筛选嫌疑线程。jvm:确认GC是否成为瓶颈。heapdump(内存高时)或trace/watch(CPU高或阻塞时):深入定位代码级问题。
🚁 Slow Queries
Y轴是慢查询数量,需要设置慢查询阈值(如1秒),默认10秒。
慢查询超时时间常见范围:
| 系统类型 | 推荐阈值 | 说明 |
|---|---|---|
| 高并发 OLTP(如电商、支付) | 0.1s ~ 0.3s | 核心交易接口要求毫秒级响应,超过200ms的SQL即需优化 |
| 一般业务系统(如后台管理) | 0.5s ~ 1s | 用户可接受秒内响应,1秒是常见警戒线 |
| 报表/分析类(OLAP) | 1s ~ 3s 或更高 | 复杂查询允许更长时间,但需结合业务容忍度 |
🔧 慢查询的分析方法
1.定位:取执行时间大于阈值的sql,找扫描行数多,或扫描行数远大于返回行数的sql
SELECT START_TIME, USER_HOST, QUERY_TIME, LOCK_TIME, DB, SQL_TEXT
FROM MYSQL.SLOW_LOG
ORDER BY START_TIME DESC LIMIT 10;
2.分析:explain sql看执行计划,关注字段
- type,all、index需要优化,最好是range或ref
- extra,出现文件排序或使用临时表需优化
- rows,预扫行数,越大说明索引过滤差
- filter,数值说低说明索引选取可能不对,回表代价大
注:适用于Mysql, Tidb,pg的执行计划无以上字段,而是以树形结构展示。
3.根因
- 索引失效,如模糊匹配、函数运算
- 统计信息失效
- 深度分页,如limit 100000, 10(跳过前10万行取10条记录,应改成where条件过滤
where id > {id} order by id limit 10)
回表
首先,数据挂在聚簇索引的叶子节点
- 未覆盖索引时,根据查询条件拿到id,再回到聚簇索引随机io(这里就是回表),以id匹配数据行。
- 覆盖索引时,理想情况在叶子节点拿到匹配记录,无回表操作。
4.如何优化:
- 添加合适的索引,避免
SELECT * - 拆分复杂查询,持续监控并优化慢SQL
- 定期执行
ANALYZE TABLE,更新统计信息帮助优化器选择更优执行计划
🚁 Table Locks
表级锁,控制并发访问的机制,防止同时对一个表进行读写。
包含以下指标:
-
Table_locks_immediate
表示一个线程请求表锁时,立即获得锁的次数。即:无需等待,直接加锁成功。 -
Table_locks_waited
表示一个线程请求表锁时,需要等待其他线程释放锁的次数。即:发生锁竞争,出现阻塞。
理想状态下,Table_locks_immediate 应远大于 Table_locks_waited。
若后者持续增长,说明存在较多锁竞争,可能影响并发性能。
优化建议
- 优先使用
InnoDB存储引擎: 支持行级锁,大幅减少锁冲突。 - 优化慢查询、添加合适索引。
- 尽量避免长事务,及时提交事务以释放锁资源。
- 设置报警规则,当
Table_locks_waited突增时及时通知。
🔧 锁竞争
激烈锁竞争会导致线程阻塞,间接导致线程池资源耗尽,表现为系统响应慢。
可能造成锁竞争的原因:
- 代码层面: 锁粒度太大,如为保护某个共享变量而锁住大方法或类
- 数据库层面: 写语句缺索引,导致锁升级为大量行锁甚至全表锁
- 大事务: 大量sql+非db操作,其中某个环节慢导致锁升级
- 热点行竞争: 如秒杀场景多个订单并发操作同一商品
如何优化锁竞争
锁竞争的本质: 执行慢 + 热点竞争,因此解决思路包括减少冲突(拆分热点、异步化)、加快冲突解决(索引优化、缩小事务、缩短锁持续时间)
针对代码
- 缩小锁粒度,只锁需要保护的最小代码块
- 使用无锁、原子性质的数据结构如
AtomicInteger - 避免锁内IO,减少锁占用时间
- 固定锁获取顺序,防止死锁
针对DB
- 确保索引有效
- 拆分大事务,尽快提交以释放锁
- 优化热点行:使用异步扣减或队列(如redis lua脚本预扣减,再异步同步至db)
- 使用乐观锁(版本号机制)代替悲观锁(for update),失败则重试
🔧 死锁
死锁,同样分为代码/DB层面,具体原因如事务A锁表1等表2,事务B锁表2等表1,互相等对方释放锁。
发生死锁时,内存不释放,大量死锁线程积压后,会耗尽内存触发OOM。
大量死锁时的表现
- 指标: CPU使用率不高,内存占用持续增长。
- 业务: 事务无法提交或回滚,破坏数据一致性,服务无响应。