该加索引:字段用于filter/exclude/order_by/select_related且表行数>10k;高区分度字段(email/uuid)或ForeignKey(默认不建需手动加);用EXPLAIN确认是否走索引。

怎么判断 Django 模型字段该不该加数据库索引
不是所有 CharField 或 ForeignKey 都值得加索引,盲目加反而拖慢写入、占用磁盘。关键看查询模式:只要这个字段出现在 filter()、exclude()、order_by() 或 select_related() 的关联条件里,且表行数 > 10k,就该优先考虑索引。
常见错误是给 status 这类低基数字段(比如只有 "draft"/"published"/"archived")加普通 B-tree 索引——效果极差,MySQL/PostgreSQL 查询优化器大概率直接跳过。这种场景更适合 partial index(如只对 status = 'published' 建索引)或组合索引中作为后置列。
- 高区分度字段(如
email、uuid、created_at)单独建索引收益明显 -
ForeignKey字段 Django 默认不自动建索引,但几乎总是需要手动加(除非确定从不用于查询) - 用
EXPLAIN SELECT ...看执行计划里是否出现key: NULL或type: ALL,这是没走索引的明确信号
Django 中正确添加索引的三种方式
别在迁移里手写 SQL;也别依赖第三方包自动扫描——可控性差、容易漏掉业务语义。Django 原生支持足够精准:
- 模型字段级:给
ForeignKey加db_index=True(默认已是True,但显式写出来更安全),给CharField加db_index=True显式声明 - 模型 Meta 级:用
indexes = [models.Index(fields=['user', 'created_at'])]定义组合索引,注意字段顺序——等值查询字段放前,范围查询(如__gt)放后 - 迁移级:用
python manage.py makemigrations --empty手动写RunSQL,适合 partial index 或表达式索引(如Lower('email'))
示例:对用户活跃时间做高频倒序分页,indexes = [models.Index(fields=['-last_active_at'])] 比 order_by('-last_active_at') 配合无索引快一个数量级。
立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
分析现有表结构并识别缺失索引的实操路径
靠人肉翻代码和猜查询逻辑不可靠。直接从数据库和日志入手:
- 查慢查询日志:PostgreSQL 开启
log_min_duration_statement = 1000,MySQL 开启slow_query_log,抓出耗时 >1s 的SELECT,再反向定位对应 Django QuerySet - 用
django.db.connection.queries在 shell 或视图里临时打印 SQL,复制到数据库里跑EXPLAIN ANALYZE - 工具辅助:
django-silk可录请求级 SQL,pg_stat_statements(PG)或sys.schema_table_statistics(MySQL 8.0+)能统计各表扫描次数和平均耗时
注意:Django 的 count()、exists()、get_or_404() 都可能触发全表扫描,尤其当过滤条件没索引时——这些调用点最容易被忽略。
组合索引字段顺序和覆盖索引的取舍
字段顺序不是随便写的。Index(fields=['a', 'b', 'c']) 能加速 a=1、a=1, b=2、a=1, b=2, c=3,但对 b=2 或 b=2, c=3 无效。真正要覆盖查询,得让 SELECT 的字段都落在索引里(即“覆盖索引”)。
- PostgreSQL 支持
INCLUDE:如models.Index(fields=['user_id'], include=['status', 'score']),避免回表 - MySQL 不支持
INCLUDE,只能把常用查询字段全塞进fields,但注意总长度限制(InnoDB 单索引最多 767 字节) - 避免过度设计:一个表索引总数建议 ≤ 5 个,太多会显著拖慢
INSERT/UPDATE,尤其是高并发写场景
最常被忽略的是:Django 的 values_list('a', 'b') 如果 a 和 b 都在同一个索引里,就能完全避免访问数据行——这点在报表类接口里省下的 I/O 很可观。

















