2010-06-23 86 views
2

兩張桌子。幫我把SUBQUERY變成JOIN

電子郵件 id(int10)|所有權(int10)

消息 emailid(int10)索引|消息(中文文本)

子查詢(這在MySQL中很糟糕)。

SELECT COUNT(*)FROM消息 WHERE消息LIKE '%字%' AND EMAILID IN(SELECT ID從電子郵件WHERE所有權= 32)


這裏的用法是,我運行一個搜索在電子郵件上(這在上面的示例中顯然是簡化的),其生成了3000個電子郵件ID的列表。然後,我想對消息進行搜索,因爲我需要進行文本匹配 - 只有這3000封電子郵件針對郵件。

對郵件的查詢很昂貴(郵件沒有編入索引),但這很好,因爲它只會檢查幾行。

想法:

i)加入。我迄今爲止的嘗試都沒有奏效,導致消息表的全表掃描(即未使用emailid索引)ii)臨時表。這可以工作,我想。 3)在客戶端緩存ID並運行2個查詢。這確實有用。不優雅。 iv)子查詢。 mySQL子查詢每次運行第二個查詢,所以這不起作用。也許固定在MySQL 6.

好的,這是我到目前爲止。這些是實際的字段名稱(我簡化了一下)。

查詢:

SELECT COUNT(*) FROM ticket LEFT JOIN ticket_subject 
ON (ticket_subject.ticketid = ticket.id) 
WHERE category IN (1) 
AND ticket_subject.subject LIKE "%about%" 

結果:

1 SIMPLE ticket ref  PRIMARY,category category 4 const 28874  
1 SIMPLE ticket_subject eq_ref PRIMARY  PRIMARY  4 deskpro.ticket.id 1 Using where 

它需要0.41秒和返回的COUNT(*)113

運行:

SELECT COUNT (*) FROM ticket WHERE category IN (1) 

需要0.01秒和f共有33,000項結果。

運行

SELECT COUNT (*) FROM ticket_subject WHERE subject LIKE "%about%" 

注意到0.14秒,發現1300個結果。

票據表和ticket_subject表都有300,000行。

ticket_subject.ticketid和ticket.category上有一個索引。

我現在意識到使用LIKE語法是一個錯誤 - 因爲它有一點關於FULLTEXT的紅鯡魚。這不是問題。問題是:

1)表A-非常快的查詢,在索引上運行。 0。001秒 2)表B - 中等到慢速查詢,沒有索引 - 進行全表掃描。 0.1秒。

這兩個結果都很好。問題是我必須加入他們,搜索需要0.3秒;這對我來說沒有意義,因爲表B上組合查詢的慢速方面應該更快,因爲我們現在只搜索該表的一小部分 - 即它不應該執行全表掃描,因爲正在JOINED的字段被索引。

+0

所以基本上你試圖迫使它通過EMAILID了'消息LIKE'之前做的過濾器%word%''有點發生?或者這正是你想要阻止的事情? – 2010-06-23 12:37:08

+0

是的,那正是我想要發生的事情。可能它正在發生,mySQL在取得這些結果(33,000)時會很慢,然後搜索它們。但似乎奇怪的是,在索引列中找到ticket_subject表中的33,000個結果要比在非索引列上在該表中搜索300,000個結果要慢。 – 2010-06-23 23:09:10

回答

8

記住帶布爾short-circuit evaluation的優勢:

SELECT COUNT(*) 
FROM messages 
join emails ON emails.id = messages.emailid 
WHERE ownership = 32 AND message LIKE '%word%' 

該過濾器由ownership則計算LIKE謂語前。總是把你的便宜的表情放在左邊。

此外,我同意@Martin Smith和@MJB,您應該考慮使用MySQL的FULLTEXT索引來加快速度。


回覆您的評論和其它信息,這裏的一些分析:

explain SELECT COUNT(*) FROM ticket WHERE category IN (1)\G 

      id: 1 
    select_type: SIMPLE 
     table: ticket 
     type: ref 
possible_keys: category 
      key: category 
     key_len: 4 
      ref: const 
     rows: 1 
     Extra: Using index 

的說明「使用索引」是一件好事,看看,因爲這意味着它能夠滿足查詢只需讀取索引數據結構,甚至不涉及表格的數據。這肯定會跑得非常快。

explain SELECT COUNT(*) FROM ticket_subject WHERE subject LIKE '%about%'\G 

      id: 1 
    select_type: SIMPLE 
     table: ticket_subject 
     type: ALL 
possible_keys: NULL  <---- no possible keys 
      key: NULL 
     key_len: NULL 
      ref: NULL 
     rows: 1 
     Extra: Using where 

