未记录事实核验 · 状态:维护中

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用边界与维护说明

本文适合具备执行计划、索引和事务基础的读者学习慢查询诊断、索引选型、查询重写、预聚合及并发库存设计,不是可直接运行上线或已证明优化收益的完整项目。本次仅作全文结构审阅,未执行脚本或作外部事实核验。数据脚本仅生成1000名用户,未提供订单、商品及深度分页负载;性能测试过程未执行待测SQL,EXPLAIN ANALYZE使用查询占位符,A/B数值仅为假设样例。分页查询与辅助函数仍仅以时间戳作为游标,延迟关联缺少外层排序;按日统计重算未实现事务保护,且源表允许空类别而统计表不允许;库存示例未实现预留失败分支、版本读取和分段失败重试,不能直接用于生产统计或真实扣减。WITH要求MySQL 8.0及以上,EXPLAIN ANALYZE要求8.0.18及以上;监控查询、Percona工具及在线DDL还需具备相应权限、安装环境和拓扑条件。实际优化结论须通过结果等价性检查、真实执行计划及代表性负载测试确认,预聚合还须明确更新周期与数据新鲜度。

🚀 MySQL慢查询优化实战 - 从发现到解决的完整流程

abstract 📋 本节概览

🔍 慢查询优化方法论

优化流程图

%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
    classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
    classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
    classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
    classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
    classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
    classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
    classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
    A[发现慢查询]
    B[收集查询信息]
    A --> B --> C[分析执行计划]
    C --> D[识别瓶颈点]
    D --> E{瓶颈类型}
    E -->|索引问题| F[索引优化]
    E -->|查询逻辑| G[查询重写]
    E -->|数据量大| H[分页/分区优化]
    E -->|锁争用| I[并发优化]
    F --> J[验证优化效果]
    G --> J
    H --> J
    I --> J
    J --> K{是否达标?}
    K -->|否| L[进一步优化]
    K -->|是| M[部署上线]
    L --> D
    M --> N[持续监控]
    class E,K decision
    class L,I,A,B,C,D,F,H,J,M,N default

📊 慢查询发现与收集

慢查询识别工具

-- 1. 实时运行的慢查询
SELECT 
    p.ID,
    p.USER,
    p.HOST,
    p.DB,
    p.TIME as duration_seconds,
    p.STATE,
    LEFT(p.INFO, 200) as query_text,
    t.trx_started,
    t.trx_rows_locked,
    t.trx_tables_locked
FROM INFORMATION_SCHEMA.PROCESSLIST p
LEFT JOIN INFORMATION_SCHEMA.INNODB_TRX t ON p.ID = t.trx_mysql_thread_id
WHERE p.TIME > 5  -- 运行超过5秒
  AND p.COMMAND = 'Query'
ORDER BY p.TIME DESC;

-- 2. Performance Schema中的慢查询
SELECT 
    DIGEST_TEXT,
    COUNT_STAR as exec_count,
    ROUND(AVG_TIMER_WAIT/1000000000000, 3) as avg_time_sec,
    ROUND(MAX_TIMER_WAIT/1000000000000, 3) as max_time_sec,
    ROUND(SUM_TIMER_WAIT/1000000000000, 3) as total_time_sec,
    ROUND(SUM_ROWS_EXAMINED / NULLIF(COUNT_STAR, 0), 0) as avg_rows_examined,
    ROUND(SUM_ROWS_SENT / NULLIF(COUNT_STAR, 0), 0) as avg_rows_sent,
    ROUND(SUM_ROWS_EXAMINED / NULLIF(SUM_ROWS_SENT, 0), 2) as examine_ratio
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME = DATABASE()
  AND AVG_TIMER_WAIT > 1000000000000  -- 平均执行时间 > 1秒
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;

