MySQL慢查询优化实战:从日志分析到SQL重构全流程

⚡ MySQL慢查询优化实战:从日志分析到SQL重构全流程

一篇带你从发现慢查询 → 分析 → 优化 → 测试 的完整实战教程

🧩 关键词:MySQL优化 / 性能分析 / 慢查询 / 索引设计


🔍 一、前言

在大型业务系统中,MySQL 慢查询会导致:

  • 系统响应变慢
  • 页面加载延迟
  • 高并发下锁等待严重

解决方案就是 找到慢查询 → 分析执行计划 → 优化 SQL → 设计合理索引

本文通过一个 电商订单查询系统 的实例,带你全流程实战演示。


🏗 二、业务场景

假设我们有一个电商系统 orders 表:

sql 复制代码
CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    status VARCHAR(20),
    total_amount DECIMAL(10,2),
    created_at DATETIME,
    INDEX idx_user_created (user_id, created_at)
);

业务需求:

  • 查询某用户在某时间段的订单
  • 查询某状态订单总额
  • 高峰期每天有百万级订单

🧾 三、开启慢查询日志

修改 MySQL 配置文件 my.cnf

ini 复制代码
[mysqld]
slow_query_log = ON
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1  # 秒
log_queries_not_using_indexes = 1

重启 MySQL:

bash 复制代码
sudo systemctl restart mysql

查询慢查询日志示例:

复制代码
# Time: 2025-11-05T15:00:01.000000Z
# User@Host: root[root] @ localhost []
# Query_time: 3.542 Lock_time: 0.000 Rows_sent: 500 Rows_examined: 100000
SELECT * FROM orders WHERE user_id=12345 AND created_at BETWEEN '2025-11-01' AND '2025-11-05';

注意:Rows_examined=100,000 → 查询扫描了大量行,效率低


🧐 四、执行计划分析

使用 EXPLAIN 分析 SQL:

sql 复制代码
EXPLAIN SELECT * FROM orders
WHERE user_id=12345 AND created_at BETWEEN '2025-11-01' AND '2025-11-05';

输出示例:

id select_type table type possible_keys key rows Extra
1 SIMPLE orders ref idx_user_created idx_user_created 100000 Using where

分析:

  • 查询使用了 idx_user_created 索引,但扫描行数仍然多
  • 原因:索引顺序和条件不完全匹配

🛠 五、SQL重构与索引优化

1️⃣ 优化 SQL

原 SQL:

sql 复制代码
SELECT * FROM orders 
WHERE user_id=12345 AND created_at BETWEEN '2025-11-01' AND '2025-11-05';

优化 SQL:

sql 复制代码
SELECT id, product_id, total_amount 
FROM orders
WHERE user_id=12345 
  AND created_at >= '2025-11-01' AND created_at <= '2025-11-05';

✅ 优化点:

  • 只查询必要字段,避免 SELECT *
  • 避免不必要的数据扫描

2️⃣ 调整索引

当前索引:

sql 复制代码
INDEX idx_user_created (user_id, created_at)

优化方案:

  • 如果查询经常按 status 和时间筛选,可加复合索引:
sql 复制代码
ALTER TABLE orders 
ADD INDEX idx_user_status_created (user_id, status, created_at);
  • 这样查询 WHERE user_id=12345 AND status='PAID' AND created_at BETWEEN ... 可以直接走索引覆盖

3️⃣ 验证优化效果

执行优化后的 SQL:

sql 复制代码
EXPLAIN SELECT id, product_id, total_amount 
FROM orders
WHERE user_id=12345 AND status='PAID' AND created_at BETWEEN '2025-11-01' AND '2025-11-05';

输出示例:

id select_type table type possible_keys key rows Extra
1 SIMPLE orders ref idx_user_status_created idx_user_status_created 500 Using index

✅ Rows 从 100,000 降到 500,查询速度提升近 200 倍


🔁 六、慢查询优化全流程总结

  1. 发现慢查询

    • 慢查询日志 + long_query_time
    • pt-query-digest 分析大量慢查询
  2. 分析执行计划

    • EXPLAIN 查看扫描方式、索引使用情况
    • 关注 rowsExtra
  3. SQL优化

    • 避免 SELECT *
    • 使用必要字段
    • 合理拆分复杂查询
  4. 索引优化

    • 复合索引覆盖查询条件
    • 使用前缀索引或联合索引减少扫描量
    • 删除冗余索引
  5. 验证与回归测试

    • EXPLAIN + 实测响应时间
    • 高并发下压测

📊 七、日志分析工具推荐

  • mysqldumpslow
  • Percona Toolkit: pt-query-digest
  • Grafana + Prometheus + MySQL Exporter
  • 慢查询日志可视化面板,辅助优化


!实战范例: MySQL慢查询优化实战项目演示:百万订单表优化


1️⃣ 数据准备:生成百万级订单表

sql 复制代码
CREATE DATABASE IF NOT EXISTS shop;
USE shop;

