EXPLAIN SELECT*FROM orders WHERE status ='pending'ORDERBY created_at DESC LIMIT 20;
EXPLAIN 输出解读:
列
值
含义
type
ALL
全表扫描 ❌
type
ref / range
走索引 ✅
rows
50000
扫描行数
Extra
Using filesort
需要额外排序 ❌
Extra
Using index
索引覆盖 ✅
possible_keys
idx_status
可用的索引
key
NULL
实际没走索引 ❌
二、索引优化
最左前缀原则
1 2 3 4 5 6 7 8 9 10 11
-- 联合索引 CREATE INDEX idx_user_status_time ON orders(user_id, status, created_at);
-- 走索引 ✅ WHERE user_id =1AND status ='paid'; WHERE user_id =1; WHERE user_id =1AND created_at >'2025-01-01';
-- 不走索引 ❌ WHERE status ='paid'; -- 跳过了 user_id WHERE created_at >'2025-01-01'; -- 跳过了前两列
索引设计要点
1 2 3 4 5 6 7 8 9
-- ✅ 高区分度字段放前面 CREATE INDEX idx_email ON users(email); -- email 区分度极高
-- ✅ 覆盖索引(查询的字段都在索引中) CREATE INDEX idx_user_status ON orders(user_id, status, amount); SELECT user_id, status, amount FROM orders WHERE user_id =1; -- Using index
-- ❌ 低区分度字段不适合单独索引 -- status 只有 'pending','paid','shipped' 三种值
-- ✅ 基于游标的分页 SELECT*FROM orders WHERE id >100000ORDERBY id LIMIT 20;
-- ✅ 子查询优化 SELECT*FROM orders WHERE id >= (SELECT id FROM orders ORDERBY id LIMIT 100000, 1) ORDERBY id LIMIT 20;
案例 2:N+1 查询
1 2 3 4 5 6
// ❌ N+1:循环查数据库 List<Order> orders = orderMapper.selectList(); for (Order order : orders) { User user = userMapper.selectById(order.getUserId()); // N 次查询 order.setUserName(user.getName()); }
1 2 3 4 5 6 7 8 9
-- ✅ JOIN 一次查出 SELECT o.*, u.name as user_name FROM orders o LEFTJOIN users u ON o.user_id = u.id WHERE o.status ='paid' LIMIT 20;
-- ✅ 批量查询 SELECT*FROM users WHERE id IN (1, 2, 3, ..., 20);
案例 3:SELECT * 问题
1 2 3 4 5 6 7
-- ❌ 查询所有字段 SELECT*FROM users WHERE email ='test@test.com';
-- ✅ 只查需要的字段 SELECT id, name, email FROM users WHERE email ='test@test.com';
-- ✅ 选择合适的类型 CREATETABLE users ( id INT UNSIGNED, -- 最大 42 亿 status TINYINT, -- 0/1 created_at DATETIME -- 占用更少,支持日期函数 );
反范式优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
-- ❌ 每次查询都需要计算 SELECT o.*, (SELECTCOUNT(*) FROM order_items WHERE order_id = o.id) as item_count, (SELECTSUM(price * quantity) FROM order_items WHERE order_id = o.id) as total FROM orders o;