索引设计与优化
🔴 困难题目描述
给定以下查询,应该如何设计索引?
-- 查询 1
SELECT * FROM orders WHERE user_id = 1 AND status = 1;
-- 查询 2
SELECT * FROM orders WHERE user_id = 1 ORDER BY created_at DESC;
-- 查询 3
SELECT * FROM orders WHERE status = 1 AND created_at >= '2024-01-01';
-- 查询 4
SELECT user_id, COUNT(*) FROM orders WHERE status = 1 GROUP BY user_id;
-- 查询 5
SELECT * FROM orders WHERE user_id IN (1, 2, 3) AND status IN (1, 2);表结构
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT,
product_id BIGINT,
status INT,
amount DECIMAL(10,2),
created_at DATETIME,
updated_at DATETIME
) ENGINE=InnoDB;提示
- 考虑查询频率
- 考虑区分度
- 考虑最左前缀
- 避免冗余索引
解法
参考答案 (3 个标签)
索引设计 联合索引 查询优化
分析查询模式
查询 1:等值查询
SELECT * FROM orders WHERE user_id = 1 AND status = 1;
-- 索引方案 1:联合索引
CREATE INDEX idx_user_status ON orders(user_id, status);
-- 分析:
-- 1. user_id 区分度高(假设 100万用户)
-- 2. status 区分度低(只有 5 个状态)
-- 3. 联合索引 (user_id, status) 效率高
-- 索引方案 2:两个独立索引
CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_status ON orders(status);
-- 分析:
-- 1. MySQL 可能只使用一个索引(user_id)
-- 2. status 需要通过 Using where 过滤
-- 3. 不如联合索引高效最优方案:
CREATE INDEX idx_user_status ON orders(user_id, status);查询 2:排序查询
SELECT * FROM orders WHERE user_id = 1 ORDER BY created_at DESC;
-- 索引方案
CREATE INDEX idx_user_created ON orders(user_id, created_at DESC);
-- 分析:
-- 1. user_id 用于过滤
-- 2. created_at 用于排序(避免 filesort)
-- 3. Extra: Using where, Using index (如果是覆盖索引)最优方案:
CREATE INDEX idx_user_created ON orders(user_id, created_at DESC);查询 3:范围查询
SELECT * FROM orders WHERE status = 1 AND created_at >= '2024-01-01';
-- 索引方案
CREATE INDEX idx_status_created ON orders(status, created_at);
-- 分析:
-- 1. status 用于过滤
-- 2. created_at 用于范围查询
-- 3. 注意:created_at >= 之后的部分不能使用索引最优方案:
CREATE INDEX idx_status_created ON orders(status, created_at);查询 4:聚合查询
SELECT user_id, COUNT(*) FROM orders WHERE status = 1 GROUP BY user_id;
-- 索引方案
CREATE INDEX idx_status_user ON orders(status, user_id);
-- 分析:
-- 1. status 用于过滤
-- 2. user_id 用于 GROUP BY(避免临时表)
-- 3. Extra: Using index (如果是覆盖索引)最优方案:
-- 如果只需要 user_id
CREATE INDEX idx_status_user ON orders(status, user_id);
-- 如果需要其他字段,考虑覆盖索引
CREATE INDEX idx_status_user_amount ON orders(status, user_id, amount);查询 5:IN 查询
SELECT * FROM orders WHERE user_id IN (1, 2, 3) AND status IN (1, 2);
-- 索引方案
CREATE INDEX idx_user_status ON orders(user_id, status);
-- 分析:
-- 1. user_id IN (1, 2, 3) 可以使用索引(多次等值查询)
-- 2. status IN (1, 2) 可以使用索引
-- 3. 联合索引 (user_id, status) 有效最优方案:
CREATE INDEX idx_user_status ON orders(user_id, status);综合索引设计
-- 方案 1:多个独立索引(不够优化)
CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_status ON orders(status);
CREATE INDEX idx_created_at ON orders(created_at);
-- 方案 2:联合索引(推荐)
CREATE INDEX idx_user_status_created ON orders(user_id, status, created_at);
-- 分析:
-- 查询 1: ✓ 使用 (user_id, status)
-- 查询 2: ✓ 使用 (user_id, created_at) - 部分使用
-- 查询 3: ✗ 不使用(user_id 在最左)
-- 查询 4: ✓ 使用 (user_id)
-- 查询 5: ✓ 使用 (user_id, status)
-- 方案 3:多个联合索引(最优)
CREATE INDEX idx_user_status ON orders(user_id, status);
CREATE INDEX idx_user_created ON orders(user_id, created_at DESC);
CREATE INDEX idx_status_created ON orders(status, created_at);
-- 分析:
-- 查询 1: ✓ 使用 idx_user_status
-- 查询 2: ✓ 使用 idx_user_created
-- 查询 3: ✓ 使用 idx_status_created
-- 查询 4: ✓ 使用 idx_user_status
-- 查询 5: ✓ 使用 idx_user_status最终推荐方案
-- 核心索引
CREATE INDEX idx_user_status ON orders(user_id, status);
CREATE INDEX idx_user_created ON orders(user_id, created_at DESC);
CREATE INDEX idx_status_created ON orders(status, created_at);
-- 覆盖索引(如果查询频繁)
CREATE INDEX idx_user_status_created_amount ON
orders(user_id, status, created_at DESC, amount);
-- 删除冗余索引
-- DROP INDEX idx_user_id; -- 被 idx_user_status 覆盖
-- DROP INDEX idx_status; -- 被 idx_status_created 覆盖索引设计原则
1. 区分度优先
-- ❌ 区分度低
CREATE INDEX idx_gender ON users(gender); -- 只有 M/F
-- ✓ 区分度高
CREATE INDEX idx_user_id ON orders(user_id); -- 假设 100万用户
-- 计算区分度
SELECT
COUNT(DISTINCT user_id) / COUNT(*) AS user_id_selectivity,
COUNT(DISTINCT status) / COUNT(*) AS status_selectivity
FROM orders;
-- user_id_selectivity: 0.9 (高)
-- status_selectivity: 0.0001 (低)2. 最左前缀原则
-- ✓ 联合索引 (a, b, c)
CREATE INDEX idx_abc ON table(a, b, c);
-- 可以使用索引:
-- WHERE a = 1
-- WHERE a = 1 AND b = 2
-- WHERE a = 1 AND b = 2 AND c = 3
-- WHERE a = 1 AND b = 2 AND c > 3
-- 不能使用索引(或部分使用):
-- WHERE b = 2
-- WHERE c = 3
-- WHERE b = 2 AND c = 3
-- WHERE a = 1 AND c = 3 (只使用 a)3. 覆盖索引
-- ✓ 覆盖索引(不回表)
CREATE INDEX idx_user_status_amount ON orders(user_id, status, amount);
SELECT user_id, status, amount FROM orders WHERE user_id = 1;
-- Extra: Using index
-- ✗ 需要回表
SELECT * FROM orders WHERE user_id = 1;
-- 需要回表获取其他字段4. 避免冗余索引
-- ❌ 冗余
CREATE INDEX idx_a ON table(a);
CREATE INDEX idx_a_b ON table(a, b);
-- ✓ 只保留联合索引
CREATE INDEX idx_a_b ON table(a, b);
-- 可以满足 WHERE a = 1 的查询5. 索引列顺序
-- 区分度高的放左边
CREATE INDEX idx_user_status ON orders(user_id, status);
-- ✓ user_id 区分度高
-- ✓ status 区分度低
-- 考虑排序需求
CREATE INDEX idx_user_created ON orders(user_id, created_at DESC);
-- ✓ 避免 filesort索引维护
查看索引使用情况
-- 查看索引基数
SELECT
table_name,
index_name,
cardinality,
seq_in_index,
column_name
FROM information_schema.statistics
WHERE table_schema = 'your_database'
AND table_name = 'orders';
-- 查看未使用的索引
SELECT
object_schema,
object_name,
index_name
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL
AND count_star = 0
ORDER BY object_schema, object_name;删除冗余索引
-- 分析冗余索引
-- 1. 查看所有索引
SHOW INDEX FROM orders;
-- 2. 识别冗余
-- idx_user (user_id)
-- idx_user_status (user_id, status)
-- idx_user_status 是超集,可以删除 idx_user
-- 3. 删除前备份
-- ALTER TABLE orders DROP INDEX idx_user;索引重建
-- 重建索引(清理碎片)
ALTER TABLE orders ENGINE=InnoDB;
-- 或
OPTIMIZE TABLE orders;
-- 分析表
ANALYZE TABLE orders;