最近会社内のサイトリニューアルに追われる日々です。
実務で機能の一部にしろリプレースする際のDB設計は、一番脳のメモリを食いますね。
それでも大体作ってて、設計を見誤ることが多いです。
さて今回は、djangoのDB操作上、常々気をつけていることを載せまーす。
(決してアドベンドカレンダーのネタがなかったとかじゃないよ)
N+1にならないようにする。
動作が遅いと思った瞬間、N+1ではないか疑って欲しい。(indexがうまく付けれてないとかもあるけど)
djangoのadmin画面とか気をつけないと、普通にN+1になってたりするので・・・。
そのためにも絶対settingsにDBのはつけるべき。
LOGGING = {
'version': 1,
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
}
},
'loggers': {
'django.db.backends': {
'level': 'DEBUG',
'handlers': ['console'],
"propagate": False
},
}
}
複数回テーブルが呼ばれている時は、prefetch_relatedかselect_relatedを設定すべし。
def hoge():
db.objects.select_related('parent').prefetch_related('child_set').all()
現職場において、「サーバーサイドで一番もっさりする作業はDBとの通信じゃ!」と教わった人間からすると、
日々いかにSQLの回数を減らせるかが見せどころだと思っている。
bluk_updateとbluk_createを多用しろ。
実際DBに書き込む際に複数回想定してなくても、絶対updateとcreateは一回で済ませれるように設計から考慮しろ。
ロジック的に無理な時以外は認めるべきじゃない。
from django.db.models import QuerySet
from dokano.models import Huga, Hoge
hugas:list = [] # setに変えたらユニークしてくれるかな?と思っても意外としてくれないから気をつけて
for r in range(100):
hugas.append(Huga(title=f'huga{r}'))
Huga.objects.bulk_create(hugas)
# bluk_update
hoge_set: Queryset[Hoge] = Hoge.objects.all() # ちゃんと型はつけような
for h in Hoge.objects.all():
h.active = True
Hoge.objects.bulk_update(hoge_set,fields=["active"])
exsits()とcount()は使わない。
「querysetを使え、querysetを」
この方の記事が一番参考になりました。
querysetの存在確認にexists()とcount()はどちらの方が早い?
exists()を使うよりもif querysetの方が実は早い?公式ドキュメントとは逆
もしかして以下のようなコード書いてないよね?
qs = Fuga.objects.all()
# exsitsの場合
if qs.exists():
pass
# ↓ こっちじゃない?
if qs:
pass
# return で exists()使ってる場合
return qs.exists()
# ↓ こっちじゃない?
return bool(qs)
# 個数が欲しくて、脳死でcount()した場合
return qs.count()
# ↓ こっちじゃない?
return len(qs)
ちなみに記事書いた人に習ってテストケースを書いてみたけど
def setup():
for _ in range(100):
HogeFactory.create()
def test_bool_queryset(self):
setup()
hoge_all = Hoge.objects.all()
start = time.time()
for i in range(10000):
if bool(hoge_all):
pass
process_time = time.time() - start
print('time:', process_time)
# time: 0.020615577697753906
# time: 0.013532400131225586
# time: 0.01642584800720215
def test_len_queryset(self):
setup()
hoge_all = Hoge.objects.all()
start = time.time()
for i in range(10000):
if len(hoge_all):
pass
process_time = time.time() - start
print('time:',process_time)
# time: 0.014984607696533203
# time: 0.01348114013671875
# time: 0.015105962753295898
といった感じ。これをみた後に exists()
と count()
を使うってことはないよね・・・。
最後に
みなさんのDB上、気をつけておきたいことがあれば、ぜひ教えてください。
あと、どうでもいいこと言っていい?
「ぼっち・ざ・ろっく」のギターサウンド痺れません?
OPとかエンドレスに聴いていられます。「あのバンド」とか青春腐ってた人間には染み渡る。
令和の「けいおん」とか言ってますけど、音楽性的には個人的には「涼宮ハルヒの憂鬱」なんだけどねー。