作为数据库性能调优中最常见的问题之一,慢SQL优化是每个开发者必须掌握的硬核技能。我在金融、电商等多个行业处理过数百个慢查询案例,发现80%的性能问题都源于不到20%的SQL语句。下面通过10个真实案例,带你系统掌握慢SQL的分析方法和优化技巧。
重要提示:所有案例均基于MySQL 8.0环境,但原理通用。建议先通过EXPLAIN分析执行计划,再针对性优化。
问题SQL:
sql复制SELECT * FROM orders WHERE status = 'pending' AND create_time > '2023-01-01';
现象:10万行数据查询耗时1.2秒
分析:
status字段有索引但create_time无索引OR条件,导致索引失效type=ALL(全表扫描)优化方案:
sql复制ALTER TABLE orders ADD INDEX idx_status_time(status, create_time);
sql复制SELECT * FROM orders
WHERE status = 'pending'
AND create_time > '2023-01-01'
ORDER BY create_time DESC LIMIT 1000;
效果:查询时间降至28ms
问题SQL:
sql复制SELECT product_name FROM products
WHERE product_name LIKE '%手机%';
现象:50万商品数据查询耗时3.5秒
分析:
%导致无法使用索引优化方案:
sql复制ALTER TABLE products ADD FULLTEXT INDEX ft_name(product_name);
SELECT product_name FROM products
WHERE MATCH(product_name) AGAINST('手机');
效果:查询时间降至120ms
典型问题:
sql复制SELECT * FROM user_logs
ORDER BY create_time DESC
LIMIT 100000, 20;
优化方案:
sql复制SELECT a.* FROM user_logs a
INNER JOIN (
SELECT id FROM user_logs
ORDER BY create_time DESC
LIMIT 100000, 20
) b ON a.id = b.id;
sql复制SELECT * FROM user_logs
WHERE id > 上次最后一条ID
ORDER BY id ASC
LIMIT 20;
问题SQL:
sql复制SELECT COUNT(*) FROM user_behavior
WHERE create_date BETWEEN '2023-01-01' AND '2023-12-31';
优化方案:
sql复制-- 创建统计表
CREATE TABLE stats_daily (
stat_date DATE PRIMARY KEY,
user_count INT
);
-- 定时任务更新
INSERT INTO stats_daily
SELECT DATE(create_time), COUNT(*)
FROM user_behavior
GROUP BY DATE(create_time);
sql复制EXPLAIN SELECT COUNT(*) FROM user_behavior;
| 指标 | 理想值 | 问题值 | 优化方向 |
|---|---|---|---|
| type | const/ref/range | ALL | 检查索引使用 |
| rows | 接近实际 | 远大于实际 | 更新统计信息 |
| Extra | Using index | Using filesort | 优化排序字段 |
最左前缀原则:
(a,b,c)可优化a=1 AND b=2,但无法优化b=2索引选择性公式:
code复制选择性 = COUNT(DISTINCT column)/COUNT(*)
选择性>0.2的字段适合建索引
索引合并优化:
sql复制-- 启用index_merge
SET optimizer_switch='index_merge=on';
ini复制# InnoDB缓冲池(建议占内存70%-80%)
innodb_buffer_pool_size = 8G
# 排序缓冲区
sort_buffer_size = 4M
# 连接线程缓存
thread_cache_size = 16
开启慢查询日志:
ini复制slow_query_log = 1
long_query_time = 1
log_queries_not_using_indexes = 1
使用Performance Schema:
sql复制SELECT * FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;
问题代码:
python复制users = User.objects.all()
for user in users:
print(user.profile.address) # 每次循环都查询profile表
优化方案:
python复制users = User.objects.select_related('profile').all()
python复制# 反例
for item in items:
Model.objects.create(**item)
# 正例
Model.objects.bulk_create(items)
问题SQL:
sql复制SELECT * FROM orders o
JOIN users u ON o.user_id = u.id
JOIN products p ON o.product_id = p.id
WHERE u.status = 1 AND p.category = '电子'
优化方案:
sql复制WITH filtered_users AS (
SELECT id FROM users WHERE status = 1
),
filtered_products AS (
SELECT id FROM products WHERE category = '电子'
)
SELECT * FROM orders o
JOIN filtered_users u ON o.user_id = u.id
JOIN filtered_products p ON o.product_id = p.id
问题SQL:
sql复制SELECT * FROM products
WHERE category_id IN (
SELECT id FROM categories WHERE type = '电子'
)
优化方案:
sql复制SELECT p.* FROM products p
JOIN categories c ON p.category_id = c.id
WHERE c.type = '电子'
| 场景 | 推荐类型 | 避免类型 |
|---|---|---|
| 状态值 | TINYINT | VARCHAR |
| IP地址 | INT UNSIGNED | VARCHAR(15) |
| 时间戳 | TIMESTAMP | VARCHAR(20) |
问题SQL:
sql复制SELECT * FROM users WHERE phone = 13800138000
(phone是VARCHAR类型)
优化方案:
sql复制SELECT * FROM users WHERE phone = '13800138000'
水平分片键选择:
全局ID生成:
sql复制CREATE TABLE sequence (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY
) ENGINE=MyISAM;
yaml复制# 数据源配置示例
spring:
datasource:
write:
url: jdbc:mysql://master:3306/db
read:
url: jdbc:mysql://slave1:3306/db,jdbc:mysql://slave2:3306/db
索引检查:
SQL写法检查:
执行计划检查:
数据库配置检查:
架构设计检查:
在实际工作中,我习惯先用pt-query-digest分析慢查询日志,定位TOP 10慢SQL后,再结合EXPLAIN逐个击破。记住:优化是个持续过程,随着数据量增长,需要定期复查SQL性能。