2009-10-09 239 views
2

說我有兩個MyISAM表:MySQL的:優化連接查詢

tab_big: id1, id2, id_a, ord   (5 billion records) 
tab_small: id1, id2, id_b    (1 billion records) 


CREATE TABLE IF NOT EXISTS `tab_big` (
    `id_a` int(10) unsigned NOT NULL, 
    `id1` int(10) unsigned NOT NULL, 
    `id2` int(10) unsigned NOT NULL, 
    `ord` int(10) unsigned NOT NULL DEFAULT '1', 
    PRIMARY KEY (`id_a`,`id1`,`id2`), 
    KEY `id1` (`id1`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1; 


CREATE TABLE IF NOT EXISTS `tab_small` (
    `id_b` int(10) unsigned NOT NULL, 
    `id1` int(10) unsigned NOT NULL, 
    `id2` int(10) unsigned NOT NULL, 
    PRIMARY KEY (`id_b`,`id1`,`id2`), 
    KEY `id_b` (`id_b`), 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

所有字段是INT。在這兩個表中,三個id字段(分別是id1,id2,id_a和id1,id2,id_b)的組合是唯一的,所以我爲這兩個字段創建了一個主鍵。

我需要獲取從第一表,其中ID_A的唯一值的高效的查詢:

  1. ID_B在第二表的表是一個給定值(縮小它下降到約10k的條目)
  2. id1/id2組合在兩個表中都是相同的
  3. 第一個表中的id_a與tab_small子集中的id1,id2字段中的任一個不相同(如由id_b字段縮小);經過一番小小的調整後,似乎在php中生成列表(大約200個ids)並將其作爲文本提供比添加另一個JOIN更好)。

我認爲這不是非常緩存,因爲兩個表都一直在改變(添加行)。

我當前的查詢是非常簡單的:

SELECT tab_big.id_a FROM tab_big, tab_small 
    WHERE tab_small.id_b = '$constant' 
    AND tab_big.id1 = tab_small.id1 AND tab_big.id2 = tab_small.id2 
    AND tab_big.id_a NOT IN ({comma delimited list of 200 ids}) 
    GROUP BY tab_big.id_a 
    ORDER BY SUM(tab_big.ord) DESC 
    LIMIT 10 

它的工作原理,但不夠快,無法真正使用它。可以用它做什麼?

EXPLAIN說它首先從tab_big獲取一個遠程查詢,然後將其應用於tab_small(編輯:下面添加)。我不知道爲什麼(EXPLAIN說查詢使用主鍵),但添加tab_big.id1索引似乎有所幫助。另外,試圖用STRAIGHT_JOIN來反過來,首先從(小)tab_small中選擇一個10k子集,然後使用它在(更大的)tab_big中進行搜索,結果會比默認的結果差得多(編輯:用一個小數據集I現在需要進行測試;對於生產數據,它顯然是相反的,EXPLAIN看起來像第二個)。

+----+-------------+-----------+--------+-----------------+---------+---------+-------------------------------------------+---------+----------------------------------------------+ 
| id | select_type | table  | type | possible_keys | key  | key_len | ref          | rows | Extra          | 
+----+-------------+-----------+--------+-----------------+---------+---------+-------------------------------------------+---------+----------------------------------------------+ 
| 1 | SIMPLE  | tab_big | range | PRIMARY,id1  | PRIMARY | 4  | NULL          | 1374793 | Using where; Using temporary; Using filesort | 
| 1 | SIMPLE  | tab_small | eq_ref | PRIMARY,id_b | PRIMARY | 12  | const,db.tab_big.id1,db.tab_big.id2  |  1 | Using index         | 
+----+-------------+-----------+--------+-----------------+---------+---------+-------------------------------------------+---------+----------------------------------------------+ 

在更大的數據集EXPLAIN可能會看起來更像這個(雖然無視「行」的價值觀 - 它是從一個較小的數據集拍攝):

+----+-------------+-----------+------+---------------------+---------+---------+------------------+-------+----------------------------------------------+ 
| id | select_type | table  | type | possible_keys  | key  | key_len | ref    | rows | Extra          | 
+----+-------------+-----------+------+---------------------+---------+---------+------------------+-------+----------------------------------------------+ 
| 1 | SIMPLE  | tab_small | ref | PRIMARY,id_b,id1 | PRIMARY | 4  | const   | 259 | Using index; Using temporary; Using filesort | 
| 1 | SIMPLE  | tab_big | ref | PRIMARY,id1   | id1  | 4  | db.tab_small.id1 | 25692 | Using where         | 
+----+-------------+-----------+------+---------------------+---------+---------+------------------+-------+----------------------------------------------+ 

有什麼想法?

+0

你可以擺脫NOT IN並把它寫成IN嗎?這通常有助於解決性能問題。 – 2009-10-09 03:39:07

+0

不,不幸的是,我只知道我不想找的東西。 :/ – Mike 2009-10-09 03:41:20

+0

你可以在SQL中發佈表結構嗎? – wenbert 2009-10-09 04:02:37

回答

3

創建以下指標:

CREATE INDEX ix_big_1_2_a ON tab_big (id1, id2, id_a) 
CREATE UNIQUE INDEX ux_small_b_2_1 ON tab_small (id_b, id2, id1) 

,並嘗試這個辦法:

SELECT DISTINCT 
     a.id_a 
FROM tab_small b 
JOIN tab_big a 
ON  (a.id1, a.id2) = (b.id1, b.id2) 
WHERE b.id_b = 2 
     AND a.id_a NOT IN 
     (
     SELECT id1 
     FROM tab_small b1 /* FORCE INDEX (PRIMARY) */ 
     WHERE b1.id_b = 2 
     ) 
     AND a.id_a NOT IN 
     (
     SELECT id2 
     FROM tab_small b2 /* FORCE INDEX (ux_small_b_2_1) */ 
     WHERE b2.id_b = 2 
     ) 

,產生這個查詢計劃:

1, 'PRIMARY', 'b', 'ref', 'PRIMARY,ux_small_b_2_1', 'PRIMARY', '4', 'const', 1, 100.00, 'Using index; Using temporary' 
1, 'PRIMARY', 'a', 'ref', 'ix_big_1_2', 'ix_big_1_2', '8', 'test.b.id1,test.b.id2', 2, 100.00, 'Using where' 
3, 'DEPENDENT SUBQUERY', 'b2', 'ref', 'ux_small_b_2_1', 'ux_small_b_2_1', '8', 'const,func', 1, 100.00, 'Using index' 
2, 'DEPENDENT SUBQUERY', 'b1', 'ref', 'PRIMARY', 'PRIMARY', '8', 'const,func', 1, 100.00, 'Using index' 

這不是因爲它可以高效是的,我仍然期待這比你的查詢更快。

我註釋掉了FORCE INDEX語句,但您可能需要取消註釋它們是優化程序不會選擇這些索引。

如果MySQL有能力做FULL OUTER JOIN使用MERGE,但事實並非如此,一切都會簡單得多。

更新:

來看你的統計數據,該查詢會更高效:

SELECT id_a 
FROM (
     SELECT DISTINCT id_a 
     FROM tab_big ad 
     ) a 
WHERE id_a NOT IN 
     (
     SELECT id1 
     FROM tab_small b1 FORCE INDEX (PRIMARY) 
     WHERE b1.id_b = 2 
     ) 
     AND id_a NOT IN 
     (
     SELECT id2 
     FROM tab_small b2 FORCE INDEX (ux_small_b_2_1) 
     WHERE b2.id_b = 2 
     ) 
     AND EXISTS 
     (
     SELECT NULL 
     FROM tab_small be 
     JOIN tab_big ae 
     ON  (ae.id1, ae.id2) = (be.id1, be.id2) 
     WHERE be.id_b = 2 
       AND ae.id_a = a.id_a 
     ) 

其工作原理如下:

  • 構建的DISTINCT id_a列表(這是100,000行)
  • 過濾掉t他存在於子集中的值
  • 對於id_a的每個值,它搜索子集中存在的(id_a, id1, id2)。這是通過迭代子集來完成的。由於找到該值的概率很高,因此最有可能搜索將從該子集的開始處成功排列在10行左右,並且EXISTS將在那一刻返回。

這很可能需要評估大約1,000,000記錄左右。

確保以下計劃用於:

1, 'PRIMARY', '<derived2>', 'ALL', '', '', '', '', 8192, 100.00, 'Using where' 
5, 'DEPENDENT SUBQUERY', 'be', 'ref', 'PRIMARY,ux_small_b_2_1', 'PRIMARY', '4', 'const', 1, 100.00, 'Using index' 
5, 'DEPENDENT SUBQUERY', 'ae', 'eq_ref', 'PRIMARY,ix_big_1_2', 'PRIMARY', '12', 'a.id_a,test.be.id1,test.be.id2', 1, 100.00, 'Using index' 
4, 'DEPENDENT SUBQUERY', 'b2', 'ref', 'ux_small_b_2_1', 'ux_small_b_2_1', '8', 'const,func', 1, 100.00, 'Using index' 
3, 'DEPENDENT SUBQUERY', 'b1', 'ref', 'PRIMARY', 'PRIMARY', '8', 'const,func', 1, 100.00, 'Using index' 
2, 'DERIVED', 'ad', 'range', '', 'PRIMARY', '4', '', 10, 100.00, 'Using index for group-by' 

,是在最後一排Using index for group-by最重要的部分。

+0

我不明白你爲什麼要按照你的建議定義索引。爲了讓連接處理索引,並不是所有在連接中使用的列都必須進行索引,並且與連接條件中的順序相同? 我的感覺是,由於連接,聲明很慢......不是因爲子查詢! – Thorsten 2009-10-09 12:22:56

+0

'JOIN'中使用的列在'ix_big_1_2_a'中編入索引。由於'JOIN',語句可能會(或可能不會)緩慢,但是我們不能確定它是真正的原因,直到我們知道'tab_big'中有多少行滿足'JOIN'條件。 – Quassnoi 2009-10-09 12:30:18

+0

不錯! 首先,ix_big_1_2_a與原始查詢有很大區別。其次,您建議的查詢效果更好。不幸的是,它丟失了原始查詢中的ORDER BY部分(應該首先提供最合適的條目),但是我可能會在此作弊。 非常感謝!對此,我真的非常感激。 :) – Mike 2009-10-09 17:09:20

0

你試過tab_small LEFT JOIN tab_big?您也可以在字段創建索引tab_small.id_btab_big.id_a

+0

試過左加入以防萬一,實際上工作更糟糕。我其實有一個tab_small id_b索引;然而,添加tab_big.id_a索引並沒有幫助。 – Mike 2009-10-09 04:22:59

0

我建議把指數上的所有四列是加入(或四個獨立的tb.id1,tb.id2,ts.id1索引的一部分和ts.id2列,或者tb.id1/id2和ts.id1/id2中的兩個)。然後看看這是否會給你帶來更好的表現。 (我想這樣做,但你永遠不知道,除非嘗試它。)


注:以下想法是不行的,但我把它放在這樣的評論還是一定意義。

而不是使用PHP生成的列表,你不能在連接條件(或者如果你更喜歡,在where子句中)表達你的限制(3)嗎?(類似於rexem的建議)

SELECT tb.id_a 
    FROM TAB_BIG tb 
    JOIN TAB_SMALL ts ON ts.id1 = tb.id1 
       AND ts.id2 = tb.id2 
       AND tb.id1 <> ts.id_a 
       AND tb.id2 <> ts.id_a 
WHERE ts.id_b = ? 

但是,這更多的是爲了清晰和簡單而不是性能。 (另請注意,附加條件可能會要求ID_A和tb.id1和tb.id2可能單獨的索引另一個指標。)

+0

試圖添加id1,id2索引,沒有幫助(解釋仍然說它使用PRIMARY)。 這裏的<>子句不會排除那些id1,id2和id \ _a在這個特定條目中相同的條目嗎?我需要排除在特定ID \ _b的ts記錄中出現的_all_ id(id1或id2)。 – Mike 2009-10-09 12:07:00

+0

好的,那麼通過rexem的EXISTS會是正確的(或Quassnoi的聲明)。爲了清晰起見,我會在帖子中留下建議。 – Thorsten 2009-10-09 12:17:13