數據庫-django ORM若何處置N+1查詢

數據庫N+1查詢是個常睹的題目,簡略描寫場景以下

根本場景

class Category(models.Model):
name = models.CharField(max_length=30)

class Article(models.Model):
title = models.CharField(max_length=30)
body = models.TextField()
category = models.ForeignKey(Category)
time = models.DateTimeField()

#----列表頁模板{% for a in Article.objects.all %}
{{ a.title }}
{{ a.category.name }}
{% endfor %}

正在天生列表頁裡時,起首履行一次

select * from article limited 0,N

然後逐條獵取category.name,又須要履行N次

select name from category where id = category_id

以是N+1題目實在應當叫做1+N 題目,那隻是一個數據庫計劃形式的題目.然則會對數據庫帶去很年夜的壓力,一個簡略的列表頁大概會有幾百次數據庫查詢

N+1題目其實不是ORM獨占,隻是應用orm的時刻,數據庫表中的止釀成一個工具,因而很天然的便輕易應用上裡的辦法去舉行查詢沒有應用orm舉行編程的情形,一樣平常間接用子查詢大概inner join

select a.*,c.name from article a,category b where a.category_id = b.id

子查詢大概inner join對數據庫來講,也是很費資本的操縱,由於須要鎖表,下並收的情形下很輕易鎖逝世

要辦理1+N題目一樣平常有3種辦法

  1. 數據庫反范式計劃,道曲黑面,便是把表歸並,計劃成冗餘表,那大概會帶去兩個題目

    1. 表中存正在年夜量的反復數據項
    2. 表中湧現年夜量的空項,全部表格釀成一個稀少矩陣(sparse matrix)

    以是,這類計劃明顯存儲效力沒有下,然則假如針對那兩種情形舉行劣化,也算是是一種沒有錯的辦理方法, MongoDB便是如許幹的

  2. 減緩存 把全部列表頁減上緩存. 如許 不管是持續履行1+N次查詢,照樣用inner join 1次查詢弄定,皆能夠.

    這類辦法的缺陷是

    1. 更新緩存 須要本錢,增長瞭代碼龐雜度
    2. 某些場景請求數據及時性,沒法應用緩存
  3. 把N+1次查詢釀成2次查詢

    簡略道 先履行 select *,category_id from article limited 0,N

    然後遍歷成果列表,掏出全部的category_id,往失落反復項

    再履行一次 select name from category where id in (category id list)

機能劣化

把子查詢/join查詢 分紅兩次,是 下並收網站數據庫調劣中異常有用的常睹做法,固然會消費更多的cpu時光,然則幸免瞭體系的逝世鎖,進步瞭並收相應才能

數據庫自己處置沒有瞭下並收,由於我們隻能包管單個數據項的操縱是本子的,而數據庫的查詢是以 列表為根本單位,那是個自然抵觸,無解

數據庫計劃范式沒有正在web framework才能規模內,以是django的ORM 隻支撐背面兩種做法

  • Article.ojbects.select_related() 那便是inner join
  • Article.objects.prefetch_related('category') 那是2次查詢

本文地點: /156.htm 魯塔弗本創文章,迎接轉載,請附帶本文鏈接