数据库索引优化实战:查询速度提升 100 倍
我们的订单表有 5000 万条记录,某些查询需要 30 秒才能返回。
通过索引优化,同样的查询现在只需要 0.3 秒。
问题背景
慢查询日志
SQL
-- 查询用户最近的订单
SELECT * FROM orders
WHERE user_id = 12345
ORDER BY created_at DESC
LIMIT 10;
-- 执行时间:32.5 秒
-- 扫描行数:50,000,000表结构
SQL
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
status VARCHAR(20) NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL
);
-- 5000 万条记录
-- 无索引(除了主键)索引优化过程
步骤 1:分析查询模式
SQL
-- 常见查询 1:按用户查询订单
SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC LIMIT 10;
-- 常见查询 2:按状态查询
SELECT * FROM orders WHERE status = 'pending' AND created_at > DATE_SUB(NOW(), INTERVAL 7 DAY);
-- 常见查询 3:按产品和时间查询
SELECT COUNT(*), SUM(amount) FROM orders
WHERE product_id = ? AND created_at BETWEEN ? AND ?;
-- 常见查询 4:复合条件查询
SELECT * FROM orders
WHERE user_id = ? AND status = 'completed'
ORDER BY amount DESC LIMIT 20;步骤 2:创建合适的索引
SQL
-- 索引 1:用户 + 时间(覆盖查询 1)
CREATE INDEX idx_user_created ON orders(user_id, created_at DESC);
-- 索引 2:状态 + 时间(覆盖查询 2)
CREATE INDEX idx_status_created ON orders(status, created_at);
-- 索引 3:产品 + 时间(覆盖查询 3)
CREATE INDEX idx_product_created ON orders(product_id, created_at);
-- 索引 4:用户 + 状态 + 金额(覆盖查询 4)
CREATE INDEX idx_user_status_amount ON orders(user_id, status, amount DESC);步骤 3:验证效果
SQL
-- 查询 1 优化后
EXPLAIN SELECT * FROM orders
WHERE user_id = 12345
ORDER BY created_at DESC
LIMIT 10;
-- 结果:
-- type: ref
-- possible_keys: idx_user_created
-- key: idx_user_created
-- rows: 10(之前是 50,000,000)
-- Extra: Using index condition
-- 执行时间:0.003 秒索引设计原则
原则 1:最左前缀原则
SQL
-- 索引:(a, b, c)
CREATE INDEX idx_abc ON table(a, b, c);
-- ✅ 可以使用索引
SELECT * FROM table WHERE a = 1;
SELECT * FROM table WHERE a = 1 AND b = 2;
SELECT * FROM table WHERE a = 1 AND b = 2 AND c = 3;
-- ❌ 无法使用索引
SELECT * FROM table WHERE b = 2;
SELECT * FROM table WHERE c = 3;
SELECT * FROM table WHERE b = 2 AND c = 3;原则 2:选择性高的列放前面
SQL
-- 假设:
-- user_id 有 100 万个不同值
-- status 只有 5 个不同值
-- ✅ 好的索引
CREATE INDEX idx_user_status ON orders(user_id, status);
-- ❌ 差的索引
CREATE INDEX idx_status_user ON orders(status, user_id);原则 3:覆盖索引
SQL
-- 如果查询只需要索引中的列,不需要回表
-- 创建覆盖索引
CREATE INDEX idx_user_amount ON orders(user_id, amount, created_at);
-- 这个查询只需要扫描索引
SELECT user_id, amount, created_at
FROM orders
WHERE user_id = 12345
ORDER BY created_at DESC
LIMIT 10;
-- Extra: Using index(覆盖索引)原则 4:避免索引失效
SQL
-- ❌ 索引失效的情况
-- 1. 函数操作
SELECT * FROM orders WHERE YEAR(created_at) = 2026;
-- 改为:
SELECT * FROM orders WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';
-- 2. 隐式类型转换
SELECT * FROM orders WHERE user_id = '12345'; -- user_id 是 BIGINT
-- 改为:
SELECT * FROM orders WHERE user_id = 12345;
-- 3. LIKE 以通配符开头
SELECT * FROM orders WHERE product_name LIKE '%phone%';
-- 无法使用索引,考虑全文索引
-- 4. OR 条件
SELECT * FROM orders WHERE user_id = 123 OR status = 'pending';
-- 改为 UNION:
SELECT * FROM orders WHERE user_id = 123
UNION
SELECT * FROM orders WHERE status = 'pending';高级索引技巧
技巧 1:部分索引(PostgreSQL)
SQL
-- 只索引活跃用户的订单
CREATE INDEX idx_active_orders ON orders(user_id, created_at)
WHERE status != 'archived';
-- 索引更小,查询更快技巧 2:表达式索引(PostgreSQL)
SQL
-- 索引日期的年月
CREATE INDEX idx_orders_year_month ON orders(EXTRACT(YEAR FROM created_at), EXTRACT(MONTH FROM created_at));
-- 查询时可以使用
SELECT * FROM orders
WHERE EXTRACT(YEAR FROM created_at) = 2026
AND EXTRACT(MONTH FROM created_at) = 7;技巧 3:全文索引
SQL
-- MySQL 全文索引
ALTER TABLE products ADD FULLTEXT INDEX ft_name_desc(name, description);
-- 查询
SELECT * FROM products
WHERE MATCH(name, description) AGAINST('iPhone 15' IN BOOLEAN MODE);
-- PostgreSQL 全文索引
CREATE INDEX idx_products_search ON products
USING gin(to_tsvector('english', name || ' ' || description));
-- 查询
SELECT * FROM products
WHERE to_tsvector('english', name || ' ' || description) @@ to_tsquery('iphone & 15');技巧 4:JSON 字段索引
SQL
-- MySQL JSON 索引
ALTER TABLE orders ADD INDEX idx_metadata
((CAST(metadata->>'$.category' AS CHAR(50))));
-- 查询
SELECT * FROM orders
WHERE metadata->>'$.category' = 'electronics';
-- PostgreSQL JSONB 索引
CREATE INDEX idx_orders_metadata ON orders USING gin(metadata);
-- 查询
SELECT * FROM orders
WHERE metadata @> '{"category": "electronics"}';索引维护
监控索引使用情况
SQL
-- MySQL:查看索引使用情况
SELECT * FROM sys.schema_unused_indexes
WHERE object_schema = 'mydb';
-- PostgreSQL:查看索引使用情况
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
ORDER BY idx_scan ASC;删除无用索引
SQL
-- 索引占用空间但不被使用
DROP INDEX idx_unused ON orders;定期重建索引
SQL
-- MySQL:优化表
OPTIMIZE TABLE orders;
-- PostgreSQL:重建索引
REINDEX INDEX idx_user_created;性能对比
| 查询类型 | 优化前 | 优化后 | 提升 |
|---------|--------|--------|------|
| 按用户查询 | 32.5s | 0.003s | 10833x |
| 按状态查询 | 15.2s | 0.05s | 304x |
| 按产品查询 | 8.7s | 0.02s | 435x |
| 复合查询 | 45.3s | 0.01s | 4530x |
总结
索引优化核心原则:
1. 分析查询模式:了解常见查询
2. 最左前缀:索引列顺序很重要
3. 选择性优先:高选择性列放前面
4. 覆盖索引:减少回表
5. 避免失效:注意函数、类型转换
6. 定期维护:删除无用索引
做好这些,查询速度可以提升 100 倍以上。
优化时间:2026年7月
数据规模:5000 万条记录
查询提升:100-10000 倍
#数据库 #索引优化 #性能优化 #MySQL
读者评论 2