19

私は具体化されたパスドリブン掲示板を持っています。次のクエリを使用してメッセージを順番に取得しています。

SELECT * FROM Board ORDER by root DESC, path ASC LIMIT 0,100

ここrootで、idはスレッドのルートメッセージであり、pathはマテリアライズドパスです。

ただし、インデックスを使用するためにこのクエリを実行するための私の努力はどれも成功しませんでした。

mysql> explain extended select path from Board order by root desc, path asc limit 100;
+-------+---------------+----------+---------+------+-------+----------+----------------------------+
| type  | possible_keys | key      | key_len | ref  | rows  | filtered | Extra
+-------+---------------+----------+---------+------+-------+----------+-----------------------------
| index | NULL          | rootpath | 261     | NULL | 21998 |   100.00 | Using index; Using filesort

現時点では、列の下のテーブルのすべての行の数が表示されていrowsます。私は疑問に思っています、その数を減らすか、他の方法でクエリを最適化する方法はありますか?

CREATE TABLE `Board` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `path` varchar(255) NOT NULL DEFAULT '0',
  `root` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `root` (`root`),
  KEY `path` (`path`),
  KEY `rootpath` (`root`,`path`)
)

クエリの主な問題はページ付けです。前のページの最後のメッセージの隣にあるメッセージから2番目のページを開始する必要があります。だから私はそれをまっすぐな方法で欲しいのです-サブレクトやものなしで。
現在の設定は、スレッドの途中から2ページ目を開始するため、あまり良くありませんが、少なくとも論理的です。

4

3 に答える 3

21

あなたが直面している問題は、この記事でうまく説明されています。そして重要な部分は次のとおりです。

最も一般的なケースは、異なる方向に2つの列で注文する場合です。…価格ASC、日付DESC LIMIT 10で注文する(価格、日付)を昇順でインデックス付けした場合、このクエリを適切に最適化することはできません–外部ソート(「ファイルソート」)が必要になります。価格ASCでインデックスを作成できる場合は、DESCの日付で、同じクエリで既に並べ替えられた順序でデータを取得できます。

また、この記事では、問題の有効な回避策についても言及しています。2番目の「order」句を逆にする:

ただし、これは「reverse_date」列のようなものを用意し、それを並べ替えに使用することで回避できるものです。MySQL 5.0では、トリガーを使用して実際の日付の更新として更新することもできるため、醜くなりません。実際、これが、たとえば、ウィキペディアのテーブル構造に「reverse_timestamp」フィールドが表示される理由です。

MySQLの公式ドキュメントからも:

場合によっては、MySQLはインデックスを使用してORDER BYを解決できませんが、WHERE句に一致する行を見つけるためにインデックスを使用します。これらのケースには、次のものが含まれます

。.....

ASCとDESCを混在させます

。SELECT*FROM t1 ORDER BY key_part1 DESC、key_part2 ASC;

提案として、ANDでインデックスが付けられているreversed_root列(reversed_root、path)を使用することをお勧めします。Integer.MAX_VALUE - root次に、次のようにクエリを実行できます。

SELECT * FROM Board ORDER by reversed_root ASC,path ASC LIMIT 0,100
于 2012-05-02T12:44:25.723 に答える
15

元のクエリ

SELECT * FROM Board ORDER by root DESC, path ASC LIMIT 0,100;

BoardDisplayOrderというルートの負の値を保持するテーブルを作成します。ここにrootinvという新しい列を追加します。

まず、サンプルデータと元のクエリを次に示します。

mysql> drop database if exists YourCommonSense;
Query OK, 2 rows affected (0.06 sec)

mysql> create database YourCommonSense;
Query OK, 1 row affected (0.00 sec)

mysql> use YourCommonSense
Database changed
mysql> CREATE TABLE `Board` (
    ->   `id` int(11) NOT NULL AUTO_INCREMENT,
    ->   `path` varchar(255) NOT NULL DEFAULT '0',
    ->   `root` int(11) NOT NULL DEFAULT '0',
    ->   PRIMARY KEY (`id`),
    ->   KEY `root` (`root`),
    ->   KEY `path` (`path`),
    ->   KEY `rootpath` (`root`,`path`)
    -> );
Query OK, 0 rows affected (0.11 sec)

mysql> INSERT INTO Board (path,root) VALUES
    -> ('Rolando Edwards',30),
    -> ('Daniel Edwards',30),
    -> ('Pamela Edwards',30),
    -> ('Dominiuqe Edwards',40),
    -> ('Diamond Edwards',40),
    -> ('Richard Washington',50),
    -> ('George Washington',50),
    -> ('Synora Washington',50);
Query OK, 8 rows affected (0.05 sec)
Records: 8  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM Board;
+----+--------------------+------+
| id | path               | root |
+----+--------------------+------+
|  2 | Daniel Edwards     |   30 |
|  3 | Pamela Edwards     |   30 |
|  1 | Rolando Edwards    |   30 |
|  5 | Diamond Edwards    |   40 |
|  4 | Dominiuqe Edwards  |   40 |
|  7 | George Washington  |   50 |
|  6 | Richard Washington |   50 |
|  8 | Synora Washington  |   50 |
+----+--------------------+------+
8 rows in set (0.00 sec)

