bigywater.tech
Django · 115

异步 ORM 查询限制

Django 的异步 ORM 有 a 前缀的方法和 async for,看起来什么都能 await 了。这篇列出还不能做的事:懒加载、事务;并且在 PostgreSQL 上量一下「并发查询」是不是真的并发。

懒加载不行
async 里访问未取出的外键 → SynchronousOnlyOperation
没有 async 事务
async with atomic() → TypeError
≈2030 ms
10 个各 0.2 s 的 async 查询 gather 的耗时:串行执行,1 个连接

外键与反向关系

实测 · 本机执行输出
  post.author (没有 select_related) → SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_a
  select_related("author") 之后 post.author: ann
  遍历 author.post_set.all() → SynchronousOnlyOperation
  async for 遍历 author.post_set.all(): ['p1']

事务

实测 · 本机执行输出
  async 里 with transaction.atomic() → SynchronousOnlyOperation
  async with transaction.atomic() → TypeError: 'django.db.transaction.Atomic' object does not support the a
  把事务放进 sync_to_async 包起来的同步函数里 → 成功,Author 数 2

底层其实是线程

实测 · 本机执行输出
  事件循环所在线程: MainThread
  acount() / aget() 执行 SQL 的线程: ['ThreadPoolExecutor-0_0', 'ThreadPoolExecutor-0_0']

a 前缀方法本身是 async,但查询仍是同步的数据库驱动调用,被放到线程里执行。所以它的好处是「不阻塞事件循环」,不是「数据库查询并行」。

并发查询排队

实测 · 本机执行输出
  10 个 async 查询(默认 thread_sensitive)总耗时 2030 ms;此刻正在执行 pg_sleep 的连接数: 1
  10 个 thread_sensitive=False 的同步查询总耗时 227 ms;此刻正在执行 pg_sleep 的连接数: 10 (每个线程各开自己的连接)

要真正并发地跑多个数据库查询,只能用 thread_sensitive=False 把它们放到不同线程——但每个线程会自己开连接,要自己负责关闭(实验里用 connection.close()),连接数会随并发数增长。(用 pg_sleep 模拟的是「慢查询」,没有测真实业务查询。)

分步演示

异步 ORM 的边界
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。自己起的临时 PostgreSQL 16(fsync=off);并发数 10;连接数由另一个连接查 pg_stat_activity(state=active 且 query 含 pg_sleep)得出
    • 没有验证:异步驱动(如 psycopg 的 async 连接池)下的行为、CONN_MAX_AGE 与线程连接的关系、prefetch_related 在 async 中的用法、Django 更早版本
    京ICP备2026067502号-1京公网安备11010502063487号