26

大きなオフセットLIMITで mysql を実行すると、パフォーマンスの問題が発生します。SELECT

SELECT * FROM table LIMIT m, n;

たとえば、オフセットmが 1,000,000 より大きい場合、操作は非常に遅くなります。

私は使用する必要がありますlimit m, n; みたいなものは使えませんid > 1,000,000 limit n

このステートメントを最適化してパフォーマンスを向上させるにはどうすればよいですか?

4

6 に答える 6

13

おそらく、ターゲットテーブルのキーに関連するシーケンシャルキーを提供するインデックステーブルを作成できます。次に、このインデックステーブルをターゲットテーブルに結合し、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;
于 2009-08-07T10:09:57.717 に答える
9

表示する行の選択をできるだけコンパクトにする方法について、インターネット上のどこかにブログ投稿があります。完全な結果を生成すると、選択した行のみに必要なすべてのデータが取得されます。

したがって、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 の「すべての抽象化は失敗した抽象化です」でした。

于 2009-08-07T10:35:16.323 に答える
3

テーブルにすでにインデックスがある場合は、別のインデックスを作成する必要はないと思います。その場合は、この主キーで並べ替えてから、キーの値を使用して次の手順を実行できます。

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/

于 2011-05-31T07:59:40.547 に答える
2

Paul Dixon の答えは確かに問題の解決策ですが、シーケンス テーブルを維持し、行のギャップがないことを確認する必要があります。

それが可能であれば、より良い解決策は、元のテーブルに行のギャップがなく、ID 1 から開始することを単純に確認することです。次に、ページネーションの ID を使用して行を取得します。

SELECT * FROM テーブル A WHERE id >= 1 AND id <= 1000;
SELECT * FROM テーブル A WHERE id >= 1001 AND id <= 2000;

等々...

于 2009-12-15T23:24:48.673 に答える
0

私は最近この問題に遭遇しました。問題は 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を高速化していない理由はわかりませんが、その間に巻き戻されているようです.

于 2016-03-04T21:50:39.847 に答える