文章目录
- 🚀 深度优化:Java 慢查询排查与性能调优实战
- 🚨1. 事故全景:从告警到定位
- 🕵️♂️1.1 事故时间线
- 📊 1.2 关键指标异常
- 🛠️ 1.3 排查工具链
- 🔍 2. 深度剖析:MySQL 分页查询的致命陷阱
- 🧠 2.1 Offset 分页的执行原理
- 🕵️♂️ 2.2 索引失效的根本原因
- ⚙️ 2.3 深度优化方案对比
- 🧰 方案一:游标分页(推荐)
- 📦 方案二:覆盖索引优化
- 📊 方案对比表
- 🛠️ 3. 完整优化实战
- 💻 3.1 MyBatis 改造示例
- ⚙️ 3.2 服务层改造
- 🛡️ 4. 防御体系:慢查询防控全景方案
- 🔒 4.1 事前预防
- 📡 4.2 事中监控
- 📊 5. 优化效果验证
- 🧮 5.1 压测数据对比
- 💡 6. 工程师的自我修养
- 📝 6.1 SQL 编写军规
🚀 深度优化:Java 慢查询排查与性能调优实战
🚨1. 事故全景:从告警到定位
🕵️♂️1.1 事故时间线
timelinetitle 故障时间轴00:00 : 监控系统首次告警00:05 : 数据库连接池使用率突破90%00:08 : 网关开始出现503错误00:12 : 自动扩容触发00:15 : 人工介入排查
📊 1.2 关键指标异常
指标 | 正常值 | 故障值 | 超出阈值 |
---|---|---|---|
接口 | P99 | 响应时间 | 200ms |
数据库 | QPS | 800 | 3500 |
活跃连接数 | 20 | 200(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;
📊 方案对比表
方案 | 扫描行数 | 排序方式 | 适用场景 |
---|---|---|---|
原始Offset | 10020 | 文件排序 | 小数据量 |
游标分页 | 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使用率 |
---|---|---|---|---|
优化前 | 85 | 6100ms | 32% | 96% |
优化后 | 2150 | 230ms | 0% | 45% |
💡 6. 工程师的自我修养
📝 6.1 SQL 编写军规
- 禁止 无限制的 SELECT *
- 必须 为分页查询添加 ORDER BY
- 推荐 使用游标替代 OFFSET
- 强制 为 WHERE 条件字段建立索引
💬 讨论话题:
你在实际工作中遇到过哪些"看似无害"却引发严重问题的SQL?
对于千万级数据的分页查询,你有什么更好的解决方案?
👇 欢迎在评论区分享你的实战经验!
📌 作者后记:
如果觉得这篇深度技术解析对你有帮助,请点赞 👍 收藏 ⭐️ 转发 🔄
你的支持是我持续创作优质内容的动力!
🔗相关推荐:
- MySQL索引原理深入解析
- 分布式系统分页查询设计模式
- Java性能调优实战手册