MySQLでorderbyとlimitを組み合わせて使用すると、ページングの結果が期待と一致しません

  MySQLでは、並べ替えにorder byを使用し、ページングに制限を使用することがよくあります。最初に並べ替えてからページングする必要がある場合は、「select * from table name order by sort field limit M、N」と同様の記述を使用することがよくあります。しかし、この書き方は、より深い使用の罠を隠します。ソートフィールドでのデータ重複の場合、ソート結果が期待と矛盾する可能性があります。

1.異常現象

  たとえば、バージョン5.6.17のMySQLデータベースには、tbl_mgm_tourテーブルがあり、テーブル構造は次のとおりです。

mysql> show full columns from tbl_mgm_tour;
+---------+--------------+-----------------+------+-----+---------+-------+---------------------------------+--------------+
| Field   | Type         | Collation       | Null | Key | Default | Extra | Privileges                      | Comment      |
+---------+--------------+-----------------+------+-----+---------+-------+---------------------------------+--------------+
| tour_id | char(15)     | utf8_general_ci | NO   | PRI |         |       | select,insert,update,references | 景区编号     |
| name    | varchar(100) | utf8_general_ci | NO   |     |         |       | select,insert,update,references | 景区名称     |
| grade   | varchar(10)  | utf8_general_ci | NO   |     |         |       | select,insert,update,references | 景区等级     |
+---------+--------------+-----------------+------+-----+---------+-------+---------------------------------+--------------+
3 rows in set (0.03 sec)

  テーブルデータは次のとおりです。

mysql> select * from tbl_mgm_tour;
+---------+----------------------------------------------+-------+
| tour_id | name                                         | grade |
+---------+----------------------------------------------+-------+
| 001     | 东方明珠广播电视塔                           | 5A    |
| 002     | 上海野生动物园                               | 5A    |
| 003     | 上海科技馆                                   | 5A    |
| 005     | 上海博物馆                                   | 4A    |
| 006     | 上海佘山国家森林公园·东佘山园                | 4A    |
| 007     | 上海佘山国家森林公园·西佘山园                | 4A    |
| 008     | 上海豫园                                     | 4A    |
| 009     | 金茂大厦88层观光厅                           | 4A    |
| 056     | 上海南汇桃花村                               | 3A    |
| 057     | 大宁郁金香公园                               | 3A    |
| 058     | 东方假日田园                                 | 3A    |
| 059     | 廊下生态园                                   | 3A    |
| 060     | 中国农民画村                                 | 3A    |
+---------+----------------------------------------------+-------+
13 rows in set (0.00 sec)

  ここで、景勝地レベルに従って降順でtbl_mgm_tourテーブルをクエリし、ページごとに5つのエントリを使用してページごとにクエリを実行します。sqlステートメントは次のように簡単に記述できます。

SELECT * FROM tbl_mgm_tour ORDER BY grade DESC LIMIT 0, 5;

  クエリの実行中に、データの最初のページをクエリすると、結果は次のようになります。

mysql> SELECT * FROM tbl_mgm_tour ORDER BY grade DESC LIMIT 0, 5;
+---------+----------------------------------------------+-------+
| tour_id | name                                         | grade |
+---------+----------------------------------------------+-------+
| 001     | 东方明珠广播电视塔                           | 5A    |
| 002     | 上海野生动物园                               | 5A    |
| 003     | 上海科技馆                                   | 5A    |
| 006     | 上海佘山国家森林公园·东佘山园                | 4A    |
| 007     | 上海佘山国家森林公园·西佘山园                | 4A    |
+---------+----------------------------------------------+-------+
5 rows in set (0.00 sec)

  2ページ目のデータをクエリすると、結果は次のようになります。

mysql> SELECT * FROM tbl_mgm_tour ORDER BY grade DESC LIMIT 5, 5;
+---------+----------------------------------------------+-------+
| tour_id | name                                         | grade |
+---------+----------------------------------------------+-------+
| 007     | 上海佘山国家森林公园·西佘山园                | 4A    |
| 006     | 上海佘山国家森林公园·东佘山园                | 4A    |
| 005     | 上海博物馆                                   | 4A    |
| 060     | 中国农民画村                                 | 3A    |
| 057     | 大宁郁金香公园                               | 3A    |
+---------+----------------------------------------------+-------+
5 rows in set (0.00 sec)

   tbl_mgm_tourテーブルには13個のデータと3ページのデータがありますが、実際のクエリプロセスでは、同じデータが最初のページと2番目のページに表示されます。

