【架构实战】SQL调优实战:从执行计划到索引优化

一、为什么需要SQL调优

在应用开发中,SQL性能直接影响系统响应速度:

慢SQL的影响:

  • 页面加载缓慢,用户体验差
  • 数据库CPU使用率飙升
  • 连接池耗尽,应用不可用
  • 甚至引发连锁故障

调优的目标:

  • 查询时间从秒级降到毫秒级
  • 减少数据库资源消耗
  • 提升系统吞吐量

二、执行计划分析

1. EXPLAIN使用

sql 复制代码
-- 基本分析
EXPLAIN SELECT * FROM orders WHERE user_id = 1001;

-- 详细分析(MySQL 8.0+)
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 1001;

返回字段说明:

字段 说明
id 查询编号
select_type 查询类型
table 涉及的表
partitions 涉及的分区
type 访问类型(重要)
possible_keys 可用的索引
key 实际使用的索引
key_len 索引长度
ref 索引列的引用
rows 预计扫描行数(重要)
filtered 过滤比例
Extra 额外信息(重要)

2. 访问类型

type值 说明 性能
ALL 全表扫描 最差
index 索引全扫描 较差
range 索引范围扫描 一般
ref 索引等值查询 较好
eq_ref 唯一索引查询 较好
const 常量查询 最好

3. Extra信息

信息 说明
Using filesort 需要额外排序
Using temporary 使用临时表
Using index 覆盖索引
Using index condition 索引下推
Using where 使用WHERE过滤

三、索引优化

1. 索引设计原则

复制代码
1. 区分度高的列放在前面
2. 复合索引遵循最左前缀原则
3. 不要在索引列上做函数运算
4. 尽量使用覆盖索引
5. 避免索引失效

2. 索引示例

sql 复制代码
-- 用户表索引设计
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    phone VARCHAR(20),
    email VARCHAR(100),
    status TINYINT,
    create_time TIMESTAMP,
    
    -- 手机号查询(高频率)
    INDEX idx_phone (phone),
    
    -- 邮箱查询
    INDEX idx_email (email),
    
    -- 复合索引:状态+创建时间(按状态筛选后按时间排序)
    INDEX idx_status_time (status, create_time),
    
    -- 复合索引:查询某个状态的最新用户
    INDEX idx_status_create (status, create_time DESC)
);

-- 订单表索引设计
CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_no VARCHAR(32),
    user_id BIGINT,
    shop_id BIGINT,
    order_status TINYINT,
    order_amount DECIMAL(12,2),
    order_time TIMESTAMP,
    pay_time TIMESTAMP,
    
    -- 订单号唯一索引
    UNIQUE INDEX uk_order_no (order_no),
    
    -- 用户订单列表(最常用)
    INDEX idx_user_time (user_id, order_time DESC),
    
    -- 商家订单列表
    INDEX idx_shop_time (shop_id, order_time DESC),
    
    -- 状态查询
    INDEX idx_status (order_status),
    
    -- 复合索引:商家+状态+时间
    INDEX idx_shop_status_time (shop_id, order_status, order_time DESC)
);

3. 索引失效场景

sql 复制代码
-- ❌ 索引列做运算
SELECT * FROM users WHERE YEAR(create_time) = 2024;

-- ✅ 正确写法
SELECT * FROM users WHERE create_time >= '2024-01-01' 
AND create_time < '2025-01-01';

-- ❌ 使用函数
SELECT * FROM users WHERE LOWER(phone) = '13800138000';

-- ✅ 正确写法
SELECT * FROM users WHERE phone = '13800138000';

-- ❌ 类型转换
SELECT * FROM orders WHERE order_no = 12345;

-- ✅ 正确写法
SELECT * FROM orders WHERE order_no = '12345';

-- ❌ 前缀模糊查询
SELECT * FROM users WHERE phone LIKE '%138';

-- ✅ 正确写法(后缀模糊查询仍可以用索引)
SELECT * FROM users WHERE phone LIKE '138%';

四、SQL优化技巧

1. 避免SELECT *

sql 复制代码
-- ❌ 查询所有列
SELECT * FROM orders WHERE order_id = 1;

-- ✅ 只查询需要的列
SELECT order_id, order_no, order_amount 
FROM orders WHERE order_id = 1;

2. 批量操作

sql 复制代码
-- ❌ 循环插入
INSERT INTO orders (order_no) VALUES ('A001');
INSERT INTO orders (order_no) VALUES ('A002');

-- ✅ 批量插入
INSERT INTO orders (order_no) VALUES ('A001'), ('A002'), ('A003');

3. 避免深度分页

sql 复制代码
-- ❌ 深度分页
SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;

-- ✅ 方式1:游标分页
SELECT * FROM orders 
WHERE id > 1000000 
ORDER BY id LIMIT 10;

-- ✅ 方式2:子查询
SELECT * FROM (
    SELECT id FROM orders ORDER BY id LIMIT 1000000, 10
) t 
JOIN orders ON t.id = orders.id;