-- 3. 特定类型的问题查询
-- 全表扫描查询
SELECT 
    DIGEST_TEXT,
    COUNT_STAR,
    SUM_SELECT_SCAN,
    ROUND(SUM_SELECT_SCAN / COUNT_STAR, 2) as avg_full_scans,
    ROUND(SUM_TIMER_WAIT/1000000000000, 3) as total_time_sec
FROM performance_schema.events_statements_summary_by_digest
WHERE SUM_SELECT_SCAN > 0
ORDER BY SUM_SELECT_SCAN DESC;

-- 临时表使用过多的查询
SELECT 
    DIGEST_TEXT,
    COUNT_STAR,
    SUM_CREATED_TMP_TABLES,
    SUM_CREATED_TMP_DISK_TABLES,
    ROUND(SUM_CREATED_TMP_DISK_TABLES / NULLIF(SUM_CREATED_TMP_TABLES, 0) * 100, 2) as disk_tmp_ratio
FROM performance_schema.events_statements_summary_by_digest
WHERE SUM_CREATED_TMP_TABLES > 0
ORDER BY SUM_CREATED_TMP_DISK_TABLES DESC;

🔧 实战案例一:JOIN查询优化

问题场景

-- 创建测试表结构
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    status TINYINT DEFAULT 1,
    
    INDEX idx_username (username),
    INDEX idx_created (created_at),
    INDEX idx_status (status)
) ENGINE=InnoDB;

CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    product_id INT NOT NULL,
    amount DECIMAL(10,2),
    order_date DATE,
    status VARCHAR(20),
    
    INDEX idx_user_id (user_id),
    INDEX idx_product_id (product_id),
    INDEX idx_order_date (order_date),
    INDEX idx_status (status)
) ENGINE=InnoDB;

CREATE TABLE products (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100),
    category_id INT,
    price DECIMAL(10,2),
    
    INDEX idx_category (category_id)
) ENGINE=InnoDB;

-- 插入测试数据
INSERT INTO users (username, email, status) 
SELECT 
    CONCAT('user_', n),
    CONCAT('user_', n, '@email.com'),
    CASE WHEN n % 10 = 0 THEN 0 ELSE 1 END
FROM (
    SELECT a.N + b.N * 10 + c.N * 100 + 1 n
    FROM (SELECT 0 N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 
          UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) a,
         (SELECT 0 N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 
          UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) b,
         (SELECT 0 N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 
          UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) c
    ORDER BY n LIMIT 1000
) nums;

-- 问题查询:获取用户订单统计
-- ❌ 优化前的慢查询
SELECT 
    u.username,
    u.email,
    COUNT(o.id) as order_count,
    SUM(o.amount) as total_amount,
    AVG(p.price) as avg_product_price,
    MAX(o.order_date) as last_order_date
FROM users u
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'completed'
LEFT JOIN products p ON o.product_id = p.id
WHERE u.created_at >= '2024-01-01'
  AND u.status = 1
GROUP BY u.id, u.username, u.email
HAVING COUNT(o.id) > 5
ORDER BY total_amount DESC
LIMIT 100;

-- 分析执行计划
EXPLAIN FORMAT=JSON 
SELECT 
    u.username,
    u.email,
    COUNT(o.id) as order_count,
    SUM(o.amount) as total_amount,
    AVG(p.price) as avg_product_price,
    MAX(o.order_date) as last_order_date
FROM users u
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'completed'
LEFT JOIN products p ON o.product_id = p.id
WHERE u.created_at >= '2024-01-01'
  AND u.status = 1
GROUP BY u.id, u.username, u.email
HAVING COUNT(o.id) > 5
ORDER BY total_amount DESC
LIMIT 100;

优化过程

-- 步骤1:创建复合索引优化WHERE条件
CREATE INDEX idx_user_status_created ON users (status, created_at);
CREATE INDEX idx_order_user_status ON orders (user_id, status);

