找回密码
 立即注册
首页 业界区 业界 如何医治一条慢SQL?

如何医治一条慢SQL?

巨到 2025-6-2 23:35:49
前言

"苏工,订单列表又崩了!"
接到电话时,我对着监控大屏上999ms的SQL响应时间哭笑不得。
几年来,我发现一个定律:所有SQL问题都是在凌晨三点爆发!
今天抽丝剥茧,教你用架构师的思维给慢SQL开刀手术。
希望对你会有所帮助。
1 术前检查:找准病灶

1.1 EXPLAIN 查看执行计划

使用EXPLAIN查看SQL语句的执行计划,相当于给SQL拍了张X光。
下面是一个典型的SQL问题,它是某电商平台历史订单查询的SQL语句:
  1. SELECT *
  2. FROM orders o
  3. LEFT JOIN users u ON o.user_id = u.id
  4. LEFT JOIN products p ON o.product_id = p.id
  5. WHERE o.create_time > '2023-01-01'
  6.   AND u.vip_level > 3
  7.   AND p.category_id IN (5,8)
  8. ORDER BY o.amount DESC
  9. LIMIT 1000,20;
复制代码
使用EXPLAIN关键字查看执行计划的结果如下:
  1. +----+-------------+-------+------+---------------+------+---------+------+---------+---------------------------------+
  2. | id | select_type | table | type | possible_keys | key  | rows    | Extra| key_len |
  3. +----+-------------+-------+------+---------------+------+---------+------+---------+---------------------------------+
  4. | 1  | SIMPLE      | o     | ALL  | idx_user_time | NULL | 1987400 | Using where; Using filesort     |  
  5. | 1  | SIMPLE      | u     | ALL  | PRIMARY       | NULL | 100000  | Using where                     |
  6. | 1  | SIMPLE      | p     | ALL  | PRIMARY       | NULL | 50000   | Using where                     |
  7. +----+-------------+-------+------+---------------+------+---------+------+---------+---------------------------------+
复制代码
诊断报告

  • 全表扫描三连击(type=ALL)
  • filesort暴力排序(内存警告)
  • 索引全军覆没
2 手术方案:精准打击

2.1 单表代谢手术

如果通过执行计划查到是索引有问题,我们就需要单独优化索引。
病根:JSON字段索引失效
错误用法:
  1. ALTER TABLE users ADD INDEX idx_extend ((extend_info->'$.is_vip'));
复制代码
extend_info字段是JSON类型的字段,即使创建了索引,索引也会丢失。
正解姿势(MySQL 8.0+):
  1. ALTER TABLE users ADD INDEX idx_vip_level (vip_level);
  2. ALTER TABLE orders ADD INDEX idx_create_user (create_time, user_id) COMMENT '组合索引覆盖查询';
复制代码
创建组合索引覆盖查询。
2.2 血管疏通术

卡点分析
原始join顺序是:
  1. orders → users → products
复制代码
优化后的方案:
  1. (子查询过滤users) → products → orders
复制代码
调整执行顺序,用小表驱动大表。
重写后的SQL:
  1. SELECT o.*
  2. FROM products p
  3. INNER JOIN (
  4.   SELECT o.id, o.amount, o.create_time
  5.   FROM orders o
  6.   WHERE o.create_time > '2023-01-01'
  7. ) o ON p.id = o.product_id
  8. INNER JOIN (
  9.   SELECT id
  10.   FROM users
  11.   WHERE vip_level > 3
  12. ) u ON o.user_id = u.id  
  13. WHERE p.category_id IN (5,8)
  14. ORDER BY o.amount DESC
  15. LIMIT 1000,20;
复制代码
术后效果

  • 先扫小表(users过滤后只有100条)
  • 消除冗余字段传输
  • 减少Join时临时表生成
2.3 开颅手术

通过执行计划锁定了问题,走错索引了,该怎么处理呢?
可以通过FORCE INDEX强制指定索引:
  1. SELECT /*+ INDEX(o idx_create_user) */
  2.        o.id, o.amount
  3. FROM orders o FORCE INDEX (idx_create_user)
  4. WHERE o.create_time > '2023-01-01';
复制代码
使用衍生表加速:
  1. SELECT *
  2. FROM (
  3.   SELECT id, amount
  4.   FROM orders
  5.   WHERE create_time > '2023-01-01'
  6.   ORDER BY amount DESC
  7.   LIMIT 1020
  8. ) tmp
  9. ORDER BY amount DESC
  10. LIMIT 1000,20;
复制代码
医嘱

  • 警惕OR导致的索引失效
  • 用覆盖索引避免回表查询
  • CTE表达式谨慎使用
2.4 生命体征监测

查看索引使用:
  1. SHOW INDEX FROM orders;
复制代码
监控索引使用率:
  1. SELECT object_schema, object_name, index_name,
  2.        count_read, count_fetch
  3. FROM performance_schema.table_io_waits_summary_by_index_usage
  4. WHERE index_name IS NOT NULL;
复制代码
3 术后护理:体系化治理

3.1 SQL消毒中心

需要制定优秀的代码规范,否则可能会出现全表扫描的问题。
在日常工作中,我们要尽可能减少Java代码感染源
MyBatis危险写法:
  1. @Select("SELECT * FROM orders WHERE #{condition}")
  2. List<Order> findByCondition(@Param("condition") String condition);
复制代码
condition参数可以传入任何内容,如何传入了1=1,可能会导致查询所有的数据,走全表扫描,让查询效率变得非常低。
正确做法(参数化查询):
  1. @Select("SELECT * FROM orders WHERE create_time > #{time}")
  2. List<Order> findByTime(@Param("time") Date time);
复制代码
消毒方案

  • SQL审核平台接入(如Yearning)
  • MyBatis拦截器拦截全表更新
  • 自动化EXPLAIN分析流水线
3.2 查杀大表癌症

如果遇到大表的癌症病例,可以用分库分表的方案解决。
病历案例:3亿订单表终极解决方案
  1. // Sharding-JDBC分片配置
  2. spring.shardingsphere.rules.sharding.tables.orders.actual-data-nodes=ds$0..1.orders_$->{2020..2023}
  3. spring.shardingsphere.rules.sharding.tables.orders.table-strategy.standard.sharding-column=create_time
  4. spring.shardingsphere.rules.sharding.tables.orders.table-strategy.standard.sharding-algorithm-name=time_range
复制代码
化疗方案

  • 时间维度分片(2020~2023年度表)
  • 用户ID取模分库
  • 冷热分离(OSS归档历史数据)
医嘱总结

优化三板斧

  • 定位:慢查询日志+执行计划分析
  • 切割:化繁为简拆分多步执行
  • 重建:符合业务场景的数据结构
避坑口诀

  • 索引不是银弹,覆盖才是王道
  • Join水深,能拆就拆
  • Order By+Limit≠分页优化
最后送上苏三的传秘方:当你优化SQL到怀疑人生时,不妨试试这三味药:

  • 删业务逻辑
  • 加缓存
  • 换数据库
保证药到病除(老板打不打死你我就不管了,哈哈哈)!
最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。
本文收录于我的技术网站:http://www.susan.net.cn

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册