欢迎来到徐庆高(Tea)的个人博客网站
磨难很爱我,一度将我连根拔起。从惊慌失措到心力交瘁,我孤身一人,但并不孤独无依。依赖那些依赖我的人,信任那些信任我的人,帮助那些给予我帮助的人。如果我愿意,可以分裂成无数面镜子,让他们看见我,就像看见自己。察言观色和模仿学习是我的领域。像每个深受创伤的人那样,最终,我学会了随遇而安。
当前位置: 日志文章 > 详细内容

Java慢查询排查与性能调优完整实战指南

2025年08月09日 Java
1. 事故全景:从告警到定位1.1 事故时间线timeline title 故障时间轴 00:00 : 监控系统首次告警 00:05 : 数据库连接池使用率突破90% 00:0

1. 事故全景:从告警到定位

1.1 事故时间线

timeline
    title 故障时间轴
    00:00 : 监控系统首次告警
    00:05 : 数据库连接池使用率突破90%
    00:08 : 网关开始出现503错误
    00:12 : 自动扩容触发
    00:15 : 人工介入排查

1.2 关键指标异常

指标正常值故障值超出阈值
接口p99响应时间200ms
数据库qps8003500
活跃连接数20200(max)10x

1.3 排查工具链

// 监控工具清单
public class troubleshootingtools {
    string[] tools = {
        "skywalking 8.7.0", 
        "arthas 3.6.7",
        "prometheus + grafana",
        "mysql slow query log"
    };
}

2. 深度剖析:mysql 分页查询的致命陷阱

2.1 offset 分页的执行原理

​​性能消耗公式​​

总成本 = 全表扫描成本 + 排序成本 + 跳过行成本

2.2 索引失效的根本原因

-- 问题sql示例
explain select * from member_info 
where status = 1 
order by create_time desc 
limit 10000, 20;

​​执行计划关键解读​​:

  • type: all:全表扫描
  • rows: 1250000:扫描行数
  • extra: using filesort:无法利用索引排序

2.3 深度优化方案对比

方案一:游标分页(推荐)

-- 优化后sql(基于id分页)
select * from member_info 
where status = 1 and id > #{lastid}
order by id asc 
limit 20;

方案二:覆盖索引优化

-- 新增复合索引
alter table member_info 
add index idx_status_createtime (status, create_time);

-- 改写sql
select * from member_info 
where status = 1 
order by create_time desc 
limit 20;

方案对比表

方案扫描行数排序方式适用场景
原始offset10020文件排序小数据量
游标分页20索引排序大数据量、深度分页
覆盖索引20索引覆盖中等数据量

3. 完整优化实战

3.1 mybatis 改造示例

public interface membermapper {
    // 旧方法(问题代码)
    @select("select * from member_info where status = #{status} limit #{offset}, #{limit}")
    list<member> listbypage(@param("status") int status, 
                          @param("offset") int offset,
                          @param("limit") int limit);

    // 新方法(优化后)
    @select("select * from member_info where status = #{status} and id > #{lastid} order by id asc limit #{limit}")
    list<member> listbycursor(@param("status") int status,
                            @param("lastid") long lastid,
                            @param("limit") int limit);
}

3.2 服务层改造

public pageresult<member> getmemberlist(int pagesize, long lastid) {
    // 游标分页查询
    list<member> members = membermapper.listbycursor(1, lastid, pagesize);
    
    // 获取下一页的游标
    long nextlastid = members.isempty() ? null : members.get(members.size()-1).getid();
    
    return new pageresult<>(members, nextlastid);
}

4. 防御体系:慢查询防控全景方案

4.1 事前预防

4.2 事中监控

# my.cnf 慢查询配置
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1
log_queries_not_using_indexes = 1

5. 优化效果验证

5.1 压测数据对比

场景tps平均响应时间错误率cpu使用率
优化前856100ms32%96%
优化后2150230ms0%45%

6. 工程师的自我修养

6.1 sql 编写军规

  1. ​​禁止​​ 无限制的 select * ​​
  2. 必须​​ 为分页查询添加 order by ​​
  3. 推荐​​ 使用游标替代 offset
  4. ​​强制​​ 为 where 条件字段建立索引

总结

到此这篇关于java慢查询排查与性能调优的文章就介绍到这了,更多相关java慢查询排查与性能调优内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!