我已经设置了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的回答。
答案 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'));
那会有用吗?
顺便说一句。你有一个状态字段索引?