DROP TABLE IF EXISTS orders;
CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    status VARCHAR(20),
    total_amount DECIMAL(10,2),
    created_at DATETIME,
    INDEX idx_user_created (user_id, created_at)
);

-- 插入测试数据(模拟100万条)
DELIMITER $$

CREATE PROCEDURE populate_orders()
BEGIN
    DECLARE i INT DEFAULT 1;
    WHILE i <= 1000000 DO
        INSERT INTO orders(user_id, product_id, status, total_amount, created_at)
        VALUES(
            FLOOR(1 + RAND() * 10000),    -- user_id
            FLOOR(1 + RAND() * 500),      -- product_id
            ELT(FLOOR(1 + RAND()*3), 'PAID','PENDING','CANCELLED'),
            ROUND(RAND()*1000, 2),
            NOW() - INTERVAL FLOOR(RAND()*30) DAY
        );
        SET i = i + 1;
    END WHILE;
END$$

DELIMITER ;

CALL populate_orders();

✅ 这样我们就有了百万级订单表,可真实模拟慢查询。


2️⃣ 原始慢查询测试

原 SQL 示例(经常使用场景):

sql 复制代码
SELECT * 
FROM orders 
WHERE user_id=1234 
  AND created_at BETWEEN '2025-11-01' AND '2025-11-05';

执行前加 EXPLAIN 分析:

sql 复制代码
EXPLAIN SELECT * 
FROM orders 
WHERE user_id=1234 
  AND created_at BETWEEN '2025-11-01' AND '2025-11-05';

输出示例:

id table type possible_keys key rows Extra
1 orders ref idx_user_created idx_user_created 100000 Using where

Rows=100,000 → 仍然扫描大量行,效率低


3️⃣ SQL重构与索引优化

优化 SQL

  • 只查询需要字段
  • 避免 SELECT *
sql 复制代码
SELECT id, product_id, total_amount 
FROM orders 
WHERE user_id=1234 
  AND created_at BETWEEN '2025-11-01' AND '2025-11-05';

新增复合索引

sql 复制代码
ALTER TABLE orders
ADD INDEX idx_user_created_status (user_id, status, created_at);

4️⃣ 优化后性能验证

sql 复制代码
EXPLAIN SELECT id, product_id, total_amount 
FROM orders 
WHERE user_id=1234 
  AND status='PAID'
  AND created_at BETWEEN '2025-11-01' AND '2025-11-05';
id table type key rows Extra
1 orders ref idx_user_created_status 500 Using index

✅ Rows 从 100,000 → 500,查询速度提升近 200倍


5️⃣ Node.js 测试脚本(可选)

用 Node.js 模拟实际请求,测试查询性能:

javascript 复制代码
const mysql = require('mysql2/promise');

(async function() {
    const conn = await mysql.createConnection({
        host: 'localhost',
        user: 'root',
        password: '123456',
        database: 'shop'
    });

    console.time('原SQL');
    await conn.query(`SELECT * FROM orders WHERE user_id=1234 AND created_at BETWEEN '2025-11-01' AND '2025-11-05'`);
    console.timeEnd('原SQL');

    console.time('优化SQL');
    await conn.query(`SELECT id, product_id, total_amount FROM orders WHERE user_id=1234 AND status='PAID' AND created_at BETWEEN '2025-11-01' AND '2025-11-05'`);
    console.timeEnd('优化SQL');

    await conn.end();
})();

运行结果示例:

复制代码
原SQL: 2450ms
优化SQL: 12ms

6️⃣ 总结

通过这个实战项目,我们完成了:

  1. 百万级订单表数据生成
  2. 原始 SQL 慢查询分析
  3. SQL 重构 + 索引优化
  4. 查询性能测试验证

✅ 实战结果:

  • 原 SQL:Rows 扫描 10万+,耗时 2-3秒
  • 优化 SQL:Rows 扫描 500,耗时 10-15ms
  • 高并发场景下性能显著提升

相关推荐
点心快奔跑4 小时前
超详细Windows系统MySQL 安装教程
数据库·windows·mysql
Neo Wordsworth5 小时前
phpstudy 无法启动mysql 但命令可以启动mysql
mysql·phpstudy
Cikiss5 小时前
图解 MySQL JOIN
数据库·后端·mysql
员大头硬花生5 小时前
六、InnoDB引擎-架构-结构
数据库·mysql·oracle
程序新视界6 小时前
在MySQL中,是否可以使用UUID作为主键?
数据库·后端·mysql
爱考证的小刘6 小时前
MySQL OCP认证和Oracle OCP认证分别是什么?
mysql·mysql数据库·数据库mysql·oracle数据库·oracle19c·oracle认证·mysql自学
挨踢攻城7 小时前
Oracle OCP认证:深度解析与实战指南
mysql·oracle·dba·开闭原则·ocp·公众号:厦门微思网络·数据库专家
wangjialelele15 小时前
mysql库操作二
数据库·mysql