Django - QuerySet


Lazy Loading(遅延ロード)とは?


ORMがSQLを呼び出す特徴のうち、QuerySetを呼び出す方法は실제로 필요할 때 호출한다.の特徴を有する.
たとえば、商品情報を含む「製品」(Products)テーブルを読み込む場合は、次のコードに従って作成します.
products = Product.objects.all()
かっこで終わるので、当然Productクラスを実行するときに、すべての関数が実行する値が製品に含まれていると考えられます.
ただし、「在庫」(ORM)では、単純な宣言ではSQLを呼び出すことはできません.コンテンツに関するSQLを呼び出すには、次の手順に従います.
products = Product.objects.all()
list(product)

#or

for product in products:
	print(product.name)
ドラム公式ドキュメントでは、次のようにQueryの動作状況をまとめています.
ドラム公式文書

すなわち、SQL文は、QuerySet(複数のオブジェクトを簡単に集約)を呼び出すために、スライス、Iteration(ループ可能)repr()、len()などの一定の方法で実行されなければならない.

遅延負荷の問題(効率低下,N+1の問題)


では、これらの特徴によって発生する状況は何でしょうか.コールを続けます.どういう意味なのか、上に見たコードを例に挙げてみましょう.
#Product에는 현재 2,940개의 row가 저장되어 있다.
products = Product.objects.all()

products[0].name
products[1].name
...
products[2939].name

>>>
상품1
상품2
상품3
...
...
상품2939
상품2940
上記のコードを使用して実行すると、SQL文は引き続きDBに送信されます.商品1のqeuryと商品2のqeury
もう一度仮定しようもし商品が店舗を参照したら?
class Store(models.Model):
	name = models.CharField()

class Product(models.Model):
	name  = models.CharField()
    store = models.ForeignKey(Store, on_delete=models.CASCADE)
もちろん、ショップと商品はM:N関係ですが、便宜上、商品が店を噛んでいると仮定します.
商品に対応するファミリーを見るためにORMを使って検索すると以下のように記述できます.
products = Product.objects.all()
stores = []

for product in products:
	stores.append(
    	{
        'id'    : product.id,
        'name'  : product.name.
        'store' : product.store.name
       }
     )
上記のコードを実行すると、商品のrow個数で照会されますか?違うはずです.商品の照会と店舗への照会は、少なくとも2倍の照会をもたらします.これはN + 1 problemです.
ハードウェアのパフォーマンスが低いか、データベースが耐えられない(計算量が非常に大きい)場合は、正しい値を返して中断することができず、最悪の場合、データベースが拡張される可能性があります.

Caching


キャッシュは、クエリーにロードされたデータをキャッシュに格納し、複数回の呼び出しを回避するのに役立ちます.上のコードでfor文、すなわち反復化が行われると、キャッシュはクエリ文によって呼び出された内容を格納します.
すなわち、productscacheにロードされた情報が格納される.その後、コンテンツが再実行されると、キャッシュに格納されているコンテンツは、他の呼び出しを行わずに呼び出されます.
products = Product.objects.all()

for product in products:
	product.name

#1번 호출함

print(products[0].name)
>>>
상품 1

#cache 에 저장된 내용을 호출했기에 새로운 query를 호출하지 않음
ドラムにはキャッシュを使う方法がたくさんありますが、今では内容が膨大になっているので、単独で議論しましょう.(きっと!)

Eager-Loding(即時ロード)(選択に関連、プリフェッチに関連)


インスタント・ロードは、遅延ロードとは逆の概念であり、遅延評価、実際の評価時にクエリーを発行するのではなく、すべてのものを一度にもたらす方法です.
本論文では,select_relatedprefetch_relatedという関数を用いてEager Loadingを実現した.
用法は似ているが違いがあり、違いは以下の通りである.
  • select related-JOIN(SQL文)を使用して、直ちにデータをロードします.非可逆参照
  • プリフェッチ関連-追加のクエリー・セットを使用してデータを取得し、アプリケーション(Python)内で戻りデータをマージし、逆参照することもできます.
  • select relatedを使用してN+1の問題を解決します.
    select relatedの使い方は以下の通りです.select_related(field명-변수)
    #store 필드까지 한번에 쿼리문으로 불러온다.
    products = Product.objects.all().select_related('store')
    stores = []
    
    for product in products:
    	stores.append(
        	{
            'id'    : product.id,
            'name'  : product.name.
            'store' : product.store.name
           }
         )
    上記のコードのように使用すると、クエリ文はDBに1回しか到達しません.
    select relatedではなくプリフェッチrelatedを使用してコードを記述し、逆参照関係を実行します.
    stores = Store.objects.all().prefetch_related('product_set')
    
    store = []
    
    for store in stores:
    	products = [product.name for product in stores.product_set.all()]
    	
        store.append(
        	{
            'id'       : store.id,
            'name'     : store.name,
            'products' : products
           }
         )
    prefetch relatedを使用すると、少なくとも2回のクエリがDBに渡されます.
    選択したクエリーと、コンテンツから参照に再入力するクエリーが実行されます.
    この内容で상품99というフィルターを使ったらどうなるでしょうか?
    stores = Store.objects.all().prefetch_related('product_set')
    
    store = []
    
    for store in stores:
    	all_products 	 = [product.name for product in stores.product_set.all()]
        filtered_product = [product.name for product in stores.product_set.filter(name = '상품99')
    	
        store.append(
        	{
            'id'       		   : store.id,
            'name'     		   : store.name,
            'all_products' 	   : all_products,
            'filtered_product' : filtered_product
           }
         )
    上記のコードに示すように、荘子はクエリ文を2回以上呼び出して상품99を検索する.
    以上に設定したプリフェッチ関連コマンドはproduct setであるためです.all()にしか掛けていないので.
    本明細書で設定したオブジェクトのprefetch relatedを使用して、prefetch relatedと一致しない問題を解決します.ちょっと紛らわしいですが、次のコードを見れば理解できます.
    stores = Store.objects.prefetch_related(
               Prefetch(
                 'product_set',
                 stores = Product.objects.all(),
                 to_attr='all_products'
                 ),
               Prefetch(
                 'product_set',
                 stores = Product.objects.filter(name='상품99'),
                 to_attr='filtered_product'
                 )
               )
    
    store = []
    
    for store in stores:
    	all_products 	 = [product.name for product in stores.all_products]
        filtered_product = [product.name for product in stores.filtered_product)
    	
        store.append(
        	{
            'id'               : store.id,
            'name'     	  	   : store.name,
            'all_products' 	   : all_products,
            'filtered_product' : filtered_product
           }
         )
    to_attrが任意の属性を指定していることがわかります.

    n/a.結論


    最終的な結論は、ORMを使用する場合、クエリー最適化環境を構成することです.
    つまり、キャッシュを使用して、Eager-Loadingを考慮し、フレームワークを理解します.