raw()返回RawQuerySet,不支持filter()等链式调用,无法延迟加载、自动JOIN或prefetch_related,且不走查询缓存,不能与annotate()混用。
raw() 在视图里用会破坏 QuerySet 链式调用
你写 book.objects.raw("select ...") 后,返回的是 rawqueryset,它不支持 filter()、order_by()、select_related() 这些方法。一旦用了 raw(),后续所有 orm 优化手段就全失效了——比如无法延迟加载字段、不能自动 join 关联表、也不能被 prefetch_related() 补救。
常见错误现象:在视图里先 raw() 查出一堆对象,再想按用户输入动态加 filter(author__contains=...),结果抛 AttributeError: 'RawQuerySet' object has no attribute 'filter'。
-
raw()返回值是只读迭代器,不是真正的QuerySet - 它不走 Django 的查询缓存机制,每次迭代都可能重新执行 SQL(取决于数据库游标行为)
- 无法和
annotate()、aggregate()混用,哪怕只是想加个计数字段都得重写整个 SQL
connection.cursor() 在视图里裸用容易漏掉事务和连接管理
直接用 from django.db import connection + cursor.execute() 看似自由,但绕过了 Django 的事务封装。你在视图里手动开 cursor,却没显式控制 transaction.atomic(),一旦中间出错,连接可能卡住、事务未回滚、甚至留下脏数据。
更隐蔽的问题是:Django 默认每个 HTTP 请求结束时自动关闭连接,但如果你在视图里用完 cursor 没及时释放(比如忘了 with 或异常跳出了 finally),这个连接会一直占着,高并发下很快耗尽数据库连接池。
- 必须用
with connection.cursor() as cursor:包裹,否则连接泄漏风险极高 - 写操作(
INSERT/UPDATE/DELETE)必须包在transaction.atomic()里,否则失败时不会自动回滚 - MySQL 下如果语句里混用字符串和整数参数(如
"WHERE id = %s"传入"1"),可能触发静默类型转换,查不到本该命中的行
SQL 注入漏洞比想象中更容易发生
很多人以为只要用了 %s 占位符就安全,其实不然。Django 的 params 只对 cursor.execute(sql, params) 和 .raw(sql, params) 有效;一旦你拼接字符串——哪怕只拼接表名、字段名或 ORDER BY 子句——就彻底失去保护。
立即学习“Python免费学习笔记(深入)”;
典型翻车场景:前端传 sort=author DESC,后端直接写 f"ORDER BY {sort}",攻击者传 sort=author; DROP TABLE app01_book-- 就能删库。
-
params只能替换 WHERE 条件里的值,不能替换标识符(表名、列名、关键字) - MySQL 和 PostgreSQL 对标识符转义规则不同,手写
`{col}`或"{col}"容易出兼容性问题 - 用
extra()方法看似方便,但它已弃用,且内部拼接逻辑同样不防注入
ORM 其实能覆盖 95% 的“必须写原生 SQL”场景
真正需要原生 SQL 的情况其实很少:跨库 JOIN、复杂窗口函数、数据库特有语法(如 MySQL 的 GROUP_CONCAT)、或者极低延迟要求的聚合查询。其余大部分所谓“ORM 写不出来”的需求,其实是没吃透 QuerySet API。
比如想查“每个作者最新三本书”,有人立刻想写子查询+LIMIT,但其实用 Subquery + OuterRef 就能纯 ORM 实现;又比如想按拼音首字母分组,用 Func 表达式调 LEFT(UPPER(name), 1) 就行,不用写原生。
- 先试
annotate()+F()+Case/When,再考虑RawSQL -
RawSQL是比raw()更安全的折中方案:它可嵌入filter()或annotate(),仍享受 ORM 缓存和链式调用 - 真要写原生,优先封装成自定义 Manager 方法,而不是散落在视图里——这样便于单元测试和复用


