這表明沒有可能有益於通配符LIKE謂詞的可能鍵。它使用WHERE子句中的條件,但必須通過運行表掃描來評估它。

explain SELECT COUNT(*) FROM ticket LEFT JOIN ticket_subject 
ON (ticket_subject.ticketid = ticket.id) 
WHERE category IN (1) 
AND ticket_subject.subject LIKE '%about%'\G 

      id: 1 
    select_type: SIMPLE 
     table: ticket 
     type: ref 
possible_keys: PRIMARY,category 
      key: category 
     key_len: 4 
      ref: const 
     rows: 1 
     Extra: Using index 

      id: 1 
    select_type: SIMPLE 
     table: ticket_subject 
     type: ref 
possible_keys: ticketid 
      key: ticketid 
     key_len: 4 
      ref: test.ticket.id 
     rows: 1 
     Extra: Using where 

同樣,訪問票錶快,但是這由LIKE狀態所發生的表掃描寵壞了。

ALTER TABLE ticket_subject ENGINE=MyISAM; 

CREATE FULLTEXT INDEX ticket_subject_fulltext ON ticket_subject(subject); 

explain SELECT COUNT(*) FROM ticket JOIN ticket_subject 
ON (ticket_subject.ticketid = ticket.id) 
WHERE category IN (1) AND MATCH(ticket_subject.subject) AGAINST('about') 

      id: 1 
    select_type: SIMPLE 
     table: ticket 
     type: ref 
possible_keys: PRIMARY,category 
      key: category 
     key_len: 4 
      ref: const 
     rows: 1 
     Extra: Using index 

      id: 1 
    select_type: SIMPLE 
     table: ticket_subject 
     type: fulltext 
possible_keys: ticketid,ticket_subject_fulltext 
      key: ticket_subject_fulltext   <---- now it uses an index 
     key_len: 0 
      ref: 
     rows: 1 
     Extra: Using where 

你永遠不會做出LIKE表現良好。看到我的介紹Practical Full-Text Search in MySQL


回覆您的評論:好吧,我已經做了類似規模的數據集一些實驗(在用戶和徽章在堆棧溢出數據錶轉儲:-)。這裏是我發現的:

select count(*) from users 
where reputation > 50000 

+----------+ 
| count(*) | 
+----------+ 
|  37 | 
+----------+ 
1 row in set (0.00 sec) 

這真的很快,因爲我在聲望列上有一個索引。

  id: 1 
    select_type: SIMPLE 
     table: users 
     type: range 
possible_keys: users_reputation_userid_displayname 
      key: users_reputation_userid_displayname 
     key_len: 4 
      ref: NULL 
     rows: 37 
     Extra: Using where; Using index 

select count(*) from badges 
where badges.creationdate like '%06-24%' 

+----------+ 
| count(*) | 
+----------+ 
|  1319 | 
+----------+ 
1 row in set, 1 warning (0.63 sec) 

這是預料之中的,因爲該表有700k行,它必須執行表掃描。現在讓我們來加入:

select count(*) from users join badges using (userid) 
where users.reputation > 50000 and badges.creationdate like '%06-24%' 

+----------+ 
| count(*) | 
+----------+ 
|  19 | 
+----------+ 
1 row in set, 1 warning (0.03 sec) 

這似乎並不壞。這裏的解釋報告:

  id: 1 
    select_type: SIMPLE 
     table: users 
     type: range 
possible_keys: PRIMARY,users_reputation_userid_displayname 
      key: users_reputation_userid_displayname 
     key_len: 4 
      ref: NULL 
     rows: 37 
     Extra: Using where; Using index 

      id: 1 
    select_type: SIMPLE 
     table: badges 
     type: ref 
possible_keys: badges_userid 
      key: badges_userid 
     key_len: 8 
      ref: testpattern.users.UserId 
     rows: 1 
     Extra: Using where 

這似乎像它的使用索引智能化的加入,它可以幫助我有包括用戶ID和美譽度一個複合索引。請記住,MySQL只能爲每個表使用一個索引,因此爲需要執行的查詢定義正確的複合索引非常重要。


回覆您的評論:OK,我已經試過這其中口碑> 5000,並在信譽> 500,並在信譽> 50.這些應該匹配更大的一組用戶。

select count(*) from users join badges using (userid) 
where users.reputation > 5000 and badges.creationdate like '%06-24%' 

+----------+ 
| count(*) | 
+----------+ 
|  194 | 
+----------+ 
1 row in set, 1 warning (0.27 sec) 

select count(*) from users join badges using (userid) 
where users.reputation > 500 and badges.creationdate like '%06-24%' 

+----------+ 
| count(*) | 
+----------+ 
|  624 | 
+----------+ 
1 row in set, 1 warning (0.93 sec) 

