数据库索引设计与优化指南 索引基础 什么是索引? 索引是数据库表中一列或多列值的副本,按特定顺序存储,用于加速数据检索。 类比:书籍的目录 索引的优势 快速查询:大幅减少 I/O 操作 排序加速:避免全表扫描 唯一性约束:保证数据完整性 索引的代价 存储开销:占用额外磁盘空间 写入性能:降低 INSERT/UPDATE/DELETE 速度 维护成本:需要定期重建和优化 索引类型 B-Tree 索引(最常用) 特点: 平衡树结构 适合范围查询 支持排序 适用场景: 创建索引: 哈希索引 特点: 只支持等值比较 查询速度 O(1) 不支持范围查询 适用场景: 全文索引 特点: 支持文本搜索 相关性排序 支持多语言 使用示例: 空间索引 特点: 支持地理数据 空间查询 距离计算 使用示例:
索引是数据库表中一列或多列值的副本,按特定顺序存储,用于加速数据检索。
类比:书籍的目录
特点:
适用场景:
-- 等值查询 WHERE id = 100 -- 范围查询 WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31' -- 排序 ORDER BY created_at DESC -- 前缀匹配 WHERE name LIKE 'Zhang%'
创建索引:
CREATE INDEX idx_user_email ON users(email); CREATE INDEX idx_order_created ON orders(created_at DESC);
特点:
适用场景:
-- 等值查询 WHERE user_id = 123 -- 哈希连接 WHERE a.user_id = b.id
特点:
使用示例:
-- 创建全文索引 CREATE FULLTEXT INDEX idx_article_content ON articles(content); -- 全文搜索 SELECT * FROM articles WHERE MATCH(content) AGAINST('database optimization' IN NATURAL LANGUAGE MODE);
特点:
使用示例:
-- 创建空间索引 CREATE SPATIAL INDEX idx_location ON locations(coordinates); -- 查询附近的位置 SELECT * FROM locations WHERE ST_Distance_Sphere(coordinates, POINT(116.4, 39.9)) < 1000;
CREATE INDEX idx_user_id ON orders(user_id); CREATE INDEX idx_status ON orders(status);
CREATE INDEX idx_user_status_date ON orders(user_id, status, created_at);
复合索引规则:
使用示例:
-- ✅ 可以使用索引 WHERE user_id = 1 WHERE user_id = 1 AND status = 'pending' WHERE user_id = 1 AND status = 'pending' AND created_at > '2024-01-01' -- ❌ 不能使用索引(跳过了 user_id) WHERE status = 'pending' WHERE status = 'pending' AND created_at > '2024-01-01'
定义:索引包含查询所需的所有字段,避免回表。
-- 创建覆盖索引 CREATE INDEX idx_user_cover ON users(user_id, name, email); -- 查询只使用索引,不回表 SELECT user_id, name, email FROM users WHERE user_id = 1;
优势:
-- 创建唯一索引 CREATE UNIQUE INDEX idx_user_email ON users(email); -- 唯一约束(自动创建唯一索引) ALTER TABLE users ADD CONSTRAINT uk_email UNIQUE (email);
选择性 = 不重复值数量 / 总行数
理想选择性:接近 1.0
-- 计算选择性 SELECT COUNT(DISTINCT email) / COUNT(*) AS email_selectivity, COUNT(DISTINCT gender) / COUNT(*) AS gender_selectivity FROM users; -- email_selectivity: 0.98 (高,适合索引) -- gender_selectivity: 0.02 (低,不适合索引)
适用场景:长文本字段(如 URL、TEXT)
-- 创建前缀索引(只索引前 20 个字符) CREATE INDEX idx_url_prefix ON logs(url(20)); -- 计算最优前缀长度 SELECT COUNT(DISTINCT LEFT(url, 10)) / COUNT(*) AS sel_10, COUNT(DISTINCT LEFT(url, 20)) / COUNT(*) AS sel_20, COUNT(DISTINCT LEFT(url, 30)) / COUNT(*) AS sel_30 FROM logs;
适用场景:经常对列进行函数计算
-- MySQL 8.0+ 函数索引 CREATE INDEX idx_email_lower ON users((LOWER(email))); -- 使用函数索引 SELECT * FROM users WHERE LOWER(email) = 'test@example.com';
-- PostgreSQL 表达式索引 CREATE INDEX idx_price_total ON orders((price * quantity)); -- 使用表达式索引 SELECT * FROM orders WHERE price * quantity > 1000;
EXPLAIN SELECT * FROM orders WHERE user_id = 1; -- 结果解读 -- type: ALL(全表), index(索引扫描), range(范围), ref(索引查找) -- key: 实际使用的索引 -- rows: 扫描的行数(估计) -- Extra: Using index(覆盖索引), Using filesort(文件排序)
-- ❌ 不好的做法 SELECT * FROM users WHERE user_id = 1; -- ✅ 好的做法 SELECT user_id, name, email FROM users WHERE user_id = 1;
-- ❌ 无法使用索引 WHERE YEAR(created_at) = 2024 -- ✅ 可以使用索引 WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01'
-- ✅ 可以使用索引(前缀匹配) WHERE name LIKE 'Zhang%' -- ❌ 无法使用索引(后缀/模糊匹配) WHERE name LIKE '%Zhang' WHERE name LIKE '%Zhang%' -- 解决方案:使用全文索引 CREATE FULLTEXT INDEX idx_name ON users(name); SELECT * FROM users WHERE MATCH(name) AGAINST('Zhang' IN BOOLEAN MODE);
-- MySQL 分析表 ANALYZE TABLE users; -- PostgreSQL 分析表 ANALYZE users;
-- MySQL 重建索引 OPTIMIZE TABLE users; -- PostgreSQL 重建索引 REINDEX TABLE users; REINDEX INDEX idx_user_email;
-- 查看未使用的索引(PostgreSQL) SELECT schemaname, tablename, indexname, idx_scan FROM pg_stat_user_indexes WHERE idx_scan = 0 AND indexname NOT LIKE '%_pkey';
-- 无索引 EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 1; -- 有索引 CREATE INDEX idx_user_id ON orders(user_id); EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 1;
# 使用 sysbench 进行压力测试 sysbench oltp_read_write \ --mysql-host=localhost \ --mysql-user=test \ --mysql-password=test \ --mysql-db=test \ --tables=10 \ --table-size=100000 \ run
原因:
解决方案:
问题:
最佳实践:
现象:
解决:
-- MySQL OPTIMIZE TABLE table_name; -- PostgreSQL VACUUM ANALYZE table_name;
索引策略:
监控维护:
测试验证:
权衡取舍:
记住:索引是把双刃剑,合理使用才能发挥最大价值!