-- 步骤2:查询重写 - 先过滤再关联
-- ✅ 优化后的查询
WITH active_users AS (
    SELECT id, username, email
    FROM users
    WHERE status = 1 
      AND created_at >= '2024-01-01'
),
user_orders AS (
    SELECT 
        o.user_id,
        COUNT(*) as order_count,
        SUM(o.amount) as total_amount,
        MAX(o.order_date) as last_order_date,
        AVG(p.price) as avg_product_price
    FROM orders o
    LEFT JOIN products p ON o.product_id = p.id
    WHERE o.status = 'completed'
      AND o.user_id IN (SELECT id FROM active_users)
    GROUP BY o.user_id
    HAVING COUNT(*) > 5
)
SELECT 
    u.username,
    u.email,
    uo.order_count,
    uo.total_amount,
    uo.avg_product_price,
    uo.last_order_date
FROM active_users u
INNER JOIN user_orders uo ON u.id = uo.user_id
ORDER BY uo.total_amount DESC
LIMIT 100;

-- 步骤3:创建覆盖索引减少回表
CREATE INDEX idx_order_covering ON orders (user_id, status, product_id, amount, order_date);

-- 步骤4:比较优化效果
-- 使用EXPLAIN ANALYZE比较执行时间
EXPLAIN ANALYZE [原查询];
EXPLAIN ANALYZE [优化后查询];

🚀 实战案例二:分页查询优化

深度分页问题

-- 问题:传统分页在大偏移量时性能急剧下降
-- ❌ 性能差的深度分页
SELECT id, username, email, created_at
FROM users
ORDER BY created_at DESC
LIMIT 100000, 20;  -- 第5001页,每页20条(页码从1开始)

-- 分析问题
EXPLAIN SELECT id, username, email, created_at
FROM users
ORDER BY created_at DESC
LIMIT 100000, 20;

-- 优化方案1:基于游标的分页(推荐用于大数据量分页)
-- 💡 原理:记住上一页最后一条记录的位置,从那里继续查询
-- 在匹配索引与数据分布适当时,可减少随偏移量增长的扫描开销;不保证固定耗时,重复时间戳还需加入唯一键作为游标
SELECT id, username, email, created_at
FROM users
WHERE created_at < '2024-05-15 10:30:00'  -- 上次查询的最小时间作为游标
ORDER BY created_at DESC
LIMIT 20;

-- 优化方案2:延迟关联(覆盖索引 + 回表)
-- 💡 原理:先通过索引快速定位主键ID,再通过主键获取完整数据
-- 可减少大偏移量分页的回表开销,但仍需扫描并跳过偏移记录;外层应明确ORDER BY
SELECT u.id, u.username, u.email, u.created_at
FROM users u
INNER JOIN (
    SELECT id              -- 子查询使用覆盖索引,速度很快
    FROM users
    ORDER BY created_at DESC
    LIMIT 100000, 20      -- 在子查询中处理大偏移量
) t ON u.id = t.id;

-- 优化方案3:记录总数缓存
-- 避免每次都COUNT(*)计算总数
-- 可以使用Redis缓存或定期更新统计表

-- 创建分页辅助函数
DELIMITER $$
CREATE FUNCTION GetNextPageToken(last_created_at TIMESTAMP, page_size INT)
RETURNS TEXT
READS SQL DATA
DETERMINISTIC
BEGIN
    DECLARE next_token TEXT DEFAULT '';
    
    SELECT MIN(created_at) INTO next_token
    FROM (
        SELECT created_at
        FROM users
        WHERE created_at < last_created_at
        ORDER BY created_at DESC
        LIMIT page_size
    ) t;
    
    RETURN IFNULL(next_token, '');
END$$
DELIMITER ;

📈 实战案例三:聚合查询优化

复杂聚合统计优化

note 💡 聚合查询优化思路

聚合查询的基础语法可参考 SQL 和 SQL,这里重点讲解MySQL特有的优化策略

