raw()是唯一带模型映射的原生SQL安全入口,必须用%s占位符配合列表或元组参数(如[123, 'published']),禁用字符串拼接、字典传参及命名参数,否则导致注入或TypeError。

用 raw() 执行带参数的原生 SQL 查询
直接写原生 SQL 时,raw() 是最轻量、最可控的方式,但它不自动做参数绑定——手拼字符串会引发 SQL 注入。必须用 %s 占位符配合参数元组,Django 会底层转义:
Book.objects.raw('SELECT * FROM myapp_book WHERE author_id = %s AND status = %s', [123, 'published'])注意:%s 是固定写法,不是 Python 的 f-string 或 .format();PostgreSQL 用户也不能改成 %(name)s,Django 的 raw() 只认位置参数。
常见错误:传入字典、或把参数写成关键字形式,会直接报 TypeError: not all arguments converted。
在 extra() 里安全注入字段/条件逻辑
extra() 已被标记为 deprecated(Django 4.2+),但很多老项目还在用。它适合“补字段”或“加 where 片段”,比如按计算字段排序:
立即学习“Python免费学习笔记(深入)”;
Book.objects.extra(
tables=['auth_user'],
where=["myapp_book.author_id = auth_user.id"],
params={'min_rating': 4},
order_by=['-auth_user.date_joined']
)关键点:
-
params仅对where中的%s生效,不能用于tables或order_by -
tables不校验表是否存在,拼错名只会在执行时报OperationalError - 避免在
select里写子查询——不同数据库语法差异大,且 Django 不做兼容处理
用 connection.cursor() 绕过 ORM 做事务级操作
当需要执行 DDL(如 CREATE INDEX)、存储过程、或跨模型聚合且无法用 annotate() 表达时,必须用原始游标:
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("UPDATE myapp_book SET view_count = view_count + 1 WHERE id = %s", [book_id])
cursor.execute("SELECT COUNT(*) FROM myapp_book WHERE status = %s", ['draft'])
count = cursor.fetchone()[0]注意事项:
- 游标操作不在 ORM 缓存体系内,也不触发
post_save等信号 - 手动
commit通常不需要——Django 默认在请求结束时 commit,但若在atomic块外执行UPDATE,又想立刻生效,得显式调connection.commit() - 别在游标中复用变量名,例如
cursor.execute("...", [x]); x = None; cursor.execute("...", [x])会因None导致类型错误
为什么不用 django.db.models.Expression 替代原生 SQL?
多数场景下,F()、Case()、Func() 能覆盖 80% 的“复杂查询”需求,而且可移植、可测试、可组合。比如实现“按作者注册年份分组统计”:
from django.db.models import Count
from django.db.models.functions import ExtractYear
<p>Book.objects.values(year=ExtractYear('author__date_joined')).annotate(total=Count('id'))这比手写 GROUP BY EXTRACT(YEAR FROM auth_user.date_joined) 更安全——因为 ExtractYear 会根据后端自动适配 PostgreSQL 的 EXTRACT、MySQL 的 YEAR()、SQLite 的 strftime。硬写原生 SQL 意味着你主动放弃这种兼容性。
真正该上原生 SQL 的时刻,是当你发现 Func 不支持某个数据库特有函数(如 PostgreSQL 的 jsonb_path_match),或者性能压测证明 ORM 生成的 JOIN 太慢、必须强制指定 STRAIGHT_JOIN 类指令——这些都不是日常开发该先考虑的事。


