2.異常分析

   どうしたの?上記のページングSQLは、最初にテーブルデータを並べ替えてから、ページングに対応するデータをフェッチしていませんか?

   上記の実際の実装結果は、現実と想像の間にしばしばギャップがあることを証明しています。実際のSQL実行は、上記の方法では実行されません。実際、MySQLはLimitを最適化します。特定の最適化方法については、公式ドキュメントを参照してください:https//dev.mysql.com/doc/refman/5.7/en/limit-optimization.html(これはバージョンの説明です) 5.7)、抽出直接関連するいくつかのポイントを以下に説明します。

  • LIMITrow_countをORDERBYと組み合わせると、MySQLは、結果全体を並べ替えるのではなく、並べ替えられた結果の最初のrow_count行が見つかるとすぐに並べ替えを停止します。インデックスを使用して順序付けを行う場合、これは非常に高速です。ファイルソートを実行する必要がある場合は、LIMIT句のないクエリに一致するすべての行が選択され、最初のrow_countが見つかる前に、それらのほとんどまたはすべてがソートされます。最初の行が見つかった後、MySQLは結果セットの残りをソートしません。

    この動作の1つの兆候は、このセクションで後述するように、LIMITがある場合とない場合のORDERBYクエリが異なる順序で行を返す場合があることです。

  上記の公式ドキュメントでは、Limitrowcountとorderbyを組み合わせると、MySQLはクエリ結果全体を並べ替えて返すのではなく、並べ替えられた行数を見つけてすぐに返すと述べています。インデックスで並べ替えると非常に高速になります。ファイルの並べ替えの場合、クエリに一致するすべての行(制限なし)が選択され、制限で必要な行数が次のようになるまで、選択した行のほとんどまたはすべてが並べ替えられます。見つかりました。limitで必要なrowcount行が見つかった場合、MySQLは結果セットの残りの行をソートしません。

  ここでは、対応するSQLの実行プランを確認します。

mysql> EXPLAIN SELECT * FROM tbl_mgm_tour ORDER BY grade DESC LIMIT 0, 5;
+----+-------------+--------------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table        | type | possible_keys | key  | key_len | ref  | rows | Extra          |
+----+-------------+--------------+------+---------------+------+---------+------+------+----------------+
|  1 | SIMPLE      | tbl_mgm_tour | ALL  | NULL          | NULL | NULL    | NULL |   13 | Using filesort |
+----+-------------+--------------+------+---------------+------+---------+------+------+----------------+
1 row in set (0.00 sec)

  ファイルソートが使用されており、テーブルにインデックスが追加されていないことを確認できます。したがって、このSQLを実行すると、limitで必要な行が検出され、すぐにクエリ結果が返されます。

  しかし、すぐに戻ったとしても、なぜページネーションが不正確なのですか?以下の指示は公式文書で行われます:

複数の行のORDERBY列の値が同じである場合、サーバーはそれらの行を任意の順序で自由に返すことができ、全体的な実行プランによって異なる場合があります。言い換えると、これらの行の並べ替え順序は、順序付けされていない列に関して非決定的です。

  フィールドによる順序に同じ値の複数の行がある場合、MySQLは、対応する実行プランに応じて、クエリ結果をランダムな順序で返します。つまり、ソートされた列が順序付けされていない場合、ソートされた結果行の順序も不確実です。

  これに基づいて、ソートするフィールドがグレードであり、同じグレード値を持つデータの行が数行しかないため、ページングが不正確である理由が基本的にわかります。実際の実行では、返された結果に対応する行の順序不確かです。上記の状況に対応して、最初のページに返される名前は「上海佘山国立森林公園・東佘山公園」であり、「上海佘山国立森林公園・西佘山公園」のデータが最初にランク付けされる可能性があります。 、上記の2つのデータ行はすぐ後ろにあるため、2番目のページが再び表示されます。

三、解決策

  この状況はどのように解決する必要がありますか?公式の解決策は次のとおりです。

LIMITの有無にかかわらず同じ行の順序を確保することが重要な場合は、ORDER BY句に追加の列を含めて、順序を確定的にします。たとえば、id値が一意である場合、次のように並べ替えることで、特定のカテゴリ値の行をid順に表示できます。

  Limitの有無にかかわらず同じソート結果を保証したい場合は、ソート条件を追加できます。たとえば、idフィールドが一意である場合、順序が安定していることを確認するために、ソートフィールドにIDソートを追加することを検討できます。

  したがって、上記の場合、tbl_mgm_tourテーブルの主キーのtour_idフィールドなど、SQLに別の並べ替えフィールドを追加して、ページングの問題を解決できます。変更されたSQLは次のとおりです。

mysql> SELECT * FROM tbl_mgm_tour ORDER BY grade DESC, tour_id LIMIT 0, 5;

  もう一度テストして問題を解決してください!

4、補足説明

  異なるデータベースバージョンの同じデータの場合、並べ替えの結果は正常または異常である可能性があります。上記でテストしたデータベースのバージョンは5.6.17です。5.7.29バージョンのデータベースでテストした場合、並べ替えの結果は正常です。

参考記事:

おすすめ

転載: blog.csdn.net/piaoranyuji/article/details/113883210