MySQL技能进阶:如何解决高并发下的慢查询问题

3次阅读
没有评论

共计 2867 个字符,预计需要花费 8 分钟才能阅读完成。

image.webp

MySQL 技能进阶:如何解决高并发下的慢查询问题

慢查询的核心概念与影响

慢查询是指执行时间超过预设阈值的 SQL 语句,通常由 long_query_time 参数定义(默认 10 秒)。这类查询会带来三个直接危害:

MySQL 技能进阶:如何解决高并发下的慢查询问题

  • 资源占用:长期占据连接数,消耗 CPU/ 内存
  • 连锁反应:引发连接池耗尽,正常请求被阻塞
  • 用户体验:页面响应时间显著增加

通过监控发现,当 QPS 超过 2000 时,一个未优化的慢查询可能导致整个集群的吞吐量下降 40%。

高并发下的典型痛点

在电商秒杀场景中,我们遇到过如下典型问题:

  1. 热点商品查询SELECT * FROM products WHERE id=xxx 在库存检查时出现 500ms 延迟
  2. 用户订单聚合SELECT COUNT(*) FROM orders WHERE user_id=? 随着订单表膨胀到千万级,执行时间从 50ms 恶化到 8 秒
  3. 联合查询排序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 添加索引

生产环境最佳实践

  1. 索引设计原则
  2. 遵循最左前缀原则
  3. 区分度高的列在前
  4. 避免超过 5 个索引列

  5. 避坑指南

  6. 不在索引列上使用函数:WHERE YEAR(create_time)=2023
  7. 避免隐式类型转换:WHERE user_id='123'(user_id 是 int)
  8. 控制 IN 列表长度:WHERE id IN(1,2,...,1000)

  9. 性能测试方法

    -- 使用 SQL_NO_CACHE 排除缓存影响
    SELECT SQL_NO_CACHE COUNT(*) FROM large_table;
    
    -- 使用 BENCHMARK 函数
    SELECT BENCHMARK(1000000, (SELECT COUNT(*) FROM users));

总结与行动建议

建议立即执行三个动作:

  1. 开启慢查询日志:

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

  2. 使用 pt-query-digest 分析日志:

    pt-query-digest /var/log/mysql/mysql-slow.log

  3. 对 TOP10 慢查询制定优化方案

优化永无止境,建议每季度进行一次系统性 SQL 审计。你在实际工作中遇到过哪些印象深刻的慢查询案例?欢迎分享你的优化经验。

正文完
 0
评论(没有评论)