2012-02-09 255 views
1

我在我的數據庫,其中有超過百萬記錄的字典,這個簡單的選擇MySQL查詢優化性能

select * from Word where languageId = 'en' order by rand() limit 1 

隨機選擇一個字。

問題是,這個請求持續3-4秒,這是非常長的,因爲我不得不重複多次。

有沒有辦法實現同樣的事情,但更快?

編輯 - 表模式

wordId - integer, auto increment 
languageId - varchar (FK), values like cs, en, de, ... 
word - varchar, word itself 

數據結構例子

wordId languageId word 
-------------------------- 
1  cs   abatyše 
... 
100000 cs   zip 
100001 en   aardvark 
... 
etc 

SQL

CREATE TABLE Language (
    languageId VARCHAR(20) NOT NULL , 
    name VARCHAR(255) NULL , 
PRIMARY KEY(languageId)); 

CREATE TABLE Word (
    wordId INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, 
    languageId VARCHAR(20) NOT NULL , 
    word VARCHAR(255) NULL , 
PRIMARY KEY(wordId) , 
INDEX Word_FK_Language(languageId), 
    FOREIGN KEY(languageId) 
    REFERENCES Language(languageId) 
     ON DELETE NO ACTION 
     ON UPDATE NO ACTION); 
+0

您是否有記錄ID列? – Cheery 2012-02-09 21:30:10

+0

@Cheery是的,我有 – user219882 2012-02-09 21:32:27

+0

您能告訴我更多關於您想要做什麼嗎?也許你不需要執行單個查詢,或者你的解決方案可以被優化。 – Cheery 2012-02-09 21:45:15

回答

3

如果你哈已經一個標識列和元素之間的差距並不大(沒有太多的元素被刪除,否則某些元素會更多地選擇),然後嘗試此查詢

SELECT * FROM `table` 
    WHERE id >= 
     (SELECT FLOOR(MAX(id) * RAND()) FROM `table` WHERE languageId = 'en') 
    AND languageId = 'en' 
    ORDER BY id LIMIT 1; 

而且看這裏不同的例子 http://akinas.com/pages/en/blog/mysql_random_row/

ps:我剛剛意識到,它只有在不需要languageId的情況下才能正常工作,否則相同languageId的ID差距可能會很大。

更新試試這個,它可能會快兩倍。我檢查針對您的查詢的執行時間..快一倍..

SELECT d.* FROM 
    (SELECT @rn:=0) r, 
    (SELECT FLOOR(count(*)*RAND()) as rnd FROM `Word` WHERE languageId = 'en') t, 
    (SELECT @rn:[email protected]+1 as rn, `Word`.* FROM `Word` WHERE languageId = 'en') d 
WHERE d.rn >= t.rnd LIMIT 1 

基本上仍造成某種連續的ID,但沒有通過他們排序。

最後更新這一個可能更快(取決於所產生的隨機數)

SELECT d.* FROM 
    (SELECT @rn:[email protected]+1 as rn, w.*, t.rnd rnd FROM 
    (SELECT @rn:=0) r, 
    (SELECT FLOOR(count(*)*RAND()) rnd FROM `Word` WHERE languageId = 'en') t, 
    `Word` w 
    WHERE w.languageId = 'en' AND @rn<t.rnd 
) d 
WHERE d.rn=d.rnd 
+0

這個查詢幾乎與我的想法類似的解決方案 – user219882 2012-02-09 21:43:40

+0

幾乎相同。我認爲主鍵必須是連續的,否則有可能不匹配隨機值。另外,我不確定,但我認爲有些rdbms會評估每條記錄的子查詢。 – Tim 2012-02-09 21:46:02

+0

@Tomas它可能是評估每個記錄的子查詢。 – Tim 2012-02-09 21:47:29

2

首先,確保你的表是正確索引。它是否有主鍵? languageId是一個索引嗎?確保它是。

其次,你只對這個詞感興趣,而不喜歡languageId或表中的其他字段?如果你是,你需要這樣的:

SELECT word_field FROM Word... 

通配符SELECT查詢返回的一切,但你並不需要檢索你永遠不會使用的數據。

第三,如果你重複多次,你只是在循環中運行相同的查詢嗎?更改LIMIT語句在一個查詢返回更多的話:

-- for 10 words 
... LIMIT 10 

可以將此結果以備後用存儲,而無需重新查詢數據庫。

最後,你可以運行你的查詢,但在它前面用EXPLAIN來概述MySQL運行時的功能。

EXPLAIN SELECT word_field FROM Word... 

利用這一點,你可以準確識別您的查詢運行速度慢。

+0

好的,當我讀到這個問題時,我錯過了關於重複的部分。 OP應該明確地重用排序列表(也防止重複)。 – Tim 2012-02-09 21:40:45

+0

字段是開始的好地方。不幸的是,我只能在循環中選擇一個單詞。否則,我將不得不在我的源代碼中創建一個緩存,一次讀取更多內容,然後從緩存中讀取並重復此過程。我也更新了我的問題 – user219882 2012-02-09 22:00:24

+0

@Tomas是的,你需要從緩存的結果中讀取。但是,幾乎每個數據庫驅動程序都會自動爲你做這件事當你執行一個查詢時,它應該保存你的結果集,直到你釋放它。 – Tim 2012-02-09 22:04:34

0

您可以按照單詞的第一個字母對錶格進行分區,隨機選擇一個字母,然後使用現有的排序在該分區中選擇一個隨機單詞。在現代服務器上對〜50,000行進行排序應該相當快。我認爲大多數數據庫排序都是ng(n),因此1/26的記錄應該排序速度提高50倍以上。分區選擇在性能方面應該可以忽略不計。另一方面,關於重複使用相同列表的模糊評論的評論無疑將在50次左右的執行後獲勝。 編輯:我想我搞砸了我的日誌在Windows Calc,所以我打算去:它應該比以前快26倍以上;)