MYSQLのより高いLIMITオフセットがクエリの速度を低下させるのはなぜですか?


173

簡単なシナリオ:1600万件を超えるレコード[サイズは2GB]のテーブル。SELECTでのLIMITオフセットが高いほど、ORDER BY * primary_key *を使用するとクエリが遅くなります

そう

SELECT * FROM large ORDER BY `id`  LIMIT 0, 30 

はるかに少ない

SELECT * FROM large ORDER BY `id` LIMIT 10000, 30 

いずれにしても、注文するレコードは30件だけで、同じです。したがって、ORDER BYによるオーバーヘッドではありません。
現在、最新の30行をフェッチする場合、約180秒かかります。その単純なクエリをどのように最適化できますか?


注:私は著者です。上記の場合、MySQLはインデックス(PRIMARY)を参照しません。説明については、ユーザー「Quassnoi」による以下のリンクを参照してください。
ラーマン

回答:


197

クエリは最初のOFFSET + LIMITレコードを数える必要があるため(そしてLIMITそれらのみを使用する必要があるため)、オフセットを大きくするとクエリの速度が低下するのが普通です。この値が大きいほど、クエリの実行時間が長くなります。

OFFSET最初にレコードが異なる長さになる可能性があるため、クエリを正しく実行できません。次に、削除されたレコードからギャップが生じる可能性があります。途中で各レコードをチェックしてカウントする必要があります。

それidがテーブルPRIMARY KEYであると仮定するとMyISAM、次のトリックを使用してスピードを上げることができます:

SELECT  t.*
FROM    (
        SELECT  id
        FROM    mytable
        ORDER BY
                id
        LIMIT 10000, 30
        ) q
JOIN    mytable t
ON      t.id = q.id

この記事を参照してください:


7
MySQLの「初期行ルックアップ」動作が、これが長すぎる理由の答えです。あなたが提供したトリックにより、(インデックスによって直接)一致したIDのみがバインドされ、あまりにも多くのレコードの不要な行ルックアップが節約されます。これでうまくいきました。
ラーマン

4
@harald:「動作しない」とはどういう意味ですか?これは純粋なパフォーマンスの向上です。使用可能なインデックスがないORDER BYか、インデックスが必要なすべてのフィールドをカバーしている場合、この回避策は必要ありません。
Quassnoi、

6
@ f055:答えは「スピードを上げる」であり、「インスタントにする」ではありません。答えの最初の文を読みましたか?
Quassnoi

3
InnoDBでこのようなものを実行することは可能ですか?
NeverEndingQueue 2015年

3
@Lanti:別の質問として投稿し、でタグ付けすることを忘れないでくださいpostgresql。これはMySQL固有の回答です。
Quassnoi

220

私もまったく同じ問題を抱えていました。このデータを大量に収集する必要があり、30の特定のセットではないという事実を考えると、おそらくループを実行してオフセットを30増やします。

したがって、代わりにできることは次のとおりです。

  1. データのセットの最後のIDを保持します(30)(たとえば、lastId = 530)
  2. 条件を追加 WHERE id > lastId limit 0,30

したがって、常にゼロのオフセットを持つことができます。パフォーマンスの向上に驚かれることでしょう。


これはギャップがある場合に機能しますか?単一の一意のキー(たとえば、複合キー)がない場合はどうなりますか?
xaisoft 2013

8
これが機能するのは、結果セットがそのキーで昇順でソートされている場合のみです(降順の場合は同じアイデアが機能しますが、> lastidを<lastidに変更します)。これが問題であるかどうかは問題ではありません。主キー、または別のフィールド(またはフィールドのグループ)
Eloff

よくやった!私の問題を解決した非常にシンプルな解決策:-)
oodavid

30
limit / offsetはページ分割された結果でよく使用され、ユーザーが常に次のページではなく、任意のページにジャンプできるため、lastIdを保持することはおそらく不可能です。言い換えると、オフセットは、連続的なパターンに従うのではなく、ページと制限に基づいて動的に計算する必要があることがよくあります。
トム


17

MySQLは、10000番目のレコード(または80000番目のバイト)に直接移動することはできません。これは、そのようにパックまたは順序付けされている(または1〜10000の連続した値がある)と想定できないためです。実際にはそうかもしれませんが、MySQLはホール/ギャップ/削除されたIDがないとは想定できません。

したがって、bobsが述べたように、MySQLはid30を返す前に、10000行をフェッチする(またはのインデックスの10000番目のエントリをトラバースする)必要があります。

編集:私のポイントを説明するために

ただし、

SELECT * FROM large ORDER BY id LIMIT 10000, 30 

だろう(ER)が遅いです

SELECT * FROM large WHERE id >  10000 ORDER BY id LIMIT 30 

