2010-07-15 56 views
3

我正在處理一個數據庫,其中項目被「標記」了一定次數。MySQL標記問題:如何選擇已標記爲X,Y和Z的項目?

項目(100K行)

  • ID
  • 其他的東西

標籤(10K行)

  • ID

item2tag(1,000,000行)

  • ITEM_ID
  • TAG_ID

我正在尋找最快的解決方案:

選擇已標記爲X,Y和Z(其中X,Y和Z對應於(可能)標記名稱)的項目?

這裏是我迄今...我只是想確保我在做它的最好的方式:

首先從名字得到tag_ids:

SELECT tag.id WHERE name IN ("X","Y","Z"); 

然後我組由那些tag_ids和使用具有過濾結果:

SELECT item2tag.*, count(tag_id) 
    FROM item2tag 
    WHERE tag_id=1 or tag_id=2 or tag_id=3 
GROUP BY item_id 
HAVING count(tag_id)=3; 

然後,我可以剛剛從具有這些ID的項目中進行選擇。

SELECT * FROM item WHERE id IN ([results from prior query]) 

我在item2tag中有數百萬行,索引在(item_id,tag_id)上。這會是最快的解決方案嗎?

回答

3

您建議的方法可能是執行查詢的最常見方式,但可能不是最快的。使用連接可以更快:

SELECT T1.item_id 
FROM item2tag T1 
JOIN item2tag T2 ON T1.item_id = T2.item_id 
JOIN item2tag T3 ON T2.item_id = T3.item_id 
WHERE T1.tag_id = 1 AND T2.tag_id = 2 AND T3.tag_id = 3 

您應該確保您有以下指標:

  • 上(ITEM_ID,TAG_ID)
  • 指數上(TAG_ID)主鍵。

我的表現在幾種不同的情況下對照原始測試了這個查詢。

  • 對於在近表格中的每個項目被打上了標記中的至少一個被搜索的情況下,原來的查詢約需5秒,JOIN版本需要大約10秒 - 稍微慢一些。
  • 對於其中兩個標籤非常頻繁地出現的情況,其中一個標籤很少會發生,原始查詢大約需要0.9秒,而JOIN查詢只需要0.003秒 - 這是相當大的性能提升。

下面粘貼了用於進行性能測試的SQL。您可以自己運行此測試或稍微修改它並測試其他查詢或不同的場景。

警告:請勿在生產數據庫上運行此腳本,因爲它會修改item2tag表的內容。運行腳本可能需要幾分鐘,因爲它會創建大量數據。

CREATE TABLE filler (
     id INT NOT NULL PRIMARY KEY AUTO_INCREMENT 
) ENGINE=Memory; 

DELIMITER $$ 

CREATE PROCEDURE prc_filler(cnt INT) 
BEGIN 
     DECLARE _cnt INT; 
     SET _cnt = 1; 
     WHILE _cnt <= cnt DO 
       INSERT 
       INTO filler 
       SELECT _cnt; 
       SET _cnt = _cnt + 1; 
     END WHILE; 
END 
$$ 
CALL prc_filler(1000000); 

CREATE TABLE item2tag (
    item_id INT NOT NULL, 
    tag_id INT NOT NULL, 
    count INT NOT NULL 
); 

INSERT INTO item2tag (item_id, tag_id, count) 
SELECT id % 150001, id % 10, 1 
FROM filler; 
ALTER TABLE item2tag ADD PRIMARY KEY (item_id, tag_id); 
ALTER TABLE item2tag ADD KEY (tag_id); 

-- Make tag 3 occur rarely.  
UPDATE item2tag SET tag_id = 10 WHERE tag_id = 3 AND item_id > 0; 

SELECT T1.item_id 
FROM item2tag T1 
JOIN item2tag T2 ON T1.item_id = T2.item_id 
JOIN item2tag T3 ON T2.item_id = T3.item_id 
WHERE T1.tag_id = 1 AND T2.tag_id = 2 AND T3.tag_id = 3; 

SELECT item_id 
FROM item2tag 
WHERE tag_id=1 or tag_id=2 or tag_id=3 
GROUP BY item_id 
HAVING count(tag_id)=3; 
+1

+1你比我快。 :)是的,特別是在MySQL中,分組解決方案很慢。我會使用自聯接解決方案。一定要在(item_id,tag_id)上創建一個複合索引。 – 2010-07-15 22:25:55

+0

副本不應該加入到T1,而不是互相鏈接? – 2010-07-15 22:28:17

+0

@OMG小馬:在這種情況下,如果A = B且B = C,那麼A = C。這是其中六個中的六個。 – 2010-07-15 22:38:05

0

你會得到更好的配置到具有已TAG_ID作爲第一列的索引 - 否則尋找具有TAG_ID 1所有項目都需要進行全表掃描(同爲任何TAG_ID,當然)。

0

根據有多少資料的標籤與單個標籤,你可能會得到標記有一個標籤的產品清單,然後過濾用於其他標籤的出現次數,這樣做:

select item_id from item2tag 
where item_id in (
    select item_id from item2tag 
    where item_id in (
     select item_id from item2tag where tag_id = TID1 
    ) and tag_id = TID2 
) and tag_id = TID3