Django使用order_by('?')时QuerySet重复查询问题及解决
问题描述
开发Django项目时,最初用如下代码实现房间内玩家随机排序的逻辑:
players = room.player_set.all().order_by('?') # 打乱玩家顺序
由于单个房间关联的玩家数据最多只有3条,判断order_by('?')的随机排序不会给数据库带来过高性能开销,符合业务需求。但实际运行时发现:每次调用players变量都会触发一次新的数据库查询,哪怕只是执行print打印操作,players返回的排序结果都会发生变化,尝试用deepcopy方法复制对象也没能解决这个问题。
核心原因
这个现象本质是Django QuerySet的设计机制导致的:
- 赋值语句执行时,
players拿到的不是已经查询完成的结果数据集,而是一个未执行的QuerySet查询构造对象——这行代码只会把查询规则(取当前room关联的所有player、按随机规则排序)存在对象里,根本不会和数据库产生交互,这就是常说的QuerySet 懒加载特性。 - 只要你对这个QuerySet对象做求值操作(遍历、打印、转列表、切片等),Django就会实时连接数据库,执行构造好的SQL语句,返回调用当下数据库的最新结果。
- 对于普通的固定排序/无排序QuerySet,第一次求值完成后Django会把结果缓存到对象内部,后续再调用不会重复查库;但带
order_by('?')的随机排序查询,因为每次SQL执行的返回结果天然不确定,Django不会对这类查询做结果缓存,每次求值都会重新执行SQL,这也是为什么每次调用players都会得到不一样的排序结果。deepcopy复制的只是QuerySet对象本身,不是已经落地的查询结果,自然无法解决重复查询的问题。 - 这套设计的初衷是为了支持QuerySet的链式调用:你可以在赋值后继续给QuerySet追加过滤、切片、新增排序条件,直到真正需要取数据的时候才执行SQL,尽可能减少不必要的数据库查询开销,只是这个特性在需要固定数据快照的随机排序场景下会和预期不符。
正确实现方案
在查询时立刻将QuerySet求值转为内存中的Python列表,再在内存层面对列表做随机打乱,既不会重复触发数据库查询,也能拿到代码执行当下的数据快照:
import random players = list(room.player_set.all()) random.shuffle(players) # 打乱玩家顺序
在单房间最多3个玩家的场景下,这种内存打乱的方式性能比数据库层随机排序更高,完全没有额外开销。
内容的提问来源于stack exchange,提问作者Lucas Sousa
相关产品推荐
相关产品推荐

