2012-07-30 26 views
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 |    | 
+--------------+--------------+------+-----+---------+----------------+ 

TStampStrings的形式爲 「YYYY.MM.DD HH:MM:SS Z」(例如, '22點27分57秒2010.03.19 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 |  |   | 
+--+------+---+--------+-------------+-----+-----+----------+-------+-----------+ 

什麼,這意味着我是在票務每一行,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)索引的回答。

+0

是否'Status'列真的要'longtext'而不是一些較短的類型,比如' VARCHAR(255)'? – 2012-07-30 23:43:45

+0

我剛剛注意到,發佈此問題時,我以爲我曾告訴JPA我想要一個VARCHAR(16),但我想它並沒有提示。儘管如此,不想將模式轉換爲生產。 – 2012-07-30 23:46:26

+0

表是InnoDB還是MyISAM? – 2012-07-30 23:54:14

回答

2

,你能做些什麼來加快查詢:

  • Status添加一個索引。即使你沒有的類型更改爲VARCHAR,您仍然可以添加部分索引:

    ALTER TABLE Ticket 
        ADD INDEX status_idx 
        Status(16) ; 
    
  • 我假設連接表的主鍵是(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) ; 
+0

當然,這會讓這個特定的查詢變得更快,但是我更關心的是,爲什麼天真地執行查詢的順序執行效果明顯好於更清晰的表單。我想相信有一些方法可以針對現有表構建查詢,以便像我的手動模仿一樣執行查詢。 – 2012-07-31 00:04:10

+0

對你的建議的解釋很有意思,它引用了tmp(206598行; select_type PRIMARY--這很好),然後i(1行; PRIMARY),然後t(35569行; DERIVED),然後j(378行; DERIVED )。這本身並不好,但好奇的是,優化器確切知道在第一步中應該包含多少行。如果是這樣的話,爲什麼它需要再次參考t?也許我們可以得到我們想要的位置,如果我們可以讓MySQL忘記臨時表以外的東西。 (順便說一下,順便提一下,謝謝你們的不斷努力,會回到AMs。) – 2012-07-31 00:33:28

-1

嘗試給第一個子串子句一個別名,並在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')); 

將這項工作?

btw。你在狀態字段上有一個索引?

+0

我在「狀態」字段中沒有索引;這是對其進行過濾的唯一查詢,並且此查詢直到生產後期才顯示出來。另外,我明白連接幾乎總是比子查詢更好(在這種情況下,依賴子查詢是因爲MySQL 5.1有問題)。當然,第二個建議的EXPLAIN輸出對i(17145585行)進行全面掃描,在j上完全掃描(16576146行),然後在t上進行主鍵連接。這表明比原始查詢的性能明顯更差。 – 2012-07-30 23:57:06

+0

'col IN(SELECT ... FROM table)'在MySQL中通常效率不高。使用'IN'嵌套查詢會使效率降低一倍。 – 2012-07-31 00:02:47

相關問題