1 术前检查:找准病灶
1.1 EXPLAIN 查看执行计划
使用 EXPLAIN 查看 SQL 语句的执行计划,相当于给 SQL 拍了张 X 光。
下面是一个典型的 SQL 问题,它是某电商平台历史订单查询的 SQL 语句:
SELECT *
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
LEFT JOIN products p ON o.product_id = p.id
WHERE o.create_time > '2023-01-01'
AND u.vip_level > 3
AND p.category_id IN (5,8)
ORDER BY o.amount DESC
LIMIT 1000,20;
复制代码
使用 EXPLAIN 关键字查看执行计划的结果如下:
+----+-------------+-------+------+---------------+------+---------+------+---------+---------------------------------+
| id | select_type | table | type | possible_keys | key | rows | Extra| key_len |
+----+-------------+-------+------+---------------+------+---------+------+---------+---------------------------------+
| 1 | SIMPLE | o | ALL | idx_user_time | NULL | 1987400 | Using where; Using filesort |
| 1 | SIMPLE | u | ALL | PRIMARY | NULL | 100000 | Using where |
| 1 | SIMPLE | p | ALL | PRIMARY | NULL | 50000 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+---------+---------------------------------+
复制代码
诊断报告:
全表扫描三连击(type=ALL)
filesort 暴力排序(内存警告)
索引全军覆没
2 手术方案:精准打击
2.1 单表代谢手术
如果通过执行计划查到是索引有问题,我们就需要单独优化索引。
病根:JSON 字段索引失效
错误用法:
ALTER TABLE users ADD INDEX idx_extend ((extend_info->'$.is_vip'));
复制代码
extend_info 字段是 JSON 类型的字段,即使创建了索引,索引也会丢失。
正解姿势(MySQL 8.0+):
ALTER TABLE users ADD INDEX idx_vip_level (vip_level);
ALTER TABLE orders ADD INDEX idx_create_user (create_time, user_id) COMMENT '组合索引覆盖查询';
复制代码
创建组合索引覆盖查询。
2.2 血管疏通术
卡点分析:
原始 join 顺序是:
orders → users → products
复制代码
优化后的方案:
(子查询过滤users) → products → orders
复制代码
调整执行顺序,用小表驱动大表。
重写后的 SQL:
SELECT o.*
FROM products p
INNER JOIN (
SELECT o.id, o.amount, o.create_time
FROM orders o
WHERE o.create_time > '2023-01-01'
) o ON p.id = o.product_id
INNER JOIN (
SELECT id
FROM users
WHERE vip_level > 3
) u ON o.user_id = u.id
WHERE p.category_id IN (5,8)
ORDER BY o.amount DESC
LIMIT 1000,20;
复制代码
术后效果:
先扫小表(users 过滤后只有 100 条)
消除冗余字段传输
减少 Join 时临时表生成
2.3 开颅手术
通过执行计划锁定了问题,走错索引了,该怎么处理呢?
可以通过 FORCE INDEX 强制指定索引:
SELECT /*+ INDEX(o idx_create_user) */
o.id, o.amount
FROM orders o FORCE INDEX (idx_create_user)
WHERE o.create_time > '2023-01-01';
复制代码
使用衍生表加速:
SELECT *
FROM (
SELECT id, amount
FROM orders
WHERE create_time > '2023-01-01'
ORDER BY amount DESC
LIMIT 1020
) tmp
ORDER BY amount DESC
LIMIT 1000,20;
复制代码
医嘱:
警惕 OR 导致的索引失效
用覆盖索引避免回表查询
CTE 表达式谨慎使用
2.4 生命体征监测
查看索引使用:
监控索引使用率:
SELECT object_schema, object_name, index_name,
count_read, count_fetch
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL;
复制代码
3 术后护理:体系化治理
3.1 SQL 消毒中心
需要制定优秀的代码规范,否则可能会出现全表扫描的问题。
在日常工作中,我们要尽可能减少 Java 代码感染源。
MyBatis 危险写法:
@Select("SELECT * FROM orders WHERE #{condition}")
List<Order> findByCondition(@Param("condition") String condition);
复制代码
condition 参数可以传入任何内容,如何传入了 1=1,可能会导致查询所有的数据,走全表扫描,让查询效率变得非常低。
正确做法(参数化查询):
@Select("SELECT * FROM orders WHERE create_time > #{time}")
List<Order> findByTime(@Param("time") Date time);
复制代码
消毒方案:
SQL 审核平台接入(如 Yearning)
MyBatis 拦截器拦截全表更新
自动化 EXPLAIN 分析流水线
3.2 查杀大表癌症
如果遇到大表的癌症病例,可以用分库分表的方案解决。
病历案例:3 亿订单表终极解决方案
// Sharding-JDBC分片配置
spring.shardingsphere.rules.sharding.tables.orders.actual-data-nodes=ds$0..1.orders_$->{2020..2023}
spring.shardingsphere.rules.sharding.tables.orders.table-strategy.standard.sharding-column=create_time
spring.shardingsphere.rules.sharding.tables.orders.table-strategy.standard.sharding-algorithm-name=time_range
复制代码
化疗方案:
时间维度分片(2020~2023 年度表)
用户 ID 取模分库
冷热分离(OSS 归档历史数据)
医嘱总结
优化三板斧:
定位:慢查询日志+执行计划分析
切割:化繁为简拆分多步执行
重建:符合业务场景的数据结构
避坑口诀:
索引不是银弹,覆盖才是王道
Join 水深,能拆就拆
Order By+Limit≠分页优化
最后送上传秘方:当你优化 SQL 到怀疑人生时,不妨试试这三味药:
删业务逻辑
加缓存
换数据库
保证药到病除(老板打不打死你我就不管了,哈哈哈)!
文章转载自:苏三说技术
原文链接:https://www.cnblogs.com/12lisu/p/18873868
体验地址:http://www.jnpfsoft.com/?from=001YH
评论