默认情况下filter(name='alice')等价于__exact,即sql的=等值比较,大小写敏感性由数据库collation决定;__iexact用于跨数据库统一的大小写不敏感匹配;模糊匹配需显式使用__contains等,不可省略后缀。

filter() 里用 __exact 和默认行为到底有啥区别?
默认情况下,filter(name='alice') 就是 __exact,不是模糊匹配。很多人以为不加后缀就“可能模糊”,其实完全不会——Django 对字符串字段默认走 SQL 的 = 等值比较,大小写是否敏感取决于数据库 collation(比如 MySQL 默认 case-insensitive,PostgreSQL 默认 case-sensitive)。
所以如果你在 PostgreSQL 上查 filter(name='Alice') 查不到 alice,不是 Django 问题,是数据库行为。要强制大小写不敏感,得显式用 __iexact。
-
__exact:显式声明等值匹配,语义清晰,但多数时候可省略 -
__iexact:跨数据库统一实现大小写不敏感,比改数据库 collation 更可控 - 别依赖“不加后缀=模糊”,这是常见误解;没后缀就是
__exact
模糊匹配必须用 __contains、__icontains 还是其他?
__contains 是最常用的子串匹配,生成 SQL 的 LIKE '%value%';__icontains 是它的大小写不敏感版。但要注意:它们无法利用 B-tree 索引加速前导通配(%xxx),性能随数据量增长明显下降。
真正需要高性能模糊查时,得换方案:PostgreSQL 可用 __trigram_similar(需开启 pg_trgm 扩展)或全文检索 SearchVector;MySQL 8.0+ 可考虑全文索引 + __search(Django 4.2+ 支持)。
-
__contains:简单够用,小表没问题;大表慎用 -
__startswith/__istartswith:能走索引(B-tree),适合搜索“以…开头” -
__regex和__iregex:功能强但性能差,数据库层解析开销大,非必要不用
exclude() 的否定逻辑容易踩哪些坑?
exclude() 不是 filter() 的简单取反,尤其涉及 NULL 或多条件时。比如 exclude(status='active') 不会包含 status=None 的记录,因为 SQL 中 status != 'active' 对 NULL 返回 UNKNOWN,被当作不满足条件过滤掉了。
要真正“排除 active,保留 NULL 和其他值”,得显式补上 Q(status__isnull=True) | Q(status__in=['draft', 'archived']),或者用两次 filter:filter().exclude(status='active') 配合 filter(status__isnull=False) 拆开处理。
-
exclude(x=y)自动忽略x IS NULL的行,这不是 bug,是 SQL 三值逻辑决定的 - 多字段组合 exclude(如
exclude(Q(a=1) & Q(b=2)))等价于NOT (a=1 AND b=2),不是分别 exclude - 想表达“a≠1 或 b≠2”,得用
exclude(Q(a=1) & Q(b=2)),别误写成exclude(a=1, b=2)(效果一样,但可读性差)
空字符串、None、空白字符在查询中怎么正确判断?
Django 对空值的处理很实在:字段设为 blank=True 只影响表单校验,不影响数据库存值;null=True 才允许存 NULL。而空字符串 '' 和 NULL 在数据库里是两个东西,查询时必须区分。
比如用户昵称字段允许为空,你用 filter(nickname='') 只能查到存了空字符串的记录,查不到 NULL;要一起查,得 filter(Q(nickname='') | Q(nickname__isnull=True))。更麻烦的是,前端可能传过来带空格的字符串,得先 strip() 再存,否则 ' ' 既不是 '' 也不是 NULL。
-
__isnull=True查NULL,__exact=''查空字符串,二者不能互相替代 - 模型字段定义时,如果业务上“无昵称”和“昵称为空”没区别,建议统一存
NULL(即null=True, blank=True),避免歧义 - 查询前对用户输入做
.strip(),再判断是否为空,不然容易漏掉带空格的“假空值”
./manage.py dbshell 里直接查几条原始数据,比翻文档快得多。










