-- 优化 EXPLAIN SELECT*FROM users WHERE birthday >='1990-01-01'AND birthday <'1991-01-01';
-- 结果 type: range ← 使用索引 key: idx_birthday
五、实战优化案例
5.1 慢查询优化
原始查询:
1 2 3 4 5 6 7 8 9
SELECT u.name, o.order_no, o.amount FROM users u JOIN orders o ON u.id = o.user_id WHERE o.status =1 AND o.created_at >='2024-01-01' ORDERBY o.created_at DESC LIMIT 20;
-- 执行时间:5.2秒
问题分析:
1 2 3 4 5 6 7 8 9 10
EXPLAIN SELECT ...
-- users表 type: ALL rows: 100000
-- orders表 type: ALL rows: 1000000 Extra: Usingwhere; Using filesort
优化步骤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
-- 1. 为orders.user_id添加索引 CREATE INDEX idx_user_id ON orders(user_id);
-- 2. 创建联合索引 CREATE INDEX idx_status_created ON orders(status, created_at);
-- 3. 优化后再次查询 EXPLAIN SELECT ...
-- orders表 type: ref key: idx_status_created rows: 1000 ← 大幅减少 Extra: Using index condition
-- 执行时间:0.05秒 ✅
5.2 分页查询优化
问题查询:
1 2 3 4 5 6
-- 深度分页,非常慢 SELECT*FROM orders ORDERBY id LIMIT 1000000, 20;
-- 执行时间:10秒
优化方案1:使用子查询
1 2 3 4 5 6 7
SELECT*FROM orders WHERE id >= ( SELECT id FROM orders ORDERBY id LIMIT 1000000, 1 ) LIMIT 20;
-- 执行时间:0.5秒
优化方案2:记录上次位置
1 2 3 4 5 6 7 8 9
-- 第一次查询 SELECT*FROM orders WHERE id >0ORDERBY id LIMIT 20; -- 返回最后一条的id为100
-- 第二次查询 SELECT*FROM orders WHERE id >100ORDERBY id LIMIT 20; -- 返回最后一条的id为120
-- 执行时间:0.01秒 ✅
5.3 COUNT优化
慢查询:
1 2
SELECTCOUNT(*) FROM orders WHERE status =1; -- 执行时间:3秒
优化方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
-- 1. 添加索引 CREATE INDEX idx_status ON orders(status);
-- 2. 如果status只有少数几个值,考虑维护计数表 CREATE TABLE order_counts ( status INTPRIMARY KEY, count INT );
-- 更新订单时,同时更新计数 BEGIN; INSERT INTO orders (...) VALUES (...); UPDATE order_counts SET count = count +1WHERE status =1; COMMIT;
-- 查询时直接读取 SELECT count FROM order_counts WHERE status =1; -- 执行时间:0.001秒 ✅
六、索引维护
6.1 查看索引使用情况
1 2 3 4 5 6 7 8 9 10 11 12
-- 查看表的索引 SHOW INDEX FROM users;
-- 查看索引统计信息 SELECT table_name, index_name, cardinality, index_type FROM information_schema.statistics WHERE table_schema ='your_database' AND table_name ='users';
6.2 检测未使用的索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
-- MySQL 5.7+ SELECT t.table_schema, t.table_name, s.index_name, s.cardinality FROM information_schema.tables t JOIN information_schema.statistics s ON t.table_schema = s.table_schema AND t.table_name = s.table_name LEFTJOIN performance_schema.table_io_waits_summary_by_index_usage p ON s.table_schema = p.object_schema AND s.table_name = p.object_name AND s.index_name = p.index_name WHERE t.table_schema ='your_database' AND p.index_name ISNULL AND s.index_name !='PRIMARY';
6.3 重建索引
索引碎片会影响性能,定期重建:
1 2 3 4 5 6 7 8
-- 重建单个索引 ALTER TABLE users DROP INDEX idx_name, ADD INDEX idx_name(name);
-- 优化表(重建所有索引) OPTIMIZE TABLE users;
-- 或使用 ALTER TABLE users ENGINE=InnoDB;
七、高级技巧
7.1 前缀索引
对于长字符串字段,可以只索引前几个字符:
1 2 3 4 5 6 7 8 9
-- 创建前缀索引 CREATE INDEX idx_email_prefix ON users(email(10));
-- 选择合适的前缀长度 SELECT COUNT(DISTINCTLEFT(email, 5)) /COUNT(*) AS sel_5, COUNT(DISTINCTLEFT(email, 10)) /COUNT(*) AS sel_10, COUNT(DISTINCTLEFT(email, 15)) /COUNT(*) AS sel_15 FROM users;
7.2 索引合并
MySQL可以合并多个索引:
1 2 3 4 5 6 7 8
CREATE INDEX idx_status ON orders(status); CREATE INDEX idx_amount ON orders(amount);
-- MySQL会合并两个索引 SELECT*FROM orders WHERE status =1AND amount >100;
-- 但联合索引更好 CREATE INDEX idx_status_amount ON orders(status, amount);
-- 优化:先获取ID,再关联 SELECT a.*FROM articles a JOIN ( SELECT id FROM articles WHERE category ='tech' ORDERBY created_at DESC LIMIT 100000, 20 ) t ON a.id = t.id;
八、监控与调优
8.1 开启慢查询日志
1 2 3 4 5 6 7 8 9 10 11
-- 查看配置 SHOW VARIABLES LIKE'slow_query%'; SHOW VARIABLES LIKE'long_query_time';