mysql> SELECT * FROM Board ORDER by root DESC, path ASC LIMIT 0,100;
+----+--------------------+------+
| id | path               | root |
+----+--------------------+------+
|  7 | George Washington  |   50 |
|  6 | Richard Washington |   50 |
|  8 | Synora Washington  |   50 |
|  5 | Diamond Edwards    |   40 |
|  4 | Dominiuqe Edwards  |   40 |
|  2 | Daniel Edwards     |   30 |
|  3 | Pamela Edwards     |   30 |
|  1 | Rolando Edwards    |   30 |
+----+--------------------+------+
8 rows in set (0.00 sec)

mysql> EXPLAIN SELECT * FROM Board ORDER by root DESC, path ASC LIMIT 0,100;
+----+-------------+-------+-------+---------------+----------+---------+------+------+-----------------------------+
| id | select_type | table | type  | possible_keys | key      | key_len | ref  | rows | Extra                       |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-----------------------------+
|  1 | SIMPLE      | Board | index | NULL          | rootpath | 261     | NULL |    8 | Using index; Using filesort |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-----------------------------+
1 row in set (0.00 sec)

mysql>

次に、rootinvとrootinvを含むインデックスを使用してテーブルBoardDisplayOrderを作成します。

mysql> CREATE TABLE BoardDisplayOrder LIKE Board;
Query OK, 0 rows affected (0.09 sec)

