2013-05-18 161 views
6

使用數據庫模式從此問題接受的answer進行標記可以使用group_concat查詢來處理大量數據嗎?我需要爲所有標記爲x的項目標記物品。在具有〜500萬個標籤的group_concat上使用查詢時,速度非常慢,時間大於15秒。沒有group_concat(項目沒有標籤)是〜0.05秒。使用group_concat標記查詢

作爲一個側面問題,SO如何解決這個問題?

+1

你可以提供樣品記錄。 –

+0

SO似乎通過將問題限制在最多5個標籤來解決這個問題。什麼讓你認爲它在處理標籤時使用'GROUP_CONCAT()'? – Barmar

+1

@Barmar:SO上的標籤限制不是出於性能原因,而是[保持焦點](http://meta.stackexchange.com/a/34743);至於[SO的模式](http://meta.stackexchange.com/a/2678),標籤以規範化的方式(PostTags表)和非規範化的方式(Posts.Tags 'field) - 後者可以非常快速地檢索帖子本身的帖子標籤,而前者可以很容易地搜索具有特定標籤組合的帖子。 – eggyal

回答

5

這可能是一種糟糕的索引策略。適應的問題the accepted answer顯示的模式,以您鏈接:

CREATE Table Items (
    Item_ID SERIAL, 
    Item_Title VARCHAR(255), 
    Content TEXT 
) ENGINE=InnoDB; 

CREATE TABLE Tags (
    Tag_ID  SERIAL, 
    Tag_Title VARCHAR(255) 
) ENGINE=InnoDB; 

CREATE TABLE Items_Tags (
    Item_ID BIGINT UNSIGNED REFERENCES Items (Item_ID), 
    Tag_ID  BIGINT UNSIGNED REFERENCES Tags (Tag_ID), 
    PRIMARY KEY (Item_ID, Tag_ID) 
) ENGINE=InnoDB; 

需要注意的是:

  • MySQL的SERIAL數據類型爲BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE的別名和,因此,被索引;

  • 定義Items_Tags中的外鍵約束在外鍵列上創建索引。

+0

嗯我很確定我有相同的索引,今晚晚些時候會檢查。 –

+0

@amiawizard:任何消息? – eggyal

3

我建議有normalized數據和denormalized數據之間的混合體。
所以使用eggyal我會做以下非規範化的結構提供標準化的結構:(Tag_Title)爲相應Item_ID

CREATE TABLE Items_Tags_Denormalized (
    Item_ID BIGINT UNSIGNED REFERENCES Items (Item_ID), 
    Tags  BLOB, 
    PRIMARY KEY (Item_ID) 
) ENGINE=InnoDB; 

Tags欄,你會擁有所有的標籤。
現在你有2種方法來實現這一目標:

  • 創建一個定期運行一個cron將使用GROUP_CONCAT建立這個表格Items_Tags_Denormalized或任何適合你(好處:當你插入或刪除不把額外的負載在Items_Tags表;缺點:非規範化的表不會永遠是最新的(取決於你是否經常運行的cron))

  • 上插入Items_Tags表中創建triggers,並以不斷更新的刪除Items_Tags_Denormalized表(優點:非規範化的t能夠將永遠是最新的;缺點:當您插入或Items_Tags表中刪除額外的負載)

選擇何種解決方案適合您需求的最佳考慮的優點和缺點。

所以最後你會得到Items_Tags_Denormalized表,從這裏你將只讀而不用做額外的操作

+0

爲什麼不把非規範化的'Tags'字段添加到'Items'表(這是怎麼做到的)? – eggyal

+0

這是至關重要的,有單獨的模型:一個規範化和一個非規範化,你的解決方案是好的,但從設計pov我建議保持它分開的各種原因:你需要重建表,你需要添加更多的列等,如果如果您在項目中添加非標準化標籤列,表格的性能將下降:更大的大小=更慢的查詢 – Stephan

1

爲什麼要使用group_concat呢?對於給定的標籤x,您表示選擇項目列表的速度很快。對於給定的項目列表,獲取所有標籤也應該很快。並且通常沒有某種限制,我的意思是一般網站在一頁上不顯示100000個條目。

我建議:

drop temporary table if exists lookup_item; 

create temporary table lookup_item (item_id serial, primary key(item_id)); 

insert into lookup_item select i.id as item_id 
from items i 
where exists (select * from items_tags where item_id = i.id and tag_id = <tag_id>) 
and <other conditions or limits>; 

select * from lookup_item 
inner join items_tags it on it.item_id = i.id 
inner join tags t on t.id = it.tag_id 
order by i.<priority>, t.<priority> 

優先級可能是最後一次修改的項目和某種重要性的標籤。

然後你得到每個項目的標籤。代碼中唯一的工作是查看結果行何時具有下一個項目。

1

如果我理解正確,GROUP_CONCAT不是你正在移除的唯一東西,這使得查詢更快速,沒有標籤。在GROUP_CONCAT內部,您選擇Tags.Tag_Title並強制要訪問標籤表。

你可以嘗試運行GROUP_CONCATItems_Tags.Tag_ID來測試我的理論。