-- ✅ 方式3:记录总数(先查ID)
SELECT * FROM orders 
WHERE id IN (SELECT id FROM orders ORDER BY id LIMIT 1000000, 10);

4. 预计算

sql 复制代码
-- ❌ 每次统计
SELECT COUNT(*) FROM orders 
WHERE order_date = '2024-01-15';

-- ✅ 预计算表
CREATE TABLE daily_order_stats (
    stat_date DATE PRIMARY KEY,
    order_count INT,
    order_amount DECIMAL(14,2)
);

-- 定时更新统计数据
INSERT INTO daily_order_stats 
SELECT order_date, COUNT(*), SUM(order_amount)
FROM orders 
WHERE order_date = '2024-01-14'
GROUP BY order_date;

五、慢查询诊断

1. 开启慢查询日志

sql 复制代码
-- 查看配置
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time%';

-- 开启慢查询
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 1秒

-- 查看慢查询
SHOW GLOBAL STATUS LIKE 'Slow_queries';

2. 分析慢查询

sql 复制代码
-- 查看最近的慢查询
SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10;

-- 使用mysqldumpslow分析
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

3. 诊断脚本

sql 复制代码
-- 查看最慢的查询
SELECT 
    query,
    count(*) as executions,
    avg(sec) as avg_sec,
    max(sec) as max_sec,
    sum(sec) as total_sec
FROM (
    SELECT SUBSTRING(SQL_TEXT, 1, 100) as query,
           TIME_TO_SEC(EXEC_TIME) as sec
    FROM mysql.slow_query_log
) t
GROUP BY query
ORDER BY total_sec DESC
LIMIT 10;

六、实战案例

案例1:订单列表优化

原始SQL:

sql 复制代码
SELECT * FROM orders 
WHERE user_id = 1001 
ORDER BY create_time DESC 
LIMIT 0, 20;

分析结果:

  • type: ALL(全表扫描)
  • rows: 1000000(扫描100万行)
  • Extra: Using filesort(需要排序)

优化方案:

sql 复制代码
-- 添加复合索引
ALTER TABLE orders ADD INDEX idx_user_time (user_id, create_time DESC);

优化后:

  • type: ref(索引查询)
  • rows: 20(只扫描20行)
  • Extra: Using index condition

案例2:统计查询优化

原始SQL:

sql 复制代码
SELECT 
    DATE(order_time) as date,
    COUNT(*) as order_count,
    SUM(order_amount) as total_amount
FROM orders
WHERE order_time >= '2024-01-01'
GROUP BY DATE(order_time);

问题: 在GROUP BY上使用函数,导致索引失效

优化方案:

sql 复制代码
-- 方案1:避免函数
ALTER TABLE orders ADD INDEX idx_order_time (order_time);

-- 方案2:预计算表
CREATE TABLE daily_stats (
    stat_date DATE PRIMARY KEY,
    order_count INT,
    order_amount DECIMAL(14,2)
);

-- 定时任务每天0点计算前一天数据
INSERT INTO daily_stats
SELECT 
    DATE(order_time) as stat_date,
    COUNT(*) as order_count,
    SUM(order_amount) as order_amount
FROM orders
WHERE order_time >= NOW() - INTERVAL 1 DAY
GROUP BY DATE(order_time);

-- 查询预计算表
SELECT * FROM daily_stats WHERE stat_date >= '2024-01-01';

七、总结

SQL调优是提升数据库性能的核心:

  • 执行计划:分析查询如何执行
  • 索引优化:创建合适的索引
  • SQL重构:避免性能陷阱
  • 慢查询监控:及时发现问题

最佳实践:

  1. 优先使用索引,避免全表扫描
  2. 避免在索引列上使用函数
  3. 用EXPLAIN分析每条慢SQL
  4. 定期维护索引(重建、删除冗余)

个人观点,仅供参考

相关推荐
恼书:-(空寄2 小时前
分库分表风险应对手册(生产实战版)
数据库·分库分表
两万五千个小时2 小时前
Claude Code 源码:Agent 工具 — 多 Agent 的路由与定义机制
人工智能·程序员·架构
XDHCOM2 小时前
ORA-06521: PL/SQL映射函数错误,权威解析Oracle报错故障修复与远程处理方案
数据库·sql·oracle
数厘2 小时前
2.11 约束的使用(主键、外键、非空、唯一、默认值约束)
sql·mysql·数据分析
wgzrmlrm743 小时前
mysql如何配置全文索引停用词_mysql ft_stopword_file设置
jvm·数据库·python
城数派3 小时前
2025年南京市全类别POI(55W+数据)
数据库·arcgis·信息可视化·数据分析·excel
大大大大晴天️3 小时前
Flink技术实践-Flink SQL 开发中的隐蔽陷阱
大数据·sql·flink
leonkay3 小时前
到底应不应该写注释?
性能优化·架构·个人开发·注释·代码规范·设计·规格说明书
疯狂成瘾者3 小时前
后端系统、服务稳定性里核心的指标有哪些
数据库