select count(*) from users join badges using (userid) 
where users.reputation > 50 and badges.creationdate like '%06-24%' 
-------------- 

+----------+ 
| count(*) | 
+----------+ 
|  1067 | 
+----------+ 
1 row in set, 1 warning (1.72 sec) 

的解釋報告是在所有情況下是相同的,但如果查詢的用戶表中尋找更多的匹配行,那麼它自然要評估對在徽章表了很多更多的匹配行的LIKE謂語。

這確實是有一些成本做一個加入。有點驚人的是,它非常昂貴。但是如果你使用索引,這可以被緩解。

我知道你說你不能使用索引的查詢,但也許是時候考慮創建您的原始列的數據的一些變換形式冗餘列,所以你可以指數吧。在上面的示例中,我可能會創建一個列creationdate_day並從DAYOFYEAR(creationdate)填充它。


這裏就是我的意思是:

ALTER TABLE Badges ADD COLUMN creationdate_day SMALLINT; 
UPDATE Badges SET creationdate_day = DAYOFYEAR(creationdate); 
CREATE INDEX badge_creationdate_day ON Badges(creationdate_day); 

select count(*) from users join badges using (userid) 
where users.reputation > 50 and badges.creationdate_day = dayofyear('2010-06-24') 

+----------+ 
| count(*) | 
+----------+ 
|  1067 | 
+----------+ 
1 row in set, 1 warning (0.01 sec) <---- not too shabby! 

這裏的解釋報告:

  id: 1 
    select_type: SIMPLE 
     table: badges 
     type: ref 
possible_keys: badges_userid,badge_creationdate_day 
      key: badge_creationdate_day <---- here is our new index 
     key_len: 3 
      ref: const 
     rows: 1318 
     Extra: Using where 

      id: 1 
    select_type: SIMPLE 
     table: users 
     type: eq_ref 
possible_keys: PRIMARY,users_reputation_userid_displayname 
      key: PRIMARY 
     key_len: 8 
      ref: testpattern.badges.UserId 
     rows: 1 
     Extra: Using where 
+0

+1從來沒有想過 – DrColossos 2010-06-23 12:50:17

+1

我不熟悉MySQL,但是你確定它在創建執行計劃時沒有對where謂詞重新排序嗎? – Mike 2010-06-23 12:53:54

+0

@Mike:是的,我確定。沒有支持短路評估的編程語言應該重新排列布爾表達式! – 2010-06-23 12:59:30

3
SELECT COUNT(*) 
FROM messages 
join emails ON emails.id = messages.emailid 
WHERE message LIKE '%word%' 
AND ownership = 32 

問題雖然是與'%word%'這總是需要掃描的消息。如果您使用的是MyISAM,則可能需要查看full text search

+0

我真的想說明一個快速搜索結果與慢速搜索結果的結合情況。 但是在這種情況下,%word%上的搜索應該非常快,因爲它只搜索索引選定的幾百或幾千行。 – 2010-06-23 13:25:00

+0

@Chris - 你可以用你迄今爲止嘗試過的最好的問題來更新你的問題,它是解釋計劃嗎? – 2010-06-23 13:47:49

+0

剛剛完成。 – 2010-06-23 23:06:20

2

我認爲這是你在找什麼:

select count(*) 
from messages m 
    inner join emails e 
    on e.id = m.emailid 
where m.message like '%word%' 
    and e.ownership = 32 

很難說肯定會表現如何。如果FTS是因爲WORD上的啓動通配符,那麼這樣做並不能解決問題。但好消息是,連接可能會限制消息表中的記錄,您必須查看。

+0

謝謝,這在馬丁的回答中以相同的速度執行。它比運行慢速查詢(針對郵件的%word%)慢3倍。 – 2010-06-23 13:30:35

+0

@Chris - 我認爲有一個問題是,您正在對非索引列進行加入 - emails.id - 因此,除非您爲該列編制索引,否則無法加快其速度。您也在該表上強制進行全表掃描(FTS)。 – MJB 2010-06-23 14:11:55

+0

此列已編入索引。我在上面提供了一個EXPLAIN。 – 2010-06-23 23:06:52

0

您是否可以通過其他方式轉接連接?看起來第二個查詢是一個比較便宜的查詢,並且由於整個事情只是一個簡單的連接,因此您希望執行更便宜的查詢來縮小數據集的範圍,然後對更昂貴的查詢進行連接。

+0

那麼子查詢會很快我相信因爲它會使用索引來獲取它需要檢查的消息列表,然後只會處理這些消息。問題是我無法創建一個似乎適用於該邏輯的連接;我所有的連接速度比對整個表運行昂貴的查詢要慢3倍。 – 2010-06-23 13:31:48