2010-09-07 36 views
3

我想確定最佳的一般方法來查詢連接兩個有很多數據的表,其中每個表都有where子句中的一列。想象一下,一個簡單的模式瓦特/兩個表:在MySQL中,如何加入兩個在WHERE條件中都有列的非常大的表?

posts 
id (int) 
blog_id (int) 
published_date (datetime) 
title (varchar) 
body (text) 

posts_tags 
post_id (int) 
tag_id (int) 

用以下指標:

posts: [blog_id, published_date] 
tags: [tag_id, post_id] 

我們要選擇上都標有「富」給定的博客最近的10個職位。爲了這個討論,假設這個博客有1000萬個帖子,並且有100萬個帖子被標記爲「foo」。什麼是查詢這些數據最有效的方法?

天真的方法是這樣:

SELECT 
    id, blog_id, published_date, title, body 
FROM 
    posts p 
INNER JOIN 
    posts_tags pt 
    ON pt.post_id = p.id 
WHERE 
    p.blog_id = 1 
    AND pt.tag_id = 1 
ORDER BY 
    p.published_date DESC 
LIMIT 10 

MySQL將使用我們的索引,但最終仍要掃描數百萬條記錄。有沒有一種更有效的方法來檢索這種數據而不反規範化模式?

+0

讓引擎做這項工作。提供提示(索引)並檢查執行計劃。如果正在執行全面掃描,那麼它可能是必需的(對於給定的提示 - 檢查您的覆蓋索引),或者計劃生成器失敗(可能它認爲[全面]掃描仍然會獲勝,在這種情況下甚至可能會是正確的)。我不是DBA,但我從來沒有遇到過需要根據非規範化數據(瞬態非規範化數據與[非規範化]非規範化關係模式不一樣)的情況。 – 2010-09-07 21:36:33

+1

偉大的問題。我發現這種類型的問題唯一的解決方案是反規範化。 – nathan 2010-09-07 22:01:44

回答

2

最可能的是,MySQL將首先使用索引(blog_id, published_date)來掃描所有滿足條件blog_id = 1的行,從最新的published_date開始。要做到這一點,只需要從正確的位置開始向後掃描索引。對於每一行,它必須加入posts_tags表。此時,tag_idpost_id都是已知的,所以它只是在主索引中查找該行是否存在。 10%的行具有標記foo,所以在找到結果集的前10行之前,必須檢查posts 表中的平均約100行。

如果標籤foo很常見,我認爲您發佈的查詢運行得相當快。我不認爲它會檢查數百萬行 - 如果你不走運,也許會檢查幾百行,或者幾千行。只要它找到了10個匹配的行,它就可以停止而不檢查更多的行。另一方面,如果您選擇的標籤發生次數少於10次,那麼它會很慢,因爲它必須掃描該博客中的所有行。

您是否有性能測量結果,顯示查詢速度特別慢,即使您搜索的標籤經常出現?你可以發佈EXPLAIN的查詢輸出嗎?

+0

我同意;不要爲此破壞你的數據模型,直到你確信你有一個真正的問題。您提出的查詢確實可能工作得很好。 – 2010-09-07 22:15:21

+0

這是我之前見過的一個常見問題,所以我沒有解釋輸出。你的觀點大約10%被標記爲w /「foo」的行是好的。假設該標籤的頻率要低得多,例如0.1%。在那種情況下,反規範化是否會成爲明智的下一步(即,在posts_tags表上覆制blog_id和published_date,並使用適當的索引)?或者有沒有更好的方式從這個模式中獲取這些數據? – Newt 2010-09-07 22:36:34

0

如果查詢計劃估計您要連接的行數很少,那麼它可能不會使用該索引。因爲掃描是一個線性操作,所以對於少量的行來說,它會表現得更好,而對於大量的行來說,使用索引會更好。正如其他人建議查看查詢計劃以查看它對行數的估計值。

雖然看起來很奇怪,但可以將blod_id和tag_id條件添加到ON條件。我不確定這是否會改變任何事情,但我通常會嘗試這樣的事情。

您也可以嘗試顛倒索引中列的順序,因爲它很重要。有點像電話簿是姓氏,名字索引,這將與具有FirstName,LastName索引的電話簿非常不同。

很難坐下來確定性地說沒有實驗就能發揮最好的效果。我通常通過實驗和基準測試來研究這些事情。有時我發現結果與我期望的基於文檔的結果相反,然後深入研究,發現我沒有實現的某些微妙的行爲/特徵適用於特定的情況。

2

如果性能是極爲重要的,然後denormalise的建議:

表:

create table posts_tags 
(
blog_id int unsigned not null, -- denormalise 
tag_id smallint unsigned not null, 
post_id int unsigned not null, 
primary key(blog_id, tag_id, post_id) -- clustered composite PK 
) 
engine=innodb; 

denormalisation觸發:

delimiter # 

create trigger posts_tags_before_ins_trig before insert on posts_tags 
for each row 
proc_main:begin 

declare b_id int unsigned default 0; 

    select blog_id into b_id from posts where post_id = new.post_id; 

    set new.blog_id = b_id; 

end proc_main # 

delimiter ; 

查詢存儲過程:(假定posts.post_id是AUTO_INCREMENT PK)

delimiter ; 

drop procedure if exists get_latest_blog_posts_by_tag; 

delimiter # 

create procedure get_latest_blog_posts_by_tag 
(
in p_blog_id int unsigned, 
in p_tag_id smallint unsigned 
) 
proc_main:begin 

    select 
    p.* 
    from 
    posts p 
    inner join 
    (
    select distinct 
     pt.post_id 
    from 
     posts_tags pt 
    where 
     pt.blog_id = p_blog_id and pt.tag_id = p_tag_id 
    order by 
     pt.post_id desc limit 10 
) rp on p.post_id = rp.post_id 
    order by 
    p.post_id desc; 

end proc_main # 

delimiter ; 

call get_latest_blog_posts_by_tag(1,1); 
3

您想對連接表執行的任何過濾器都應該加入連接。從技術上講,WHERE子句應只包含需要多個表或主表的條件。儘管它可能不會加快所有查詢速度,但可以確保MySQL正確優化查詢。

FROM 
posts p 
INNER JOIN 
posts_tags pt 
ON pt.post_id = p.id 
    AND pt.tag_id = 1 
WHERE 
p.blog_id = 1