だろう速い(ER) 、そして同じ結果がまったく欠けがないことを提供返していましidS(すなわち、ギャップ)。


2
これは正しいです。しかし、 "id"によって制限されているので、そのidがインデックス(主キー)内にあるときになぜそんなに時間がかかるのですか?オプティマイザーはそのインデックスを直接参照し、一致したIDを持つ行をフェッチする必要があります(そのインデックスから取得されます)
Rahman

1
idでWHERE句を使用した場合、そのマークに直接行くことができます。ただし、ID順に並べて制限を設定すると、それは最初からの相対的なカウンターに過ぎないため、全体を横切る必要があります。
Riedsio 2010

非常に良い記事のeversql.com/...
Pažout

私のために働いた@Riedsioありがとう。
mahesh kajale

8

SELECTクエリORDER BY id LIMIT X、Yを最適化する興味深い例を見つけました。3500万行あるので、行の範囲を見つけるのに2分ほどかかりました。

ここにトリックがあります:

select id, name, address, phone
FROM customers
WHERE id > 990
ORDER BY id LIMIT 1000;

取得した最後のIDのWHEREを置くだけで、パフォーマンスが大幅に向上します。私にとっては2分から1秒でした:)

ここに他の興味深いトリック:http : //www.iheavy.com/2013/06/19/3-ways-to-optimize-for-paging-in-mysql/

文字列でも動作します


1
これは、データが削除されないテーブルでのみ機能します
miro

1
@miroこれは、クエリがランダムなページでルックアップを実行できるという仮定の下で作業している場合にのみ当てはまります。これは、この投稿者が想定しているとは思えません。私はほとんどの実際のケースではこの方法が好きではありませんが、最後に取得したIDに常に基づいている限り、これはギャップで機能します。
Gremio

5

2つのクエリの時間のかかる部分は、テーブルから行を取得することです。論理的に言えば、LIMIT 0, 30バージョンでは、取得する必要があるのは30行だけです。ではLIMIT 10000, 30、バージョン、10000行が評価され、30行が返されます。データの読み取りプロセスでいくつかの最適化を行うことができますが、次の点を考慮してください。

クエリにWHERE句がある場合はどうなりますか?エンジンは、条件を満たすすべての行を返し、データを並べ替えて、最終的に30行を取得する必要があります。

行がORDER BYシーケンスで処理されない場合も考慮してください。どの行を返すかを決定するために、すべての適格行をソートする必要があります。


1
なぜそれらの10000行をフェッチするのに時間がかかるのかと思っているだけです。そのフィールドで使用されるインデックス(id、これは主キーです)は、レコード番号のPKインデックスを検索するのと同じくらい速くそれらの行を取得します。10000。これは、そのオフセットにインデックスレコード長を掛けたファイルをシークするのと同じように高速であると考えられます(つまり、10000 * 8 =バイトなし80000-8がインデックスレコード長である場合)
Rahman

@Rahman-10000行を超えて数える唯一の方法は、それらを1つずつステップオーバーすることです。これにインデックスのみが含まれる場合がありますが、それでもインデックス行はステップ実行に時間がかかります。正しく(すべての場合に)10000を「シーク」できるMyISAMまたはInnoDB構造はありません。10000* 8の提案では、(1)MyISAM、(2)固定長レコード、(3)テーブルからの削除は想定されていません。 。とにかく、MyISAMインデックスはBTreesなので、機能しません。
リックジェームス

この回答で述べたように、本当に遅いのは、インデックスをトラバースしない行ルックアップです(もちろん、これも加算されますが、ディスク上の行ルックアップほどの距離ではありません)。この問題に対して提供されている回避策クエリに基づいて、インデックスの外側の列を選択している場合、たとえそれらがorder byまたはwhere句の一部でなくても、行のルックアップが発生する傾向があると思います。これが必要な理由はわかりませんが、いくつかの回避策が役立つ理由です。
Gremio

1

比較や数値に興味がある人のために:)

実験1:データセットには約1億行が含まれています。各行には、いくつかのBIGINT、TINYINT、および約1kの文字を含む(意図的に)2つのTEXTフィールドが含まれています。

  • 青:= SELECT * FROM post ORDER BY id LIMIT {offset}, 5
  • オレンジ:= @Quassnoiのメソッド。 SELECT t.* FROM (SELECT id FROM post ORDER BY id LIMIT {offset}, 5) AS q JOIN post t ON t.id = q.id
  • もちろん、3番目のメソッドは... WHERE id>xxx LIMIT 0,5一定の時間であるため、ここには表示されません。

実験2:1つの行に3つのBIGINTしかないことを除いて、同じことです。

  • 緑:=前の青
  • 赤:=前のオレンジ

ここに画像の説明を入力してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.