2

JPA ManyToMany 関係をセットアップしました。これにより、3 つの重要なテーブル (Ticket テーブル、Join テーブル、および Inventory テーブル) が得られます。それらは MySQL 5.1 の InnoDB テーブルです。関連するビットは次のとおりです。

Ticket:
+--------+----------+------+-----+---------+----------------+
| Field  | Type     | Null | Key | Default | Extra          |
+--------+----------+------+-----+---------+----------------+
| ID     | int(11)  | NO   | PRI | NULL    | auto_increment |
| Status | longtext | YES  |     | NULL    |                |
+--------+----------+------+-----+---------+----------------+

JoinTable:
+-------------+---------+------+-----+---------+-------+
| Field       | Type    | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+-------+
| InventoryID | int(11) | NO   | PRI | NULL    |       | Foreign Key - Inventory
| TicketID    | int(11) | NO   | PRI | NULL    |       | Foreign Key - Ticket
+-------------+---------+------+-----+---------+-------+

Inventory:
+--------------+--------------+------+-----+---------+----------------+
| Field        | Type         | Null | Key | Default | Extra          |
+--------------+--------------+------+-----+---------+----------------+
| ID           | int(11)      | NO   | PRI | NULL    | auto_increment |
| TStampString | varchar(32)  | NO   | MUL | NULL    |                |
+--------------+--------------+------+-----+---------+----------------+

TStampString の形式は「yyyy.mm.dd HH:MM:SS Z」です (たとえば、「2010.03.19 22:27:57 GMT」)。現在、作成されたすべてのチケットは、特定の時間の TStampString に直接対応しているため、次のようになりSELECT COUNT(*) FROM Ticket;ます。SELECT COUNT(DISTINCT(SUBSTRING(TStampString, 1, 13))) FROM Inventory;

私がやりたいのは、TStampString の分単位の粒度 (SUBSTRING(TStampString, 1, 16)) に基づいて特定のチケットを再グループ化することです。そこで、INSERT INTO ... SELECT ステートメントの SELECT をプロファイリングしてテストしています。

EXPLAIN SELECT SUBSTRING(i.TStampString, 1, 16) FROM Ticket t JOIN JoinTable j
ON t.ID = j.TicketID JOIN Inventory i ON j.InventoryID = i.ID WHERE t.Status
= 'Regroup' GROUP BY SUBSTRING(i.TStampString, 1, 16);

+--+------+---+--------+-------------+-----+-----+----------+-------+-----------+
|id| type |tbl| type   | psbl_keys   | key | len | ref      | rows  | Extra     |
+--+------+---+--------+-------------+-----+-----+----------+-------+-----------+
|1 | SMPL | t | ALL    | PRI         | NULL| NULL| NULL     | 35569 | where     |
|  |      |   |        |             |     |     |          |       | +temporary|
|  |      |   |        |             |     |     |          |       | +filesort |
|1 | SMPL | j | ref    | PRI,FK1,FK2 | FK2 | 4   | t.ID     |   378 | index     |
|1 | SMPL | i | eq_ref | PRI         | PRI | 4   | j.Invent |     1 |           |
|  |      |   |        |             |     |     |    oryID |       |           |
+--+------+---+--------+-------------+-----+-----+----------+-------+-----------+

これが意味することは、チケットの各行に対して、MySQL が最初に結合を行い、後で WHERE 句のために行が無効であると判断することです。確かに、ランタイムはひどいです (30 分後にあきらめました)。t.Status = 'Regroup' を最初の JOIN 句に移動し、WHERE 句を使用しないと、速度が速くならないことに注意してください。

しかし興味深いのは、このクエリを 3 つのステップで手動で実行すると、オプティマイザーが行うと思っていたことを実行すると、各ステップがほぼ即座に返されることです。

--Step 1: Select relevant Tickets (results dumped to file)
SELECT ID FROM Ticket WHERE Status = 'Regroup';

