数据库查询变慢主因常是缺索引或索引误用;需用explain分析执行计划,按最左前缀原则建b-tree单列或复合索引,避免冗余与过度索引,并结合orm特性手动补全关键索引。

数据库查询变慢,八成不是代码写得差,而是缺索引或索引没用对。加错索引比不加还糟——写入变慢、磁盘吃紧、查询计划反而绕路。
WHERE 字段没建索引,SELECT 再快也没用
很多 Python Web 应用(尤其是 Django/Flask + SQLAlchemy)在开发初期只顾功能,忽略 WHERE 条件字段是否被索引覆盖。比如用户登录查 email,订单列表按 user_id 过滤,但表上只有主键索引。
- 先用
EXPLAIN SELECT ...看执行计划:如果type是ALL,基本等于全表扫描 -
email字段高频等值查询,建 B-Tree 索引就够了:CREATE INDEX idx_users_email ON users(email) - 别给
TEXT或超长VARCHAR字段直接建全文索引——先确认是否真需要模糊搜索,多数场景用前缀索引更轻量:CREATE INDEX idx_logs_msg_100 ON logs(message(100)) - SQLite 用户注意:
CREATE INDEX必须在conn.execute()后调用conn.commit(),否则重启后索引丢失
复合索引的字段顺序错了,等于白建
SQL 查询里同时用到 status 和 created_at,建了 (status, created_at) 索引,但实际查询是 WHERE created_at > ? AND status = ?——这个索引大概率不会被用上。
- 牢记最左前缀原则:索引
(a, b, c)能加速WHERE a = ?、WHERE a = ? AND b = ?、WHERE a = ? AND b = ? AND c = ?,但对WHERE b = ?或WHERE c = ?无效 - 把等值查询字段放前面,范围查询字段放后面:比如排行榜常用
WHERE server_id = ? ORDER BY level DESC LIMIT 20,索引应为(server_id, level),不是(level, server_id) - SQLAlchemy 中定义复合索引:
Index('idx_server_level', Player.server_id, Player.level),别漏掉括号里的逗号顺序
索引太多,写操作卡顿,磁盘 IO 暴涨
有团队在一张日志表上建了 12 个单列索引,结果插入速度下降 70%,iostat 显示磁盘利用率长期 100%。索引不是越多越好,每个索引都会在 INSERT/UPDATE/DELETE 时额外维护。
- 删掉长期不用的索引:MySQL 可用
sys.schema_unused_indexes视图;PostgreSQL 查pg_stat_all_indexes中idx_scan为 0 的项 - 避免重复索引:
(a)和(a, b)同时存在时,(a)通常冗余 - 区分读多写少 vs 写多读少场景:游戏排行榜表可激进建索引;用户行为埋点表建议少索引+定期归档
- SQLite 尤其敏感:每个索引都占额外文件空间,且 WAL 模式下会加剧页分裂
ORM 自动生成的索引容易被忽略
Django 的 unique=True 或 db_index=True、SQLAlchemy 的 index=True 确实会自动建索引,但它们只覆盖单字段,且无法控制复合索引或表达式索引。
- Django 中手动加复合索引要写在
Meta.indexes里:models.Index(fields=['status', 'updated_at']) - SQLAlchemy 不会自动为
relationship关联字段建索引,外键列如order.user_id必须显式加索引,否则 JOIN 性能崩塌 - 别信 ORM 日志里 “SELECT …” 就代表用了索引——一定要用数据库原生命令验证执行计划
- Python 连接池配置不当也会掩盖索引问题:比如
pool_size设太小,导致连接排队,误以为是查询慢
索引优化不是一锤子买卖。上线后必须持续看慢查询日志、定期跑 EXPLAIN、监控索引使用率。最危险的不是没索引,而是以为建了索引就万事大吉,结果查询还在全表扫。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











