三个查询速度超过一个 - 我的加入错误了什么?

时间:2012-07-30 23:30:08

标签: mysql join

我已经设置了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    |                |
+--------------+--------------+------+-----+---------+----------------+

TStampStrings的形式为“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的分钟粒度重新组合某些Tickets :( 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分钟,最后一个查询具有唯一重要的运行时间。

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

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

最后,我最终手动重新创建了磁盘上连接的每一步。成千上万的文件每个都有一千个查询,但仍然比我的MySQL版本要快得多。但由于这个过程对于外行人来说非常具体而且无益,我接受了ypercube对Add(Partial)Indexes的回答。

2 个答案:

答案 0 :(得分: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) ;

答案 1 :(得分:-1)

尝试为第一个substring子句赋予别名并在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'));
那会有用吗?

顺便说一句。你有一个状态字段索引?