共计 2867 个字符,预计需要花费 8 分钟才能阅读完成。
MySQL 技能进阶:如何解决高并发下的慢查询问题
慢查询的核心概念与影响
慢查询是指执行时间超过预设阈值的 SQL 语句,通常由 long_query_time 参数定义(默认 10 秒)。这类查询会带来三个直接危害:

- 资源占用:长期占据连接数,消耗 CPU/ 内存
- 连锁反应:引发连接池耗尽,正常请求被阻塞
- 用户体验:页面响应时间显著增加
通过监控发现,当 QPS 超过 2000 时,一个未优化的慢查询可能导致整个集群的吞吐量下降 40%。
高并发下的典型痛点
在电商秒杀场景中,我们遇到过如下典型问题:
- 热点商品查询:
SELECT * FROM products WHERE id=xxx在库存检查时出现 500ms 延迟 - 用户订单聚合:
SELECT COUNT(*) FROM orders WHERE user_id=?随着订单表膨胀到千万级,执行时间从 50ms 恶化到 8 秒 - 联合查询排序:
SELECT * FROM a JOIN b ON a.id=b.a_id ORDER BY create_time DESC LIMIT 100在 JOIN 大表时出现 filesort
解决方案技术对比
方案一:索引优化(见效最快)
-- 优化前(全表扫描)SELECT user_name FROM users WHERE register_date > '2023-01-01';
-- 优化后(添加 B + 树索引)ALTER TABLE users ADD INDEX idx_regdate(register_date);
EXPLAIN SELECT user_name FROM users WHERE register_date > '2023-01-01'; -- type: range
适用场景:等值查询、范围查询、排序操作
方案二:查询重写(性价比最高)
-- 优化前(使用 OR 导致索引失效)SELECT * FROM logs WHERE status=1 OR deleted=0;
-- 优化后(UNION 替代 OR)SELECT * FROM logs WHERE status=1
UNION
SELECT * FROM logs WHERE deleted=0;
方案三:缓存策略(适合读多写少)
# Python 伪代码示例
def get_product(id):
cache_key = f"product:{id}"
data = redis.get(cache_key)
if not data:
data = db.execute("SELECT * FROM products WHERE id=?", id)
redis.setex(cache_key, 3600, data) # 缓存 1 小时
return data
实战优化案例
案例 1:分页查询优化
-- 原始慢查询(3.2 秒)SELECT * FROM articles ORDER BY view_count DESC LIMIT 100000, 20;
-- 优化方案(0.15 秒)SELECT a.* FROM articles a
JOIN (
SELECT id FROM articles
ORDER BY view_count DESC
LIMIT 100000, 20
) tmp ON a.id = tmp.id;
原理:先通过覆盖索引获取主键,再回表查询完整数据
案例 2:统计查询优化
-- 原始查询(全表扫描 12 秒)SELECT COUNT(*) FROM user_actions WHERE action_type='click';
-- 优化方案(0.8 秒)ALTER TABLE user_actions ADD INDEX idx_type(action_type);
SELECT COUNT(id) FROM user_actions WHERE action_type='click';
案例 3:JOIN 优化
-- 原始查询(5.7 秒)SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id=o.user_id
WHERE u.vip_level > 3;
-- 优化方案(0.3 秒)SELECT u.name, o.amount
FROM orders o
JOIN (SELECT id,name FROM users WHERE vip_level>3) u
ON o.user_id=u.id;
EXPLAIN 执行计划精读
关键指标解读:
- type 列:从快到慢 system > const > eq_ref > ref > range > index > ALL
- Extra 列:
Using filesort:需要额外排序Using temporary:使用临时表Using index:覆盖索引
示例分析:
EXPLAIN SELECT * FROM products WHERE category_id=5;
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | products | ALL | NULL | NULL | NULL | NULL | 9832 | Using where |
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
诊断:该查询正在进行全表扫描(type=ALL),建议为 category_id 添加索引
生产环境最佳实践
- 索引设计原则:
- 遵循最左前缀原则
- 区分度高的列在前
-
避免超过 5 个索引列
-
避坑指南:
- 不在索引列上使用函数:
WHERE YEAR(create_time)=2023 - 避免隐式类型转换:
WHERE user_id='123'(user_id 是 int) -
控制 IN 列表长度:
WHERE id IN(1,2,...,1000) -
性能测试方法:
-- 使用 SQL_NO_CACHE 排除缓存影响 SELECT SQL_NO_CACHE COUNT(*) FROM large_table; -- 使用 BENCHMARK 函数 SELECT BENCHMARK(1000000, (SELECT COUNT(*) FROM users));
总结与行动建议
建议立即执行三个动作:
-
开启慢查询日志:
# my.cnf 配置 slow_query_log = 1 slow_query_log_file = /var/log/mysql/mysql-slow.log long_query_time = 1 -
使用 pt-query-digest 分析日志:
pt-query-digest /var/log/mysql/mysql-slow.log -
对 TOP10 慢查询制定优化方案
优化永无止境,建议每季度进行一次系统性 SQL 审计。你在实际工作中遇到过哪些印象深刻的慢查询案例?欢迎分享你的优化经验。
正文完
