【问题标题】:Three Queries Faster than One -- What's Wrong with my Joins?三查询比一查询快——我的联接有什么问题?
【发布时间】:2012-07-30 23:30:07
【问题描述】:

我已经设置了一个 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”)。现在所有创建的Tickets都直接对应于某个特定时间的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 对添加(部分)索引的回答。

【问题讨论】:

  • Status 列是否真的必须是 longtext 而不是更短的类型,例如 VARCHAR(255)
  • 我刚刚在发布这个问题时注意到了这一点;我以为我已经告诉 JPA 我想要一个 VARCHAR(16),但我想它没有接受暗示。不过,不想在这么晚的时候将模式更改为生产。
  • 表是 InnoDB 还是 MyISAM?
  • 选择 distinct() 并删除组怎么样?
  • @MarkusMikkolainen - 这只是一个示例性查询。我将使用相同的表格做更多的事情,其中​​之一将涉及选择一个更独特的列,这里为了简洁起见没有提到。如果这使我的人为示例有点误导,我深表歉意。 @ypercube - 将原帖更新为 InnoDB。

标签: mysql join


【解决方案1】:

你可以做些什么来加快查询速度:

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

【讨论】:

  • 当然这会使这个特定的查询运行得更快,但我更关心的是为什么天真地按顺序执行查询比应该是更清洁的形式表现得更好。我想相信有某种方法可以针对现有表构建查询,使其像我的手动模仿一样执行。
  • 您的建议的解释很有趣,因为它引用了 tmp(206598 行;select_type PRIMARY - 这很好),然后是 i(1 行;PRIMARY),然后是 t(35569 行;DERIVED),然后 j(378 行;派生)。这本身并不好,但奇怪的是优化器确切地知道第一步应该涉及多少行。如果是这样,为什么需要再次引用 t 呢?如果我们可以让 MySQL 忘记临时表之外的 t,也许我们可以到达我们想要的位置。 (顺便说一句,今天打卡。感谢您一直以来的努力。将回到 AM。)
【解决方案2】:

尝试给第一个子字符串子句一个别名并在 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'));

这行得通吗?

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

【讨论】:

  • 状态字段没有索引;这是唯一对其进行过滤的查询,并且该查询直到生产后期才出现。此外,我了解到连接几乎总是比子查询好(在这种情况下,依赖子查询,因为 MySQL 5.1 是错误的)。当然,您的第二个建议的 EXPLAIN 输出对 i(17145585 行)进行了全面扫描,并在 j(16576146 行)上进行了全面扫描,然后在 t 上进行了主键连接。这表明性能明显低于原始查询。
  • col IN (SELECT ... FROM table) 在 MySQL 中通常效率不高。使用IN 的嵌套查询会使效率加倍。
猜你喜欢
  • 2019-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-19
  • 2015-07-31
  • 2010-12-23
相关资源
最近更新 更多