Computer368106 views
高校倫理1440106 views
高校物理160203 views
数学講師2886227 views
小学算数1200427 views
教育149510 views
高校日本史190571 views
いろは3010814 views
中学英語811710 views
高校化学2924392 views
Help
Tools
NewsSpreadsheetCalendarSlidesTier ListPen ToolIllustrationCrayonPixel ArtASCII ArtPerspectiveEndless StairsGraphMind MapER DiagramFamily TreeMemeCurved TextImage EditorMosaicRetro FilterPencil SketchOCR/HighlighterMakeup EditorFaviconVideo TrimmerScrolling VideoVideo TitleColor PickerColor ExtractorBonfireFireworksWater RippleWater SplashBreaking GlassWood GrainMarble TextureCSS ButtonIcon MakerBar ChartGrouped Bar ChartStacked Bar ChartPie ChartLine ChartArea ChartStacked Area ChartScatter Plot3D Bar Chart3D Pie ChartBar Chart RaceBubble ChartPopulation PyramidPictogramEarningsCandlestick ChartInvestment RiskMortgage SimulatorCalculatorMatrix CalculatorFunction GraphPolynomial ExpansionVenn DiagramField VisualizerRubik's Cube Group TheoryTraveling SalesmanVoronoi and DelaunayFractalColumn ArithmeticDraw Math FiguresArithmetic AnimationArithmetic Word ProblemsCounting with Tree DiagramsCube NetsCross SectionsMotion PathMechanicsWavesLight and LensesThermodynamicsHow Semiconductors WorkMolecular StructuresAtomic OrbitalsElectrochemical CellsChemical EquilibriumCrystal LatticesBuffer pHComplex IonsDNA Double HelixCell DivisionMembrane ChannelsNerve ImpulseRock ClassificationWeatherConstellationsSolar and Lunar Eclipses3D ModelingFloor PlanSeismic StructuresIntersection TurnMaglevCooking AnimationOrigamiLive Viewer CountGeoJSON MapRailway MapPopulation MapCrime MapLand Price MapSchool MapShrine and Castle MapHouse of Representatives MapWord MapSolitaireReversiHakoiri MusumeChessHamburgerRippleSlide Puzzle MakerNeon PinballNovel MakerJapanese Typing PracticePiano Score EditorMusic Theory

English

Django ORM の N+1 問題を select_related と prefetch_related で解決する

Django ORM は便利ですが、リレーション先のデータにアクセスするたびに SQL が発行されるという落とし穴があります。これが N+1 問題です。1 回のクエリで N 件取得し、それぞれのリレーション先にアクセスすると追加で N 回のクエリが走る——合計 N+1 回のクエリが発行されることからこの名前が付いています。

N+1 問題の具体例

次のコードを見てみましょう。

# 1 回目のクエリ: 記事一覧を取得
articles = Article.objects.all()

for article in articles:
    # 記事ごとに追加クエリ: カテゴリを取得
    print(article.category.name)

記事が 100 件あれば、合計 101 回の SQL が発行されます。記事一覧の取得に 1 回、各記事のカテゴリ取得に 100 回です。開発中は気付きにくいものの、本番環境ではレスポンスの著しい低下を引き起こします。

select_related で JOIN する(多対一・一対一)

select_related は SQL の JOIN を使って、リレーション先のデータを 1 回のクエリでまとめて取得します。ForeignKey と OneToOneField に対して使えます。

# JOIN で 1 回のクエリにまとめる
articles = Article.objects.select_related('category').all()

for article in articles:
    # 追加クエリは発行されない
    print(article.category.name)

生成される SQL は次のようになります。

SELECT article.*, category.*
FROM article
INNER JOIN category ON article.category_id = category.id;

1 回のクエリで記事とカテゴリの両方を取得するため、N+1 問題は解消されます。

複数のリレーションを同時に解決したい場合は、引数を並べるだけです。

articles = Article.objects.select_related(
    'category', 'author'
).all()

リレーションが深い場合はアンダースコア 2 つで辿れます。

# article → category → parent_category の 2 段階
articles = Article.objects.select_related(
    'category__parent_category'
).all()

prefetch_related で別クエリを最適化する(多対多・逆参照)

prefetch_related は JOIN ではなく、追加のクエリを発行してから Python 側で結合します。ManyToManyField や逆方向の ForeignKey(related_name でのアクセス)に適しています。

# 記事一覧とタグを 2 回のクエリで取得
articles = Article.objects.prefetch_related('tags').all()

for article in articles:
    # 追加クエリは発行されない
    for tag in article.tags.all():
        print(tag.name)

prefetch_related は内部で 2 回のクエリを発行します。

-- 1 回目: 記事一覧
SELECT * FROM article;

-- 2 回目: 関連するタグを IN で一括取得
SELECT * FROM tag
INNER JOIN article_tags ON tag.id = article_tags.tag_id
WHERE article_tags.article_id IN (1, 2, 3, ...);

N 件の記事があっても 2 回のクエリで済むため、N+1 問題を回避できます。

select_related

JOIN で 1 回のクエリに結合する。ForeignKey と OneToOneField に使う

prefetch_related

別クエリを発行して Python 側で結合する。ManyToManyField と逆参照に使う

Prefetch オブジェクトで条件を絞る

prefetch_related でプリフェッチするデータに条件を付けたい場合は、Prefetch オブジェクトを使います。

from django.db.models import Prefetch

articles = Article.objects.prefetch_related(
    Prefetch(
        'comments',
        queryset=Comment.objects.filter(
            is_approved=True
        ).order_by('-created_at')
    )
).all()

この例では、記事ごとのコメントのうち承認済みのものだけを新しい順にプリフェッチしています。単純に prefetch_related('comments') と書くと全コメントを取得してしまうため、Prefetch で絞り込むことでデータ量を抑えられます。

select_related と prefetch_related を併用する

両者は排他的ではなく、併用できます。

articles = Article.objects.select_related(
    'category'
).prefetch_related(
    'tags'
).all()

ForeignKey のリレーション(category)は select_related で JOIN し、ManyToManyField のリレーション(tags)は prefetch_related で別クエリにする——これが最も効率的な組み合わせです。

Django Debug Toolbar で確認する

N+1 問題の厄介なところは、コードの見た目からは判別しにくい点にあります。Django Debug Toolbar を導入すると、各ページで発行された SQL の回数と内容をブラウザ上で確認でき、パフォーマンス上の問題を早期に発見できます。

pip install django-debug-toolbar

開発時に SQL の回数を常に意識する習慣をつけておくと、本番環境でのパフォーマンス問題を未然に防げます。select_relatedprefetch_related は Django ORM を使う限り避けて通れない最重要の最適化手法です。