一、SQL 优化基本原则
1. 基础优化原则
-
避免 SELECT *,只选择需要的列
-
使用 LIMIT 限制返回的行数
-
避免在 WHERE 子句中使用函数或表达式
-
合理使用连接(JOIN)代替子查询
二、索引策略示例
1. 单列索引
sql
-- 创建单列索引
CREATE INDEX idx_user_email ON users(email);
CREATE INDEX idx_order_date ON orders(order_date);
-- 复合索引(最左前缀原则)
CREATE INDEX idx_user_name_email ON users(last_name, first_name, email);
2. 覆盖索引优化
sql
-- 创建覆盖索引,包含查询中所有需要的列
CREATE INDEX idx_order_covering ON orders(user_id, order_date, total_amount, status);
-- 查询可以利用覆盖索引,无需回表
SELECT user_id, order_date, total_amount
FROM orders
WHERE user_id = 123 AND order_date > '2024-01-01';
3. 索引失效的常见场景
sql
-- 1. 对索引列进行运算
SELECT * FROM users WHERE YEAR(create_time) = 2024; -- 索引失效
SELECT * FROM users WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01'; -- 有效
-- 2. 使用 NOT LIKE,!=,<>
SELECT * FROM users WHERE email NOT LIKE 'test%@example.com'; -- 可能失效
-- 3. OR 条件未全部使用索引
SELECT * FROM users WHERE email = 'a@b.com' OR phone = '123456'; -- 如果phone无索引,索引可能失效
三、查询优化案例与 Explain 对比
案例1:分页查询优化
优化前:
sql
EXPLAIN SELECT * FROM orders
ORDER BY create_time DESC
LIMIT 100000, 20;
-- Explain 结果:
-- type: ALL (全表扫描)
-- rows: 100020
-- Extra: Using filesort
优化后:
sql
EXPLAIN SELECT * FROM orders
WHERE id > (
SELECT id FROM orders
ORDER BY create_time DESC
LIMIT 100000, 1
)
ORDER BY create_time DESC
LIMIT 20;
-- 或者使用延迟关联
EXPLAIN SELECT o.* FROM orders o
JOIN (
SELECT id FROM orders
ORDER BY create_time DESC
LIMIT 100000, 20
) AS tmp ON o.id = tmp.id;
-- Explain 结果:
-- type: range (范围扫描)
-- rows: 20
-- Extra: Using where
案例2:JOIN 查询优化
优化前:
sql
EXPLAIN SELECT *
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.status = 'completed'
AND u.country = 'US';
-- Explain 结果可能显示:
-- 驱动表选择不当
-- 未使用合适的索引
优化后:
sql
-- 1. 添加复合索引
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
CREATE INDEX idx_users_country ON users(country);
-- 2. 重写查询
EXPLAIN SELECT o.*, u.name
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.country = 'US'
AND o.status = 'completed';
-- Explain 结果改善:
-- 驱动表变为 users(更小的结果集)
-- 使用了复合索引
案例3:子查询优化
优化前:
sql
EXPLAIN SELECT * FROM products p
WHERE p.category_id IN (
SELECT category_id FROM popular_categories
WHERE sales > 10000
);
-- Explain 结果:
-- type: DEPENDENT SUBQUERY
-- Extra: Using where
优化后:
sql
-- 使用 JOIN 代替子查询
EXPLAIN SELECT DISTINCT p.*
FROM products p
JOIN popular_categories pc ON p.category_id = pc.category_id
WHERE pc.sales > 10000;
-- 或者使用 EXISTS
EXPLAIN SELECT * FROM products p
WHERE EXISTS (
SELECT 1 FROM popular_categories pc
WHERE pc.category_id = p.category_id
AND pc.sales > 10000
);
-- Explain 结果:
-- type: eq_ref 或 ref
-- Extra: Using index
四、Explain 关键字段解读
1. type 字段(访问类型),从好到差:
sql
-- 最佳到最差排序
system > const > eq_ref > ref > range > index > ALL
-- 示例解释:
const: 通过主键或唯一索引一次找到
eq_ref: 唯一索引关联查询
ref: 非唯一索引查询
range: 范围查询
index: 索引全扫描
ALL: 全表扫描
2. Extra 字段重要信息:
-
Using index: 使用覆盖索引
-
Using where: 在存储引擎后过滤
-
Using temporary: 使用临时表(需要优化)
-
Using filesort: 需要额外排序(需要优化)
-
Using join buffer: 需要使用连接缓冲区
五、实战优化案例
案例:电商订单统计查询
原始查询:
sql
EXPLAIN
SELECT
DATE(o.order_date) as day,
COUNT(*) as total_orders,
SUM(o.amount) as total_amount,
AVG(o.amount) as avg_amount
FROM orders o
WHERE o.order_date BETWEEN '2024-01-01' AND '2024-12-31'
AND o.status IN ('completed', 'shipped')
AND o.user_id IN (
SELECT id FROM users
WHERE vip_level > 1
AND register_time > '2023-01-01'
)
GROUP BY DATE(o.order_date)
ORDER BY day DESC
LIMIT 30;
优化步骤:
- 添加索引
sql
-- 复合索引
CREATE INDEX idx_orders_date_status_user ON orders(order_date, status, user_id, amount);
CREATE INDEX idx_users_vip_register ON users(vip_level, register_time, id);
- 重写查询
sql
EXPLAIN
SELECT
DATE(o.order_date) as day,
COUNT(*) as total_orders,
SUM(o.amount) as total_amount,
AVG(o.amount) as avg_amount
FROM orders o
JOIN (
SELECT id FROM users
WHERE vip_level > 1
AND register_time > '2023-01-01'
) AS vip_users ON o.user_id = vip_users.id
WHERE o.order_date >= '2024-01-01'
AND o.order_date < '2025-01-01'
AND o.status IN ('completed', 'shipped')
GROUP BY DATE(o.order_date)
ORDER BY day DESC
LIMIT 30;
- 进一步优化(使用覆盖索引)
sql
EXPLAIN
SELECT
day,
COUNT(*) as total_orders,
SUM(amount) as total_amount,
AVG(amount) as avg_amount
FROM (
SELECT
DATE(o.order_date) as day,
o.amount
FROM orders o
WHERE o.order_date >= '2024-01-01'
AND o.order_date < '2025-01-01'
AND o.status IN ('completed', 'shipped')
AND EXISTS (
SELECT 1 FROM users u
WHERE u.id = o.user_id
AND u.vip_level > 1
AND u.register_time > '2023-01-01'
)
) AS order_summary
GROUP BY day
ORDER BY day DESC
LIMIT 30;
六、优化工具和技巧
1. 使用 SQL_NO_CACHE 测试
sql
SELECT SQL_NO_CACHE * FROM large_table WHERE ...;
2. 分析索引使用情况
sql
-- 查看索引使用统计
SHOW INDEX FROM orders;
SELECT * FROM sys.schema_index_statistics;
3. 监控慢查询
sql
-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
-- 查看慢查询
SELECT * FROM mysql.slow_log;
4. 使用优化器提示
sql
-- 强制使用特定索引
SELECT * FROM orders FORCE INDEX(idx_order_date)
WHERE order_date > '2024-01-01';
-- 忽略索引
SELECT * FROM orders IGNORE INDEX(idx_order_date)
WHERE ...;
-- 使用索引合并
SELECT * FROM orders USE INDEX(idx_user, idx_date)
WHERE ...;
七、总结的最佳实践
-
索引设计原则:
-
为 WHERE、JOIN、ORDER BY、GROUP BY 的列创建索引
-
使用复合索引时,区分度高的列在前
-
避免过度索引,每个表建议不超过5个索引
-
-
查询编写原则:
-
使用 EXPLAIN 分析每个复杂查询
-
小表驱动大表(小结果集驱动大结果集)
-
避免 SELECT *,只获取需要的列
-
合理使用批量操作,减少事务次数
-
-
监控和调整:
-
定期分析慢查询日志
-
监控索引的使用情况
-
根据数据变化调整索引策略
-