-- 场景:用户行为统计报表
-- ❌ 性能差的实时聚合查询
SELECT 
    DATE(o.order_date) as date,
    p.category_id,
    COUNT(*) as order_count,
    COUNT(DISTINCT o.user_id) as unique_users,
    SUM(o.amount) as total_amount,
    AVG(o.amount) as avg_amount,
    MIN(o.amount) as min_amount,
    MAX(o.amount) as max_amount
FROM orders o
JOIN products p ON o.product_id = p.id
WHERE o.order_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
  AND o.status = 'completed'
GROUP BY DATE(o.order_date), p.category_id
ORDER BY date DESC, total_amount DESC;

-- 问题分析:
-- 1. WHERE直接比较order_date,不能据此认定日期索引失效;需结合执行计划分析分组、排序及扫描成本
-- 2. 大量数据的实时聚合计算
-- 3. 复杂的多维度分组

-- 优化方案1:创建预聚合表(空间换时间策略)
-- 💡 原理:提前计算好统计结果,查询时直接读取,避免实时聚合计算
CREATE TABLE daily_order_stats (
    stat_date DATE NOT NULL,
    category_id INT NOT NULL,
    order_count INT DEFAULT 0,
    unique_users INT DEFAULT 0,
    total_amount DECIMAL(15,2) DEFAULT 0,
    avg_amount DECIMAL(10,2) DEFAULT 0,
    min_amount DECIMAL(10,2) DEFAULT 0,
    max_amount DECIMAL(10,2) DEFAULT 0,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    PRIMARY KEY (stat_date, category_id),
    INDEX idx_date (stat_date),
    INDEX idx_category (category_id)
) ENGINE=InnoDB;

-- 优化方案2:按日删除后重新计算统计数据(非真正增量更新;删除与插入应在事务中原子完成)
DELIMITER $$
CREATE PROCEDURE UpdateDailyStats(IN target_date DATE)
BEGIN
    -- 删除当天的统计数据
    DELETE FROM daily_order_stats WHERE stat_date = target_date;
    
    -- 重新计算并插入
    INSERT INTO daily_order_stats (
        stat_date, category_id, order_count, unique_users,
        total_amount, avg_amount, min_amount, max_amount
    )
    SELECT 
        target_date,
        p.category_id,
        COUNT(*),
        COUNT(DISTINCT o.user_id),
        SUM(o.amount),
        AVG(o.amount),
        MIN(o.amount),
        MAX(o.amount)
    FROM orders o
    JOIN products p ON o.product_id = p.id
    WHERE DATE(o.order_date) = target_date
      AND o.status = 'completed'
    GROUP BY p.category_id;
END$$
DELIMITER ;

-- 优化方案3:使用物化视图(MySQL 8.0可用存储过程模拟)
-- ✅ 查询优化后的统计数据
SELECT 
    stat_date,
    category_id,
    order_count,
    unique_users,
    total_amount,
    avg_amount
FROM daily_order_stats
WHERE stat_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
ORDER BY stat_date DESC, total_amount DESC;

🔒 实战案例四:锁争用优化

高并发写入优化

-- 场景:商品库存更新的高并发问题
CREATE TABLE products_inventory (
    product_id INT PRIMARY KEY,
    stock_quantity INT NOT NULL DEFAULT 0,
    reserved_quantity INT NOT NULL DEFAULT 0,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    version INT NOT NULL DEFAULT 0,  -- 乐观锁版本号
    
    INDEX idx_updated (updated_at)
) ENGINE=InnoDB;

-- ❌ 问题:热点商品的库存更新导致锁争用
-- 传统的库存扣减方式
UPDATE products_inventory 
SET stock_quantity = stock_quantity - 1,
    version = version + 1
WHERE product_id = 12345 
  AND stock_quantity >= 1;

-- 优化方案1:乐观锁控制(适用于并发冲突较少的场景)
-- 💡 原理:假设冲突很少,先读取版本号,更新时检查版本号是否变化
-- ✅ 带版本控制的更新
UPDATE products_inventory 
SET stock_quantity = stock_quantity - 1,
    version = version + 1,
    updated_at = NOW()
