adminアプリでのselect_relatedの罠

仕事がDjango漬けなこともありいろんな点に気付いたりはまったりしてます。本日「ギャー、ヤラレター!」と思ったことがあったので紹介。

Djangoを使うと管理画面はadmin任せになることが多いと思うのだけど、ModelAdminクラスを何も考えずに書いてると、データベースへものすごい負荷がかかることがある。

どういうときにまずいのか。

ModelAdminのlist_displayにForeignKeyのフィールドが含まれていて、レコードの数が多い場合。

このときadminアプリで一覧表示をしようとすると、DBに負荷の高いSQLが投げられてしまう。list_displayがForeignKeyを含んでいる場合にはadminはselect_relatedでDBを引きに行くのが原因。ちなみに、select_relatedが使用されることはドキュメントにもしっかり書かれてる。

http://djangoproject.jp/doc/ja/1.0/ref/contrib/admin.html#list-select-related

コード例を書く。

models.py

from django.db import models
from django.contrib import admin

class M1(models.Model):
    name = models.CharField(max_length=100)

class M2(models.Model):
    m1 = models.ForeignKey(M1)

class M3(models.Model):
    m2 = models.ForeignKey(M2)

class M4(models.Model):
    m3 = models.ForeignKey(M3)

class M4Admin(admin.ModelAdmin):
    list_display = ['m3']

admin.site.register(M4, M4Admin)

これで、adminからM4の一覧表示をするとき、以下のクエリが投げられている。

SELECT `hoge_m4`.`id`, `hoge_m4`.`m3_id`, `hoge_m3`.`id`,
 `hoge_m3`.`m2_id`, `hoge_m2`.`id`, `hoge_m2`.`m1_id`,
 `hoge_m1`.`id`, `hoge_m1`.`name`
 FROM `hoge_m4`
 INNER JOIN `hoge_m3` ON (`hoge_m4`.`m3_id` = `hoge_m3`.`id`)
 INNER JOIN `hoge_m2` ON (`hoge_m3`.`m2_id` = `hoge_m2`.`id`)
 INNER JOIN `hoge_m1` ON (`hoge_m2`.`m1_id` = `hoge_m1`.`id`)
 ORDER BY `hoge_m4`.`id` DESC

さて、このとき「hoge_m1」「hoge_m2」「hoge_m3」「hoge_m4」のそれぞれテーブルが大量のレコードを持っていた場合、結合がさあ大変。DBにかなり負荷がかかっちゃう。

レコードが少ないと特に問題もないので気付きにくいと思った。以上。