😊

ページネーションでのパフォーマンス問題

2025/03/11に公開

Daily Blogging80日目

webアプリやスマホアプリでも良くあるページネーション
データを一括で持ってくるとパフォーマンスに影響出るから、ページングして分割して取ってくるのはよく見かける手法だと思います

このページングを実現するときに、SQLのoffsetlimitを使うことがあると思いますが、
この2つは気をつけないと急激なパフォーマンスの悪化を招くことになるんだとか...

オフセット法

offsetとlimitを使ったページネーションをオフセット法っていうらしい
この手法のパフォーマンス上の特徴は、
後方のページネーションでパフォーマンスが急激に悪化する
ということ

オフセット法だと例えばこういうクエリが実行されるはず

SELECT * FROM orders ORDER BY created_at DESC LIMIT 10 OFFSET 100000;

100,001番目〜100,010番目のレコードを取得するクエリですね

10件だけ取得するクエリか...
何が問題なんだろうか...?

問題点

取得される最終的なレコードは10件だけだけど、DB上でスキャンされるデータはそれ以上にある。
この場合、1番目〜100,000番目のレコードもスキャンされることになる
→100,001番目のレコードを特定するために、その手前までのレコードのスキャンも発生する

これだと無駄なスキャンが発生してしまい、パフォーマンスに悪影響が出る。
後方のページで急激にパフォーマンスが悪くなるのは、offsetの数が増えスキャン数が増えるのが原因

シーク法

オフセット法とはべつに、シーク法っていう手法もある。

こういうやつ

SELECT
    *
FROM
    orders
WHERE
    id < 100 # これがポイント
ORDER BY
    id DESC
LIMIT 10;

シーク法ではoffsetは使わない
その代わりに、where句で取得するデータをフィルタリングしている

※データの順番とidの数に関連性があることが前提
例えば、
id=1のレコードが一番最初の注文データであり、注文を受けた順番にレコードが追加されている場合とか

この例でいうと、
100番目の注文までを前のページネーションで取得していて、次に101番目〜110番目の注文を取得しようとしている。

where句のメリット

offsetではなくwhere句で条件を絞っていることで次のメリットがあるよ

  • indexを上手く活用してパフォーマンスを上げられる
  • ページが進んでもパフォーマンスが変わらない

データによってはこういうのもいける

SELECT
    *
FROM
    orders
WHERE
    created_at < '2025-03-11 00:00:00' # 日付でフィルタリング
ORDER BY
    created_at DESC
LIMIT 10;

Discussion