【架构实战】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. 定期维护索引(重建、删除冗余)

个人观点,仅供参考

相关推荐
科技小花17 小时前
全球化深水区,数据治理成为企业出海 “核心竞争力”
大数据·数据库·人工智能·数据治理·数据中台·全球化
X566118 小时前
如何在 Laravel 中正确保存嵌套动态表单数据(主服务与子服务)
jvm·数据库·python
码点滴19 小时前
什么时候用 DeepSeek V4,而不是 GPT-5/Claude/Gemini?
人工智能·gpt·架构·大模型·deepseek
heimeiyingwang19 小时前
【架构实战】状态机架构:订单/工单状态流转设计
观察者模式·架构·wpf
虹科网络安全19 小时前
艾体宝干货|数据复制详解:类型、原理与适用场景
java·开发语言·数据库
2301_7717172119 小时前
解决mysql报错:1406, Data too long for column
android·数据库·mysql
小江的记录本20 小时前
【Kafka核心】架构模型:Producer、Broker、Consumer、Consumer Group、Topic、Partition、Replica
java·数据库·分布式·后端·搜索引擎·架构·kafka
dvjr cloi20 小时前
MySQL Workbench菜单汉化为中文
android·数据库·mysql
dFObBIMmai20 小时前
MySQL主从同步中大事务导致的延迟_如何拆分大事务优化同步
jvm·数据库·python
szccyw020 小时前
mysql如何限制特定存储过程执行权限_MySQL存储过程安全访问
jvm·数据库·python