一、何时创建索引
1.1 适合创建索引的场景
-- 1. WHERE条件列
SELECT * FROM orders WHERE user_id = 123;
CREATE INDEX idx_user_id ON orders(user_id);
-- 2. ORDER BY排序列
SELECT * FROM products ORDER BY price DESC;
CREATE INDEX idx_price ON products(price);
-- 3. GROUP BY分组列
SELECT category, COUNT(*) FROM products GROUP BY category;
CREATE INDEX idx_category ON products(category);
-- 4. JOIN连接列
SELECT * FROM orders o JOIN users u ON o.user_id = u.id;
CREATE INDEX idx_user_id ON orders(user_id);
-- 5. DISTINCT去重列
SELECT DISTINCT category FROM products;
CREATE INDEX idx_category ON products(category);
1.2 不适合创建索引的场景
-- 1. 小表(< 1000行)
-- 全表扫描更快
-- 2. 频繁更新的列
-- 维护索引代价高
-- 3. 区分度低的列(选择性 < 0.01)
SELECT * FROM users WHERE gender = '男'; -- 50%数据
-- 不如全表扫描
-- 4. 不在WHERE/ORDER BY/JOIN中使用的列
-- 纯粹浪费空间
二、选择索引列的原则
2.1 选择性高的列
-- 计算列的选择性
SELECT
COUNT(DISTINCT column) / COUNT(*) AS selectivity
FROM table_name;
-- 选择性 > 0.1:适合索引
-- 选择性 < 0.01:不适合索引
示例:
-- 身份证号:选择性 ≈ 1(唯一)✅
-- 邮箱:选择性 ≈ 0.95 ✅
-- 年龄:选择性 ≈ 0.02 ❌
-- 性别:选择性 ≈ 0.005 ❌
2.2 WHERE条件中频繁使用的列
-- 统计查询频率
SELECT
query_text,
COUNT(*) AS frequency
FROM slow_log
WHERE query_text LIKE '%WHERE user_id%'
GROUP BY query_text;
2.3 数据类型小的列
-- ✅ 好:INT类型(4字节)
CREATE INDEX idx_user_id ON orders(user_id);
-- ❌ 差:VARCHAR(500)(最大2000字节)
CREATE INDEX idx_description ON products(description); -- 太长
三、联合索引设计
3.1 最左前缀原则
-- 创建联合索引
CREATE INDEX idx_a_b_c 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 c = 3; -- 只用到a
-- 不能使用索引
WHERE b = 2;
WHERE c = 3;
WHERE b = 2 AND c = 3;
3.2 列顺序选择
规则:
- 等值条件列在前
- 范围条件列在后
- 选择性高的在前
-- ❌ 差
CREATE INDEX idx_date_user ON orders(created_at, user_id);
WHERE user_id = 123 AND created_at > '2024-01-01';
-- user_id是等值,应该在前
-- ✅ 好
CREATE INDEX idx_user_date ON orders(user_id, created_at);
WHERE user_id = 123 AND created_at > '2024-01-01';
3.3 避免冗余索引
-- ❌ 差:冗余
CREATE INDEX idx_a ON table(a);
CREATE INDEX idx_a_b ON table(a, b); -- idx_a冗余
-- ✅ 好:只保留联合索引
DROP INDEX idx_a;
CREATE INDEX idx_a_b ON table(a, b);
四、索引长度控制
4.1 前缀索引
-- 对长字符串列使用前缀索引
CREATE INDEX idx_email ON users(email(20)); -- 只索引前20个字符
-- 计算合适的前缀长度
SELECT
COUNT(DISTINCT LEFT(email, 10)) / COUNT(*) AS sel_10,
COUNT(DISTINCT LEFT(email, 20)) / COUNT(*) AS sel_20,
COUNT(DISTINCT LEFT(email, 30)) / COUNT(*) AS sel_30,
COUNT(DISTINCT email) / COUNT(*) AS sel_full
FROM users;
-- 选择选择性接近完整列的最短前缀
4.2 限制索引列数量
-- ❌ 差:太多列
CREATE INDEX idx_many ON table(a, b, c, d, e, f); -- 6列
-- ✅ 好:通常不超过3-4列
CREATE INDEX idx_few ON table(a, b, c);
五、索引命名规范
5.1 命名约定
-- 单列索引:idx_列名
CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_created_at ON orders(created_at);
-- 联合索引:idx_列1_列2_列3
CREATE INDEX idx_user_status_date ON orders(user_id, status, created_at);
-- 唯一索引:uk_列名(unique key)
CREATE UNIQUE INDEX uk_email ON users(email);
-- 全文索引:ft_列名(fulltext)
CREATE FULLTEXT INDEX ft_content ON articles(content);
5.2 见名知意
-- ❌ 差
CREATE INDEX idx1 ON users(name);
CREATE INDEX index_temp ON products(category);
-- ✅ 好
CREATE INDEX idx_name ON users(name);
CREATE INDEX idx_category ON products(category);
六、索引数量控制
6.1 单表索引数量
建议:
- 单表索引数量 < 5个
- 最多不超过10个
原因:
- 每个索引占用存储空间
- INSERT/UPDATE/DELETE需要维护所有索引
- 影响写入性能
6.2 定期审计
-- 查看表的索引数量
SELECT
TABLE_NAME,
COUNT(*) AS index_count
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'your_database'
GROUP BY TABLE_NAME
HAVING COUNT(*) > 5
ORDER BY index_count DESC;
七、索引维护
7.1 定期重建索引
-- 优化表(重建索引)
OPTIMIZE TABLE users;
-- 或重建表
ALTER TABLE users ENGINE=InnoDB;
7.2 删除未使用的索引
-- 查找未使用的索引(MySQL 8.0+)
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
AND index_name != 'PRIMARY'
ORDER BY object_schema, object_name;
八、实战案例
案例1:订单表索引设计
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(50) UNIQUE, -- 唯一索引
user_id INT NOT NULL,
status VARCHAR(20) NOT NULL,
total_amount DECIMAL(10,2),
created_at DATETIME NOT NULL,
paid_at DATETIME,
INDEX idx_user_id (user_id), -- 单列索引:用户查询
INDEX idx_status_created (status, created_at), -- 联合索引:状态+时间查询
INDEX idx_created_at (created_at) -- 单列索引:时间范围查询
) ENGINE=InnoDB;
查询场景:
-- 场景1:用户订单列表
WHERE user_id = 123; -- 使用idx_user_id
-- 场景2:待处理订单
WHERE status = 'pending' AND created_at > '2024-01-01'; -- 使用idx_status_created
-- 场景3:近7天订单
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY); -- 使用idx_created_at
案例2:商品表索引设计
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(200) NOT NULL,
category VARCHAR(50) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock INT NOT NULL,
sales INT DEFAULT 0,
created_at DATETIME,
INDEX idx_category_price (category, price), -- 分类+价格排序
INDEX idx_sales (sales), -- 销量排序
FULLTEXT INDEX ft_name (name) -- 全文搜索
) ENGINE=InnoDB;
九、总结
核心要点
- 何时创建:WHERE、ORDER BY、JOIN、GROUP BY
- 选择列:选择性高、频繁查询、数据类型小
- 联合索引:最左前缀、等值在前、避免冗余
- 长度控制:前缀索引、列数不超过3-4个
- 命名规范:idx_列名、见名知意
- 数量控制:单表 < 5个
- 定期维护:重建索引、删除未使用
记忆口诀
创建索引有原则,WHERE JOIN ORDER BY列,
选择性高频繁用,数据类型要够小。
联合索引左前缀,等值条件排在前,
冗余索引要删除,数量不超五个先。
命名规范idx开头,定期维护不能忘。
本文字数:约2,400字 难度等级:⭐⭐⭐(索引实践)