你的查詢,以及下面的策略將從指數ON log(device_id,when)
受益。該索引可以取代索引ON log(device_id)
,因爲該索引是多餘的。
如果有日誌條目的整體一大堆每個設備時,在查詢JOIN將會產生良好的中小型中間結果集,這將得到滲透到每個設備一行。我不相信MySQL優化器對於反連接操作有任何「快捷方式」(至少不是5.1)......但是您的查詢可能是最有效的。
問:我可以用不同的策略完成工作嗎?
是的,還有其他的策略,但我不知道任何這些都比你的查詢「更好」。
UPDATE:你可能會考慮是增加另一個表,您的架構,一個適用於每個設備的最新的日誌條目
一種策略。這可以通過log
表中定義的TRIGGER來維護。如果您只執行插入操作(不更新最新日誌條目的UPDATE和DELETE,則非常簡單,只要針對log
表執行插入操作,就會觸發AFTER INSERT FOR EACH ROW
觸發器,該觸發器將插入到日誌中的when
值device_id的表格與log_latest
表格中當前的when
值相比較,並插入/更新log_latest
表格中的行,以便最新的行始終存在。或者,您可以將latest_when
和latest_message
列添加到設備表中,並將其保留在那裏。)
但是,這種策略超出了您的原始問題......但是如果您需要頻繁運行「針對所有設備的最新日誌消息」查詢,這是一個可行的策略。缺點是你有一張額外的表格,並且在執行插入log
表格時性能受到影響。這個表格可以使用類似你的原始查詢或下面的替代方法完全刷新。
一種方法是查詢,做了簡單的device
和log
表的加盟,獲得由設備和下降when
命令行。然後使用一個內存變量來處理行,過濾除「最新」日誌條目以外的所有行。請注意,此查詢返回一個額外的列。 (這額外的一列可以通過包裹整個查詢作爲內嵌視圖中刪除,但你可能會得到更好的性能,如果你可以返回一個額外的列活:
SELECT IF(s.id = @prev_device_id,0,1) AS latest_flag
, @prev_device_id := s.id AS id
, s.name
, s.message
FROM (SELECT d.id
, d.name
, l.message
FROM device d
LEFT
JOIN log l ON l.device_id = d.id
WHERE d.active = 1
ORDER BY d.id, l.when DESC
) s
JOIN (SELECT @prev_device_id := NULL) i
HAVING latest_flag = 1
什麼在選擇第一表達列表正在做的是「標記」一行,只要該行上的設備標識值與前一行中的設備標識差異HAVING子句過濾掉所有未標記爲1的行(可以省略HAVING子句來看看這個表達式是如何工作的。)
(我沒有測試過這個語法錯誤,如果你有錯誤,讓我知道,我會仔細看看,我的桌面檢查說沒關係...但我可能錯過了一個paren或comm一,)
(您可以通過包裝,在另一個查詢「擺脫」額外列
SELECT r.id,r.name,r.message FROM (
/* query from above */
) r
(但同樣,這可能會影響性能,你可能會得到,如果你能更好的性能與額外的列一起生活)
當然,在最外層的查詢中添加一個ORDER BY,以確保您的結果集按您需要的方式排序。
這種方法對於一大堆設備來說工作得很好,而且在日誌中只有幾個相關的行。否則,這將產生大量的中間結果集(按照日誌表中的行數),該結果集將被轉移到臨時的MyISAM表中。
UPDATE:
如果從device
基本上讓所有的行(其中謂詞是不是非常有選擇性的),你也許可以得到通過獲得在每一個DEVICE_ID最新的日誌條目更好的性能log
表,並推遲加入device
表。 (但注意,指數將不提供設置爲做好加入該中間結果,所以它真的需要測試來衡量性能。)
SELECT d.id
, d.name
, t.message
FROM device d
LEFT
JOIN (SELECT IF(s.device_id = @prev_device_id,0,1) AS latest_flag
, @prev_device_id := s.device_id AS device_id
, s.messsage
FROM (SELECT l.device_id
, l.message
FROM log l
ORDER BY l.device_id DESC, l.when DESC
) s
JOIN (SELECT @prev_device_id := NULL) i
HAVING latest_flag = 1
) t
ON t.device_id = d.id
注:我們指定兩個降序內聯視圖的ORDER BY子句中的device_id
和when
列別名爲s
,這不是因爲我們需要降序device_id順序的行,而是允許MySQL通過允許MySQL執行「反向掃描」操作來避免文件操作操作帶有前導列的索引(device_id,when)。
NOTE:該查詢仍然會將中間結果集作爲臨時MyISAM表進行假脫機,並且不會有任何索引。所以它的可能性不如原來的查詢。
另一種策略是在SELECT列表中使用相關子查詢。你只返回從日誌表中的單個列,所以這是很容易查詢到理解:
SELECT d.id
, d.name
, (SELECT l.message
FROM log l
WHERE l.device_id = d.id
ORDER BY l.when DESC
LIMIT 1
) AS message
FROM device d
WHERE d.active = 1
ORDER BY d.id ASC;
注:由於id
是在device
表的主鍵(或唯一鍵),和由於您沒有執行任何會生成額外行的JOIN,因此可以省略GROUP BY
子句。
注:此查詢將使用「嵌套循環」操作。也就是說,對於從device
表返回的每一行,(實質上)需要運行單獨的查詢以從日誌中獲取相關行。對於只有少數device
行(如將與在device
表更具選擇性的謂詞被退回),併爲每個設備日誌條目的一大堆,性能不會太差。但對於很多設備,每個設備只有幾條日誌消息,其他方法很可能會更加高效。)
另請注意,使用此方法時請注意,您可以輕鬆地將其擴展爲也返回第二個最新的日誌消息作爲一個單獨的列,通過向SELECT列表添加另一個子查詢(就像第一個子查詢),只需更改LIMIT子句跳過第一行,然後獲取第二行。
, (SELECT l.message
FROM log l
WHERE l.device_id = d.id
ORDER BY l.when DESC
LIMIT 1,1
) AS message_2
對於從設備獲得基本上都行,你可能會得到使用JOIN操作的最佳性能。這種方法的一個缺點是,當有兩個(或更多)行與設備的最新when
值匹配時,它有可能爲設備返回多行。 (基本上,這種做法是保證返回一個「正確」的結果集的時候,我們有一個保證log(device_id,when)
是唯一
有了這個查詢作爲內嵌視圖,以獲得「最新的」當值:
SELECT l.device_id
, MAX(l.when)
FROM log l
GROUP BY l.device_id
我們可以加入此將日誌和設備表。
SELECT d.id
, d.name
, m.messsage
FROM device d
LEFT
JOIN (
SELECT l.device_id
, MAX(l.when) AS `when`
FROM log l
GROUP BY l.device_id
) k
ON k.device_id = d.id
LEFT
JOIN log m
ON m.device_id = d.id
AND m.device_id = k.device_id
AND m.when = k.when
ORDER BY d.id
所有這些都是備選策略(我相信是你問的問題),但我也不清楚箇中ose將會更好地滿足您的特殊需求。 (但它總是好的有幾個不同的工具,在工具帶酌情使用。)
如果您在添加索引(DEVICE_ID時)?這可能會使JOIN更有效率。 – 2012-07-30 21:25:18
正如你經歷過的第一手MySQL越慢查詢越大,你存儲的數據越多等等。如果你的情況超過了100k行,我會推薦使用不同的解決方案:NoSQL – libjup 2012-07-30 21:28:51
@libjup,without意思是把OP放到10萬行並不是很大,實際上它相當小。建議不僅要改變RDBMS而且要改變數據庫管理系統,因爲有一個10萬個表是一個巨大的過度反應。 – Ben 2012-07-30 21:30:44