WHERE product_id = 12345 
  AND stock_quantity >= 1 
  AND version = @current_version;  -- 乐观锁检查

-- 优化方案2:分段库存策略(化解热点,分散锁争用)
-- 💡 原理:把一个热点库存拆分成多个小库存,降低单点锁竞争
-- 将热点商品库存分成多个段,减少锁争用
CREATE TABLE inventory_segments (
    product_id INT NOT NULL,
    segment_id TINYINT NOT NULL,
    stock_quantity INT NOT NULL DEFAULT 0,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    PRIMARY KEY (product_id, segment_id),
    INDEX idx_product (product_id)
) ENGINE=InnoDB;

-- 随机选择段进行更新
SET @segment = FLOOR(RAND() * 10);  -- 假设分10个段
UPDATE inventory_segments 
SET stock_quantity = stock_quantity - 1
WHERE product_id = 12345 
  AND segment_id = @segment
  AND stock_quantity >= 1;

-- 优化方案3:预留库存机制
-- 先预留,后确认
START TRANSACTION;

-- 预留库存
UPDATE products_inventory 
SET reserved_quantity = reserved_quantity + 1
WHERE product_id = 12345 
  AND (stock_quantity - reserved_quantity) >= 1;

-- 检查预留是否成功
SELECT stock_quantity, reserved_quantity 
FROM products_inventory 
WHERE product_id = 12345;

-- 根据业务逻辑决定确认或释放预留
-- 以下仅展示确认语句:必须先检查预留UPDATE的受影响行数为1;失败时回滚,不能执行确认
-- 确认购买:
UPDATE products_inventory 
SET stock_quantity = stock_quantity - 1,
    reserved_quantity = reserved_quantity - 1
WHERE product_id = 12345;

COMMIT;

📊 优化效果验证

性能对比测试

