以下通过完整的示例演示MySQL优化全过程,包含建表、百万数据生成、性能测试和优化步骤:


数据准备阶段

1. 创建测试表结构
-- 创建订单表(未优化版本)
CREATE TABLE unoptimized_orders (
  id INT PRIMARY KEY AUTO_INCREMENT,
  order_no VARCHAR(20) NOT NULL COMMENT '订单号',
  user_id INT NOT NULL,
  product_id INT NOT NULL,
  amount DECIMAL(10,2) NOT NULL,
  status ENUM('pending','completed','canceled') NOT NULL DEFAULT 'pending',
  province VARCHAR(20) COMMENT '省份',
  city VARCHAR(20) COMMENT '城市',
  create_time DATETIME NOT NULL,
  update_time DATETIME NOT NULL
) ENGINE=InnoDB;

-- 添加基本索引
ALTER TABLE unoptimized_orders 
  ADD INDEX idx_user (user_id),
  ADD INDEX idx_status (status);
2. 生成百万测试数据(约5分钟)
-- 创建存储过程
DELIMITER $$
CREATE PROCEDURE GenerateOrders()
BEGIN
  DECLARE i INT DEFAULT 0;
  WHILE i < 1000000 DO
    INSERT INTO unoptimized_orders (
      order_no, user_id, product_id, amount, 
      status, province, city, create_time, update_time
    ) VALUES (
      CONCAT('NO', LPAD(i, 8, '0')),  -- 订单号
      FLOOR(RAND()*10000),            -- 用户ID 0-9999
      FLOOR(RAND()*500),              -- 商品ID 0-499
      RAND()*1000,                    -- 金额 0-999.99
      ELT(FLOOR(RAND()*3)+1, 'pending','completed','canceled'),
      ELT(FLOOR(RAND()*5)+1, '北京','上海','广东','江苏','浙江'),
      ELT(FLOOR(RAND()*5)+1, '朝阳区','浦东新区','天河区','鼓楼区','余杭区'),
      NOW() - INTERVAL FLOOR(RAND()*365) DAY,
      NOW() - INTERVAL FLOOR(RAND()*300) HOUR
    );
    SET i = i + 1;
  END WHILE;
END$$
DELIMITER ;

-- 执行数据生成(根据机器性能调整)
CALL GenerateOrders();

优化实战演示

案例1:复合索引优化(高频查询)
-- 查询:某用户已完成的订单(执行时间:1.8秒)
EXPLAIN 
SELECT * FROM unoptimized_orders 
WHERE user_id = 123 
  AND status = 'completed'
  AND create_time BETWEEN '2023-01-01' AND '2023-12-31';

-- 优化步骤:
ALTER TABLE unoptimized_orders 
  ADD INDEX idx_user_status_time (user_id, status, create_time);

-- 执行时间降为0.03秒,EXPLAIN显示:
-- type: ref
-- key: idx_user_status_time
-- rows: 35
案例2:分页查询优化
-- 原始分页(执行时间:2.4秒)
SELECT * FROM unoptimized_orders 
WHERE status = 'completed'
ORDER BY id DESC
LIMIT 900000, 20;

-- 优化方案:使用游标分页
SELECT * FROM unoptimized_orders 
WHERE status = 'completed' 
  AND id < 900000  -- 假设当前页最后一条记录的ID
ORDER BY id DESC
LIMIT 20;

-- 执行时间降为0.15秒,EXPLAIN显示:
-- type: range
-- key: PRIMARY
-- rows: 450000 → 20
案例3:覆盖索引优化
-- 查询用户订单统计(执行时间:1.2秒)
EXPLAIN 
SELECT user_id, COUNT(*), SUM(amount) 
FROM unoptimized_orders 
WHERE status = 'completed'
GROUP BY user_id;

-- 创建覆盖索引
ALTER TABLE unoptimized_orders 
  ADD INDEX idx_status_user_amount (status, user_id, amount);

-- 执行时间降为0.25秒,EXPLAIN显示:
-- Extra: Using index
-- 扫描行数从100万降为35642
案例4:前缀索引优化
-- 地址联合查询(执行时间:0.8秒)
SELECT * FROM unoptimized_orders 
WHERE province = '广东' 
  AND city = '天河区';

-- 添加组合索引(使用前缀)  
ALTER TABLE unoptimized_orders
  ADD INDEX idx_province_city (province(4), city(6));

-- 执行时间降为0.12秒,索引大小减少60%

优化后完整表结构

CREATE TABLE optimized_orders (
  -- 原字段保持不变
  ...
) ENGINE=InnoDB
  COMMENT='优化后的订单表';

-- 优化后的索引配置
ALTER TABLE optimized_orders
  ADD PRIMARY KEY (id),
  ADD INDEX idx_user_status_time (user_id, status, create_time),
  ADD INDEX idx_status_user_amount (status, user_id, amount),
  ADD INDEX idx_province_city (province(4), city(6)),
  ADD INDEX idx_order_no (order_no(8));

性能对比测试

查询类型 优化前耗时 优化后耗时 提升倍数
用户+状态联合查询 1800ms 28ms 64x
百万级分页查询 2400ms 150ms 16x
分组统计查询 1200ms 250ms 4.8x
地址联合查询 800ms 120ms 6.6x

数据验证技巧

  1. 执行时间测试
SELECT BENCHMARK(1000, (
  SELECT COUNT(*) FROM unoptimized_orders 
  WHERE status = 'completed'
));
  1. 索引效果验证
-- 查看索引使用情况
SELECT index_name, stat_value*@@innodb_page_size/1024/1024 AS size_mb
FROM mysql.innodb_index_stats
WHERE table_name = 'unoptimized_orders';

优化总结建议

  1. 数据量级验证:在100万数据量级下,全表扫描成本约为1-2秒量级
  2. 索引黄金法则
    • 优先为WHERE条件的列创建索引
    • 范围查询字段放在复合索引最后
    • 超过100万数据时,字符串字段建议使用前缀索引
  3. 维护策略
-- 定期优化表
OPTIMIZE TABLE optimized_orders;

-- 重建索引(MySQL 8.0+)
ALTER TABLE optimized_orders ALTER INDEX idx_user_status_time INVISIBLE VISIBLE;

通过以上实例可以明显看出,合理的索引设计和SQL优化能让百万级数据表保持毫秒级响应。实际生产环境中建议结合pt-query-digest工具进行慢查询分析,持续优化热点查询。

Logo

永洪科技,致力于打造全球领先的数据技术厂商,具备从数据应用方案咨询、BI、AIGC智能分析、数字孪生、数据资产、数据治理、数据实施的端到端大数据价值服务能力。

更多推荐