--Step 2: Get relevant Inventory entries
SELECT InventoryID FROM JoinTable WHERE TicketID IN (step 1s file);

--Step 3: Select what I wanted all along
SELECT SUBSTRING(TStampString, 1, 16) FROM Inventory WHERE ID IN (step 2s file)
GROUP BY SUBSTRING(TStampString, 1, 16);

私の特定のテーブルでは、最初のクエリは 154 の結果を返し、2 番目のクエリは 206,598 行を作成し、3 番目のクエリは 9198 行を返します。それらをすべて組み合わせると、実行に約 2 分かかり、最後のクエリだけが重要な実行時間になります。

中間結果をファイルにダンプするのは面倒です。さらに重要なことに、元のクエリを適切に実行する方法を知りたいです。では、この 3 つのテーブル結合を可能な限り高速に実行するにはどうすればよいでしょうか?

UPDATE : Status(16) にプレフィックス インデックスを追加しました。これにより、EXPLAIN プロファイル行がそれぞれ 153、378、および 1 に変更されます (最初の行には使用するキーがあるため)。私のクエリの JOIN バージョンは現在、最大 6 分かかります。これは許容範囲ですが、手動バージョンよりもかなり遅くなります。ジョインのパフォーマンスが非常に最適化されていない理由を知りたいのですが、バグのある MySQL 5.1 では独立したサブクエリを作成できない可能性があります。十分な時間が経過したら、問題の解決策として Add Index を受け入れますが、それは私の質問に対する正確な回答ではありません。

結局、ディスク上で結合のすべてのステップを手動で再作成することになりました。1,000 個のクエリを含む数万個のファイルは、私のバージョンの MySQL で実行できるどのファイルよりもはるかに高速でした。しかし、そのプロセスは非常に具体的で素人には役に立たないため、ypercube の Add (Partial) Indexes の回答を受け入れます。

4

2 に答える 2

2

クエリを高速化するためにできること:

  • に索引を追加しますStatus。タイプを に変更しなくてもVARCHAR、部分インデックスを追加できます。

    ALTER TABLE Ticket
      ADD INDEX status_idx
        Status(16) ;
    
  • Join テーブルの主キーは(InventoryID, TicketID). 別のインデックスを追加することもでき(TicketID, InventoryID)ます。これは、この特定のクエリには役に立たないかもしれませんが、他のクエリでは役に立ちます。

これがなぜ起こるかについての答えは、オプティマイザが常に最適な計画を選択するとは限らないということです。クエリのこのバリエーションを試して、EXPLAINプランがどのように異なるか、および効率が向上するかどうかを確認できます。

SELECT SUBSTRING(i.TStampString, 1, 16) 
FROM 
    ( SELECT (DISTINCT) j.InventoryID 
      FROM Ticket t 
        JOIN JoinTable j
          ON t.ID = j.TicketID 
      WHERE t.Status = 'Regroup' 
    ) AS tmp
  JOIN Inventory i 
    ON tmp.InventoryID = i.ID
GROUP BY SUBSTRING(i.TStampString, 1, 16) ;
于 2012-07-30T23:53:32.517 に答える
-1

最初の substring-clause に別名を付けて、それを group-by で使用してみてください。

SELECT SUBSTRING(i.TStampString, 1, 16) as blaa FROM Ticket t JOIN JoinTable j
ON t.ID = j.TicketID JOIN Inventory i ON j.InventoryID = i.ID WHERE t.Status
= 'Regroup' GROUP BY blaa;

あなたはそれを必要としないので、結合も完全に避けてください..

SELECT distinct(SUBSTRING(i.TStampString, 1,16)) from inventory i where i.ID in 
 ( select id from JoinTable j where j.TicketID in 
    (select id from Ticket t where t.Status = 'Regroup'));

それはうまくいくでしょうか?

ところで。Status フィールドにインデックスはありますか?

于 2012-07-30T23:36:13.550 に答える