我已经建立了一个 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 对添加(部分)索引的回答。