-- 1. 创建性能测试框架
CREATE TABLE query_performance_log (
    id INT AUTO_INCREMENT PRIMARY KEY,
    query_name VARCHAR(100),
    query_type ENUM('before', 'after'),
    execution_time_ms DECIMAL(10,3),
    rows_examined INT,
    rows_sent INT,
    created_tmp_tables INT,
    created_tmp_disk_tables INT,
    test_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 2. 性能测试存储过程骨架:当前未执行query_sql,记录值不能作为目标查询耗时
DELIMITER $$
CREATE PROCEDURE TestQueryPerformance(
    IN query_name VARCHAR(100),
    IN query_sql TEXT,
    IN query_type ENUM('before', 'after')
)
BEGIN
    DECLARE start_time DECIMAL(20,6);
    DECLARE end_time DECIMAL(20,6);
    DECLARE execution_time DECIMAL(10,3);
    
    -- 记录开始时间
    SET start_time = UNIX_TIMESTAMP(NOW(6));
    
    -- 执行查询(这里需要动态SQL,实际使用时需要具体实现)
    -- SET @sql = query_sql;
    -- PREPARE stmt FROM @sql;
    -- EXECUTE stmt;
    -- DEALLOCATE PREPARE stmt;
    
    -- 记录结束时间
    SET end_time = UNIX_TIMESTAMP(NOW(6));
    SET execution_time = (end_time - start_time) * 1000;
    
    -- 记录性能数据
    INSERT INTO query_performance_log (
        query_name, query_type, execution_time_ms
    ) VALUES (
        query_name, query_type, execution_time
    );
END$$
DELIMITER ;

-- 3. 性能对比报告
SELECT 
    query_name,
    AVG(CASE WHEN query_type = 'before' THEN execution_time_ms END) as before_avg_ms,
    AVG(CASE WHEN query_type = 'after' THEN execution_time_ms END) as after_avg_ms,
    ROUND(
        (AVG(CASE WHEN query_type = 'before' THEN execution_time_ms END) - 
         AVG(CASE WHEN query_type = 'after' THEN execution_time_ms END)) /
        AVG(CASE WHEN query_type = 'before' THEN execution_time_ms END) * 100, 2
    ) as improvement_percent
FROM query_performance_log
WHERE test_date >= DATE_SUB(NOW(), INTERVAL 1 DAY)
GROUP BY query_name
ORDER BY improvement_percent DESC;

-- 4. A/B测试框架
CREATE TABLE ab_test_results (
    test_id VARCHAR(50),
    version ENUM('A', 'B'),
    metric_name VARCHAR(100),
    metric_value DECIMAL(10,3),
    sample_size INT,
    test_date DATE,
    
    PRIMARY KEY (test_id, version, metric_name, test_date)
);

-- 插入假设的A/B测试样例数据,仅演示表结构与报告流程,不代表实际测量或优化收益
INSERT INTO ab_test_results VALUES 
('join_optimization', 'A', 'avg_response_time', 2.5, 1000, CURDATE()),
('join_optimization', 'B', 'avg_response_time', 0.8, 1000, CURDATE()),
('join_optimization', 'A', 'cpu_usage', 75.2, 1000, CURDATE()),
('join_optimization', 'B', 'cpu_usage', 45.8, 1000, CURDATE());

🎓 优化工具与技巧总结

常用优化工具

# 1. pt-query-digest - 慢查询分析
pt-query-digest --limit 100% /var/log/mysql/slow.log > analysis.txt

# 2. pt-online-schema-change - 在线DDL
pt-online-schema-change --alter "ADD INDEX idx_name (name)" \
  --execute h=localhost,D=database,t=table

# 3. pt-table-checksum - 数据一致性检查
pt-table-checksum --replicate=percona.checksums h=master-host

# 4. pt-index-usage - 索引使用分析
pt-index-usage /var/log/mysql/slow.log

# 5. mysqlslap - 负载测试
mysqlslap --user=root --password=password \
  --host=localhost --concurrency=50 --iterations=10 \
  --query="SELECT * FROM users WHERE id = FLOOR(RAND()*10000)" \
  --create-schema=testdb

优化决策树

%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
    classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
    classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
    classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
    classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
    classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
    classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
    classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
    A[发现慢查询]
    B{查询频率如何?}
    A --> B
    B -->|高频| C[重点优化]
    B -->|低频| D[考虑是否优化]
    E{瓶颈在哪里?}
    C --> E
    E -->|索引| F[创建/优化索引]
    D --> E
    E -->|JOIN| G[优化连接顺序]
    E -->|聚合| H[考虑预聚合]
    E -->|排序| I[优化ORDER BY]
    J{索引类型选择}
    F --> J
    J -->|单列| K[普通索引]
    I --> J
    J -->|多列| L[复合索引]
    J -->|覆盖| M[覆盖索引]
    G --> N{表大小关系}
    N -->|小驱动大| O[调整连接顺序]
    N -->|索引支持| P[确保被驱动表有索引]
    class N decision
    class A,B,D,E,G,I,J,L,O,P default

tip 💡 慢查询优化要点

优化优先级

  1. 高频慢查询优先优化
  2. 影响核心业务的查询优先
  3. 优化成本低的问题优先
  4. ROI(投入产出比)高的优化优先

常见优化策略

  • 索引优化:若假设某个特定样本中80%的性能问题可通过索引解决,该比例仍需样本与测量支持;本文未提供此证据,不应将其作为通用事实,实际应依据执行计划和负载测试判断
  • 查询重写:改变查询逻辑,避开性能瓶颈
  • 数据架构:分表、分库、缓存等架构级优化
  • 硬件升级:SSD、内存扩容等硬件优化

warning ⚠️ 优化注意事项

  • 不要过度优化:确保优化带来的收益大于成本
  • 监控副作用:索引优化可能影响写入性能
  • 测试验证:在生产环境前充分测试
  • 回滚准备:准备好快速回滚方案

🔗 下一步学习

完成慢查询优化后,建议学习:


最后更新:2024-06-29