投稿日:2019年12月3日
Djangoでデータベースを操作する際に使うQuerySetは裏でSQLを発行しています。しかし、このクエリが実行されるタイミングが少し複雑。この記事ではDjangoのQuerySetのクエリが実行されるタイミングについて、実例を交えて解説します。
Djangoでデータベースを扱う際には十中八九QuerySetを使う事になるでしょう。このQuerySetはクエリが実行されるタイミングが少し特殊です。その部分をきちんと理解することできちんと最適化されたWebアプリケーションが作成できます。
この記事ではQuerySetのクエリが実際に実行されるタイミングについて、大量のレコードを使った実例をみながら詳しく解説します。
今回はデータベースとしてDjangoのデフォルトのデータベースSQLiteを使用しています。パフォーマンスのテストをする際には本来使用するDB(おそらくMySQL、Postgresなど)を使用しなければいけませんが、この記事で試すのはあくまでどこでクエリが実行されるのか確認するだけなので、とりあえずSQLiteで良いでしょう。
ここではテストする環境を準備します。
まずはテストする対象のWebアプリケーションを準備します。プロジェクト名はsample_site、そしてblogというアプリケーションを一つ作成します。
以下の様なディレクトリ構成になります。
.
├── blog
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── auto_insert.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── manage.py
└── sample_site
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
[...]
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog',
]
[...]
サンプルとして、ありがちな構成で適当なテーブルを作ってみます。
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=123,default="no-title")
body = models.TextField(default="asfasdfasdfasdfasdfasdf,asdfasdfasfadf\r\nasdfasdfasdfasdf\r\nasdfasdfasdf\r\n")
created = models.DateField(auto_now_add=True)
edited = models.DateField(auto_now=True)
データベースに反映させます。
$ python manage.py makemigrations
$ python manage.py migrate
これでとりあえずサンプルのアプリケーションの作成は完了です。
つぎに大量のレコードの生成をしてみます。Djangoでのレコードの生成方法はいくつかありますが、大量のサンプルレコードを作成するときにはDjangoシェルのスクリプトファイルを作成してそれを実行するのが良いでしょう。
まずはスクリプトファイルを新しく作成します。
from blog.models import Post
insert_posts = []
for i in range(100000):
insert_post = Post()
insert_posts.append(insert_post)
Post.objects.bulk_create(insert_posts)
実行します。
$ python manage.py shell < blog/auto_insert_data.py
これで10万行のレコードが新しく作成できました。
公式ドキュメントによるとQuerySetのクエリは以下のタイミングで実行されます。
[ start_index : end_index ]
の様な場合にはクエリは実行されないのですが、[ stat_index : end_index : step]
のようにstepが入ったときにはここでクエリが実行されます。Djangoのshellを起動してクエリの実行タイミングについて確認してみます。
ここで本来ならコードをスクリプトファイルとして作成しpython manage.py shell < スクリプトファイル
のように実行したかったのですが、何故かtimeモジュールのimportが上手く行かず...(知っている方いたら教えてください...)。そのため、少し面倒ですが以下の手順を踏みます。
from blog.models import Post
import time
# 計測開始
start = time.time()
def print_exec_time(sign="0"):
"""
経過時間を図って表示
"""
execution_time = time.time() - start
print("{}:{}".format(sign, execution_time))
# 1:QuerySetの生成
posts = Post.objects.all()
print_exec_time("1")
# 2:QuerySetを分割
posts_splited = posts[0:100]
print_exec_time("2")
# 3:長さを取得
length_query = len(posts_splited)
print_exec_time("3")
Djangoシェルを起動して実行してみます。
$ python manage.py shell
1:0.0006344318389892578
2:0.0011267662048339844
3:2.4477956295013428
1,2までの実行時間は非常に短いのにたいして、3に到達するまでに大きく時間がかかっています。
グラフで表すと明らかですね。
QuerySetの生成や単純なSliceではクエリは実行されず、特定のメソッドでの評価のタイミングでクエリが実行されたためそこでの時間がかかっているわけですね。
The 10 Most Scariest Things About Ticktok Pornstars pornstar