【Python/Django】クエリの発行(実行)タイミング、キャッシュについて
はじめに
Djangoのクエリはいつ発行されていつ実行されるのか気になったので公式ドキュメント等を読みながら調べました。
結果としてDjangoのクエリセットは遅延評価されるようで、実際にその値が必要になるときにデータベースに
アクセスするようです。  
以下で解説していきます。詳しく見ていきましょう。  
クエリセットについて問題
例えば以下の文があったとします。
code:shell
>> books = Book.objects.all()
>> books = books.filter(title="book1")
>> books = books.filter(insert_date = "20211108")
>> books = books:5
この文ではDBへのアクセスはいつされるでしょうか?
all()を使った時点でDBにアクセスされる?filterを使う場合は2回3回と別で追加でアクセスされる?
など迷う人もいると思います。
では実際に検証してみましょう。
django-debug-toolbarを使った検証
実際にdjango-debug-toolbarを導入してdjangosqlshellを起動し見ていきましょう。
djangosqlshellはDBにアクセスしたタイミングでSQL文を表示してくれる便利ツールです。
code:djangosqlshell
>> books = Book.objects.all()
>> books = books.filter(title="book1")
>> books = books.filter(insert_date = "20211108")
>> books = books:5
あれ何も表示されない...
そうなんです。まだこの時点ではクエリセットを構築しているだけなんです。
次は実際にクエリが発行(実行)されるタイミングを見ていきましょう。
クエリが発行(実行)されるタイミング
初めに書いた通りクエリが発行されるタイミングとしては、実際にその値が必要になるときとなっています。
以下で、実際にクエリセットを構築した変数を使用してみます。
code:djangosqlshell
>> len(books)
SELECT "book"."id",
"book"."title",
"book"."insert_date"
FROM "book"
WHERE ("book"."title" = 'book1'
AND "book"."insert_date" = '20211108')
LIMIT 5 0.86ms
SQLが実行されたことが確認できると思います。
len(books)で初めて実際の値が必要になったためDBにアクセスされたということです。
このようにDjangoのクエリセットは遅延評価されます。
つまり何かしらの処理を行うために、実際にDBから値を取り出さなければいけない場面が来たときに
初めてクエリセットが評価されDBにアクセスされます。
table:例(以下を使ったりする場合にDBにアクセスされます)
・スライス
・pickle
・repr()
・len()
・list()
・bool()
クエリセットのキャッシュついて
使用するときに毎回発行されるの?と思った人もいると思います。
毎回発行されるとするとすごく重くなりそうですよね...
しかしDjangoには構築したクエリセットのキャッシュ情報を保存する機能があります。
これを有効活用すれば無駄に毎回クエリを発行してしまうことを避けられます。
例えば以下のコードがあったとします。
code:shell
>> books = Book.object.all()
>> print(book1.title for book1 in books)
>> print(book1.insert_date for book1 in books)
こちらのコードであれば1回目のprint時にはDBにアクセスしますが、2回目は同じクエリセットを使うようになっているので
2回目は1回目のクエリセットのキャッシュが使われます。
逆に以下のコードのように書いてしまうとDBへのアクセスを2回することになり、DBへの負担も2倍になります。
code:shell
>> print(book1.title for book1 in Book.objects.all())
>> print(book1.insert_date for book1 in Book.objects.all())
クエリセットがキャッシュされない場合
またクエリセットのキャッシュがされない場合もあります。
例えばクエリセットに対してスライスやインデックス番号を使ったクエリセットの一部のみを評価してしまうときです。
スライスやインデックス番号を使った場合キャッシュの存在チェックはされるのですが、キャッシュが存在しない場合
後続のクエリ結果はキャッシュとして保存されません。
キャッシュがすでに存在した場合には、後続のクエリ結果をキャッシュできます。
code:shell
>> books = Book.objects.all()
>> len(books1:6) #キャッシュが空の為DBアクセスあり、またキャッシュは保存されない
>> len(books1:6) #キャッシュが空の為DBアクセスあり、またキャッシュは保存されない
>> len(books) #キャッシュが空の為DBアクセスあり、またキャッシュは保存される
>> len(books) #キャッシュがある為DBアクセスなし
>> len(books1:6) #キャッシュがある為DBアクセスなし
このように過去にキャッシュが一度も保存されていない状態でスライスやインデックス番号を使うと、
その結果はキャッシュされません。
まとめ
以上がクエリセットの発行(実行)タイミングとクエリのキャッシュについてです。
クエリの発行(実行)タイミング、クエリのキャッシュについてDjangoを使っていると使う機会もたくさんあるはずなので、
上手く活用していきましょう。
#Python #Django