mysql> ALTER TABLE BoardDisplayOrder DROP INDEX root;
Query OK, 0 rows affected (0.11 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE BoardDisplayOrder DROP INDEX path;
Query OK, 0 rows affected (0.09 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE BoardDisplayOrder DROP INDEX rootpath;
Query OK, 0 rows affected (0.08 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE BoardDisplayOrder ADD COLUMN rootinv int(11) NOT NULL;
Query OK, 0 rows affected (0.17 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE BoardDisplayOrder ADD INDEX rootpathid (rootinv,path,id,root);
Query OK, 0 rows affected (0.11 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SHOW CREATE TABLE BoardDisplayOrder \G
*************************** 1. row ***************************
       Table: BoardDisplayOrder
Create Table: CREATE TABLE `boarddisplayorder` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `path` varchar(255) NOT NULL DEFAULT '0',
  `root` int(11) NOT NULL DEFAULT '0',
  `rootinv` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `rootpathid` (`rootinv`,`path`,`id`,`root`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

mysql>

次に、BoardDisplayOrderにデータを入力します。

mysql> INSERT INTO BoardDisplayOrder (id,path,root,rootinv)
    -> SELECT id,path,root,-root FROM Board;
Query OK, 8 rows affected (0.06 sec)
Records: 8  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM BoardDisplayOrder;
+----+--------------------+------+---------+
| id | path               | root | rootinv |
+----+--------------------+------+---------+
|  7 | George Washington  |   50 |     -50 |
|  6 | Richard Washington |   50 |     -50 |
|  8 | Synora Washington  |   50 |     -50 |
|  5 | Diamond Edwards    |   40 |     -40 |
|  4 | Dominiuqe Edwards  |   40 |     -40 |
|  2 | Daniel Edwards     |   30 |     -30 |
|  3 | Pamela Edwards     |   30 |     -30 |
|  1 | Rolando Edwards    |   30 |     -30 |
+----+--------------------+------+---------+
8 rows in set (0.00 sec)

mysql>

ここで、BoardDisplayOrderに対してクエリを実行しますが、rootinvでDESCを使用しません。

mysql> SELECT id,path,root FROM BoardDisplayOrder ORDER by rootinv, path LIMIT 0,100;
+----+--------------------+------+
| id | path               | root |
+----+--------------------+------+
|  7 | George Washington  |   50 |
|  6 | Richard Washington |   50 |
|  8 | Synora Washington  |   50 |
|  5 | Diamond Edwards    |   40 |
|  4 | Dominiuqe Edwards  |   40 |
|  2 | Daniel Edwards     |   30 |
|  3 | Pamela Edwards     |   30 |
|  1 | Rolando Edwards    |   30 |
+----+--------------------+------+
8 rows in set (0.00 sec)

mysql> EXPLAIN SELECT id,path,root FROM BoardDisplayOrder ORDER by rootinv, path LIMIT 0,100;
+----+-------------+-------------------+-------+---------------+------------+---------+------+------+-------------+
| id | select_type | table             | type  | possible_keys | key        | key_len | ref  | rows | Extra       |
+----+-------------+-------------------+-------+---------------+------------+---------+------+------+-------------+
|  1 | SIMPLE      | BoardDisplayOrder | index | NULL          | rootpathid | 269     | NULL |    8 | Using index |
+----+-------------+-------------------+-------+---------------+------------+---------+------+------+-------------+
1 row in set (0.00 sec)

mysql>

試してみる!!!

警告

ルートがINTであったため、これは簡単に実行できました。

rootがVARCHARの場合、rootinvは文字のフリップフロップである必要があります。言い換えると、

  • A->Z
  • B->Y
  • ..。
  • M->N
  • N->M
  • ..。
  • Y->B
  • Z->A

これは主に、DESCを実行する必要のあるすべてのフィールドで機能します。この問題は、MySQLがインデックス内のキーをASCまたはDESCとして内部的に順序付けしないという事実に起因します。インデックス内のすべてが昇順です。そのため、でハンドラー統計SHOW GLOBAL STATUS LIKE 'handler%';を表示すると、次のように表示されます。

などなど。

現在のMySQLドキュメントによると

index_col_name指定は、ASCまたはDESCで終了できます。これらのキーワードは、昇順または降順のインデックス値ストレージを指定するための将来の拡張で許可されます。現在、それらは解析されますが無視されます。インデックス値は常に昇順で保存されます。

試してみる!!!

更新2012-05-0406:54EDT

私の答えについての@frailのコメント

ALTER TABLE BoardDisplayOrder ADD INDEX rootpathid(rootinv、path、id、root)は私にはかなり不必要に思えますが、ALTER TABLE BoardDisplayOrder ADD INDEX rootpathid(rootinv、path)で十分です。

私の解決策があった理由はALTER TABLE BoardDisplayOrder ADD INDEX rootpathid (rootinv,path,id,root)、カバーインデックスを提供することです。この場合のカバーインデックスは次のようになります。

  • 検索に必要な列を常に持っている
  • 説明計画の質が向上します。
    • クエリがデータ取得のためにテーブルから読み取ることはありません
    • クエリは、データ取得のためにインデックスからのみ読み取ります
    • インデックス範囲スキャンになります

元のクエリについて考えてみてください。

SELECT * FROM Board ORDER by root DESC, path ASC LIMIT 0,100;

これには、path、id、およびrootの3つの列を取得する必要があります。したがって、それらはインデックスに含まれている必要があります。もちろん、インデックスのサイズを大きくするとトレードオフになります。ボードテーブルが非常に大きい場合、検索を高速化できればスペースを気にしない人もいます。ルートパスインデックスがちょうど(rootinv、path)の場合、すべてのインデックス範囲スキャンには、残りの列のテーブルへのrefルックアップが伴います。これが私が選んだ理由ですALTER TABLE BoardDisplayOrder ADD INDEX rootpathid (rootinv,path,id,root);

于 2012-05-02T16:46:48.810 に答える
5

データ自体が必要な方法で取得できないこの状況では、必要な情報を含む追加の列を作成することが適切な場合があります。これにより、必要な順序で取得できるようになります。

この場合、データを保存するとデータ自体は更新されないように見えるため、特に適切です。あなたのメッセージが投稿されると、それらは更新されません(またはそれは私の最初の読書からのようです)。

この道をたどると仮定すると、私がお勧めする手順は次のとおりです。

  • テーブルに新しい列を追加しroot_pathます。
  • この更新ステートメントを実行しupdate Board set root_path = root + pathます。(既存の列のデータ型に基づいて調整する必要がある場合があります。)
  • テーブルに新しい行を追加するときはいつでも、この新しい列も追加してください。(これはトリガーで処理できますが、人々がコードの他の部分を変更しているときにトリガーが見落とされる可能性があるため、トリガーには注意が必要です。)

次に、その新しい列にインデックスを設定し、その列に対して選択を書き込むことができるはずです-必要に応じてインデックスをヒットします。

キーの1つを逆の順序で並べ替える必要がある場合でも、これは機能すると思います。

CREATE TABLE foo
(
  id serial NOT NULL,
  int_field integer DEFAULT 0,
  varchar_field character varying(255),
  composite_field character varying(255),
  CONSTRAINT foo_pkey PRIMARY KEY (id )
);

CREATE INDEX composite_field_idx ON foo (composite_field);

INSERT INTO foo (int_field, varchar_field, composite_field) VALUES 
(1,'t','t1'),
(2,'z','z2'),
(2,'w','w2'),
(4,'u','u4'),
(5,'u','u5'),
(5,'x','x5'),
(7,'v','v7');

explain select * from foo order by composite_field desc;

上記のコードを実行すると、explainステートメントに参照されているキーcomposite_field_idxが表示されます。

クエリの結果は次のとおりです。

select * from foo order by composite_field desc;

 id | int_field | varchar_field | composite_field 
----+-----------+---------------+-----------------
  2 |         2 | z             | z2
  6 |         5 | x             | x5
  3 |         2 | w             | w2
  7 |         7 | v             | v7
  5 |         5 | u             | u5
  4 |         4 | u             | u4
  1 |         1 | t             | t1
于 2012-05-02T13:23:24.660 に答える