數據庫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種辦法
-
數據庫反范式計劃,道曲黑面,便是把表歸並,計劃成冗餘表,那大概會帶去兩個題目
- 表中存正在年夜量的反復數據項
- 表中湧現年夜量的空項,全部表格釀成一個稀少矩陣(sparse matrix)
以是,這類計劃明顯存儲效力沒有下,然則假如針對那兩種情形舉行劣化,也算是是一種沒有錯的辦理方法, MongoDB便是如許幹的
-
減緩存 把全部列表頁減上緩存. 如許 不管是持續履行1+N次查詢,照樣用inner join 1次查詢弄定,皆能夠.
這類辦法的缺陷是
- 更新緩存 須要本錢,增長瞭代碼龐雜度
- 某些場景請求數據及時性,沒法應用緩存
-
把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 joinArticle.objects.prefetch_related('category')
那是2次查詢
本文地點: /156.htm 魯塔弗本創文章,迎接轉載,請附帶本文鏈接