Django ORM 入坑记录

原创
2015/11/05 16:09
阅读数 272

前言

Django ORM 总的来说还是非常好用的,但是坑还是要一个一个踩。在使用 ORM 的同时使用 debug_toolbar 或者 logging 查看数据库查询语句,才能玩得溜啊。

Queryset 缓存

Django 的 查询是带有 Lazy 的,而且是有缓存的。Lazy 就在于查询只会在查询结果被调用时才开始进行,查询结果会保存在缓存中,多次访问同一查询结果里的内容不会产生新的查询。而需要注意的如下:

bids = Bidding.objects.all()
# 我发现如下打印查询结果,分别会进行进行两次数据库查询,一共进行了两次查询操作
bids[0]
bids[1]

# 但是如果我们先将 bids 转化为 list 类型,或者直接将 bids 做成 for 循环,如下:
bidlist = list(bids)
bidlist[0]
bidlist[1]
# 或者
for bid in bids:
    print bid.id
# 两者都会将 bids 查询到的两列元素打印出来,但只做了一次查询

prefetch_related 和 select_related

关系型数据库中常见的关系类型有 OneToOne, ManyToOne, ManyToMany。公司员工表中的一个员工与其在另外一张员工信息表中的关系为 OneToOne,这个员工在职位表中可能存在多个职位数据,这里关系就是 ManyToOne 的了,而 ManyToMany 则可以是卫生值日表,一个人可以同时擦窗户和擦桌子,同时扫地这件事情可能是由多个人来做的。在 ManyToMany 的关系中,Django 会自动生成一张额外的表存储对应信息。

select_related 适用于 ManyToOne 和 OneToOne,用于存在外键时,采用 Join 操作事先提取所有外键对应的信息,避免了 n+1 次查询。

prefetch_related 适用于 ManyToOne 和 ManyToMany,不用一条 SQL 语句解决问题,而是稍微进行多次查询,采用 IN 操作减少查询次数。详情可见参考资料。

prefetch_related 和 select_related 默认是不支持分片的,如下:

# #1
books = Book.objects.filter(writer__id=writer_id).prefetch_related('user', 'city', 'supplier', 'supplier__user')[start:end]
# #2
books = Book.objects.filter(writer__id=writer_id).select_related('user', 'city', 'supplier', 'supplier__user')[start:end]
# #3
books = Book.objects.filter(writer__id=writer_id)[start:end].prefetch_related('user', 'city', 'supplier', 'supplier__user')
# #4
books = Book.objects.filter(writer__id=writer_id)[start:end].select_related('user', 'city', 'supplier', 'supplier__user')

writers = Writer.objects.filter(books__in=books)

上述四种查询都会让 writer 这句代码引起 This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery 的错误,原因在于所有 books 的赋值语句都不会执行,而是由于 lazy queryset 的原因,这句查询只会作为一个子查询带入到 Writer 的查询语句里面,而 books 的子查询语句中自带了 limit,正巧子查询不支持 limit,所以就报了错。

要解决这个问题,可以先将 books 变成一个现成的结果,比如将其变成一个 book_id 的 list,然后就可以通过以下代码来取出想要的结果啦。

writers = Writer.objects.filter(books_id__in=book_id_list)

更新 update 操作

Book.objects.filter(writer__id=writer_id).update(status=Book.OUT_OF_DATE)

会产生两条数据库语句,一条查询,一条更新

Book.objects.filter(id=book_id).update(status=Book.OUT_OF_DATE)

直接产生一条更新语句,原因是前者的查询条件中有外键。

不同类型的条件取并集

不同类型的条件取并集,在带入查询时候比较蛋疼,这里用了一种比较丑陋的实现。

level_type = request.GET.get('level') or None
pay_status = request.GET.get('pay') or None
clearing_status = request.GET.get('clearing') or None
filters = Q()
if level_type:
    filters &= Q(level_type=level_type)
if pay_status:
    filters &= Q(pay_status=pay_status)
if clearing_status:
    filters &= Q(clearing_status=clearing_status)
orders = Order.objects.filter(filters)

参考资料

展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部