大きなオフセットLIMIT
で mysql を実行すると、パフォーマンスの問題が発生します。SELECT
SELECT * FROM table LIMIT m, n;
たとえば、オフセットm
が 1,000,000 より大きい場合、操作は非常に遅くなります。
私は使用する必要がありますlimit m, n
; みたいなものは使えませんid > 1,000,000 limit n
。
このステートメントを最適化してパフォーマンスを向上させるにはどうすればよいですか?
大きなオフセットLIMIT
で mysql を実行すると、パフォーマンスの問題が発生します。SELECT
SELECT * FROM table LIMIT m, n;
たとえば、オフセットm
が 1,000,000 より大きい場合、操作は非常に遅くなります。
私は使用する必要がありますlimit m, n
; みたいなものは使えませんid > 1,000,000 limit n
。
このステートメントを最適化してパフォーマンスを向上させるにはどうすればよいですか?
おそらく、ターゲットテーブルのキーに関連するシーケンシャルキーを提供するインデックステーブルを作成できます。次に、このインデックステーブルをターゲットテーブルに結合し、where句を使用して、必要な行をより効率的に取得できます。
#create table to store sequences
CREATE TABLE seq (
seq_no int not null auto_increment,
id int not null,
primary key(seq_no),
unique(id)
);
#create the sequence
TRUNCATE seq;
INSERT INTO seq (id) SELECT id FROM mytable ORDER BY id;
#now get 1000 rows from offset 1000000
SELECT mytable.*
FROM mytable
INNER JOIN seq USING(id)
WHERE seq.seq_no BETWEEN 1000000 AND 1000999;
表示する行の選択をできるだけコンパクトにする方法について、インターネット上のどこかにブログ投稿があります。完全な結果を生成すると、選択した行のみに必要なすべてのデータが取得されます。
したがって、SQLは次のようになります(テストされていないため、実際にうまくいくかどうかはわかりません):
select A.* from table A
inner join (select id from table order by whatever limit m, n) B
on A.id = B.id
order by A.whatever
SQL エンジンが原始的すぎてこの種の SQL ステートメントを許可できない場合、または期待に反して何も改善されない場合は、この単一のステートメントを複数のステートメントに分割し、ID をデータ構造にキャプチャする価値があるかもしれません。
更新: 私が話していたブログ投稿を見つけました。それは、コーディング ホラーに関するJeff Atwood の「すべての抽象化は失敗した抽象化です」でした。
テーブルにすでにインデックスがある場合は、別のインデックスを作成する必要はないと思います。その場合は、この主キーで並べ替えてから、キーの値を使用して次の手順を実行できます。
SELECT * FROM myBigTable WHERE id > :OFFSET ORDER BY id ASC;
もう1つの最適化は、SELECT *を使用せず、IDのみを使用して、インデックスを読み取るだけで、すべてのデータを見つける必要がないようにすることです(IOオーバーヘッドを削減します)。他の列のいくつかが必要な場合は、おそらくこれらをインデックスに追加して、主キー(メモリに保持される可能性が高いため、ディスクルックアップを必要としない)で読み取られるようにすることができます-これは適切ではありませんがすべての場合に、あなたは遊びをしなければならないでしょう。
私はより詳細な記事を書きました:
http://www.4pmp.com/2010/02/scalable-mysql-avoid-offset-for-large-tables/
Paul Dixon の答えは確かに問題の解決策ですが、シーケンス テーブルを維持し、行のギャップがないことを確認する必要があります。
それが可能であれば、より良い解決策は、元のテーブルに行のギャップがなく、ID 1 から開始することを単純に確認することです。次に、ページネーションの ID を使用して行を取得します。
SELECT * FROM テーブル A WHERE id >= 1 AND id <= 1000;
SELECT * FROM テーブル A WHERE id >= 1001 AND id <= 2000;
等々...
私は最近この問題に遭遇しました。問題は 2 つの部分を修正する必要がありました。まず、主キーのみで制限とオフセットを行う FROM 句で内部選択を使用する必要がありました。
$subQuery = DB::raw("( SELECT id FROM titles WHERE id BETWEEN {$startId} AND {$endId} ORDER BY title ) as t");
次に、それをクエリの from 部分として使用できます。
'titles.id',
'title_eisbns_concat.eisbns_concat',
'titles.pub_symbol',
'titles.title',
'titles.subtitle',
'titles.contributor1',
'titles.publisher',
'titles.epub_date',
'titles.ebook_price',
'publisher_licenses.id as pub_license_id',
'license_types.shortname',
$coversQuery
)
->from($subQuery)
->leftJoin('titles', 't.id', '=', 'titles.id')
->leftJoin('organizations', 'organizations.symbol', '=', 'titles.pub_symbol')
->leftJoin('title_eisbns_concat', 'titles.id', '=', 'title_eisbns_concat.title_id')
->leftJoin('publisher_licenses', 'publisher_licenses.org_id', '=', 'organizations.id')
->leftJoin('license_types', 'license_types.id', '=', 'publisher_licenses.license_type_id')
このクエリを初めて作成したとき、MySql で OFFSET と LIMIT を使用しました。これは、100ページを超えるまではうまく機能し、その後オフセットが耐えられないほど遅くなり始めました。内部クエリでそれを BETWEEN に変更すると、どのページでも高速化されました。MySqlがOFFSETを高速化していない理由はわかりませんが、その間に巻き戻されているようです.