LoginSignup
5

More than 1 year has passed since last update.

djangoのDB操作で私が気にかけていること

Last updated at Posted at 2022-12-20

最近会社内のサイトリニューアルに追われる日々です。
実務で機能の一部にしろリプレースする際のDB設計は、一番脳のメモリを食いますね。
それでも大体作ってて、設計を見誤ることが多いです。

さて今回は、djangoのDB操作上、常々気をつけていることを載せまーす。
(決してアドベンドカレンダーのネタがなかったとかじゃないよ)

N+1にならないようにする。

動作が遅いと思った瞬間、N+1ではないか疑って欲しい。(indexがうまく付けれてないとかもあるけど)
djangoのadmin画面とか気をつけないと、普通にN+1になってたりするので・・・。
そのためにも絶対settingsにDBのはつけるべき。

settings.py
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とかエンドレスに聴いていられます。「あのバンド」とか青春腐ってた人間には染み渡る。
令和の「けいおん」とか言ってますけど、音楽性的には個人的には「涼宮ハルヒの憂鬱」なんだけどねー。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5