2

我已经建立了一个 JPA ManyToMany 关系,它为我提供了三个重要的表:我的 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 |       |           |
+--+------+---+--------+-------------+-----+-----+----------+-------+-----------+

这对我来说意味着对于 Ticket 中的每一行,MySQL 首先进行连接,然后由于 WHERE 子句而决定该行无效。当然运行时间很糟糕(我在 30 分钟后放弃了)。请注意,将 t.Status = 'Regroup' 移到第一个 JOIN 子句并且没有 WHERE 子句时,它不会更快。

但有趣的是,如果我分三步手动运行这个查询,做我认为优化器会做的事情,每一步几乎都会立即返回:

--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 个结果,第二个查询创建 206,598 行,第三个查询返回 9198 行。所有这些都需要大约 2 分钟才能运行,最后一个查询具有唯一重要的运行时间。

将中间结果转储到文件中很麻烦,更重要的是我想知道如何编写我的原始查询以使其合理运行。那么如何构建这个三表连接,使其运行速度尽可能快呢?

更新:我在 Status(16) 上添加了一个前缀索引,它将我的 EXPLAIN 配置文件行分别更改为 153、378 和 1(因为第一行有一个要使用的键)。我的查询的 JOIN 版本现在需要大约 6 分钟,这是可以容忍的,但仍然比手动版本慢得多。我仍然想知道为什么连接执行得非常糟糕,但可能是无法在有缺陷的 MySQL 5.1 中创建独立的子查询。如果有足够的时间过去,我会接受 Add Index 作为我的问题的解决方案,尽管它不完全是我问题的答案。

最后,我确实最终在磁盘上手动重新创建了连接的每个步骤。数以万计的文件每个都有一千个查询,这仍然比我可以让我的 MySQL 版本执行的任何操作都要快得多。但是由于该过程对于外行来说非常具体且无益,因此我接受了 ypercube 对添加(部分)索引的回答。

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'));

那行得通吗?

顺便提一句。您在状态字段上有索引吗?

于 2012-07-30T23:36:13.550 回答