2016-09-16 66 views
0

在一個項目中,我有一個帶有兩個大表的數據庫,「terminosnoticia」有4億行,「noticia」3百萬行。我有一個查詢我想,使打火機(從10秒花費400秒):優化涉及數百萬行的mysql查詢

SELECT noticia_id, termino_id 
     FROM noticia 
     LEFT JOIN terminosnoticia on terminosnoticia.noticia_id=noticia.id AND termino_id IN (7818,12345) 
    WHERE noticia.fecha BETWEEN '2016-09-16 00:00' AND '2016-09-16 10:00' 
     AND noticia_id is not null AND termino_id is not null;` 

唯一可行的解​​決方案,我要探討的是進行非規範化的數據庫,包括在大表中的「出生日期」字段,但是,這會使指數大小倍增。

解釋計劃:爲建議

+----+-------------+-----------------+--------+-----------------------+------------+---------+-----------------------------------------+-------+-------------+ 
| id | select_type | table   | type | possible_keys   | key  | key_len | ref          | rows | Extra  | 
+----+-------------+-----------------+--------+-----------------------+------------+---------+-----------------------------------------+-------+-------------+ 
| 1 | SIMPLE  | terminosnoticia | ref | noticia_id,termino_id | termino_id | 4  | const         | 58480 | Using where | 
| 1 | SIMPLE  | noticia   | eq_ref | PRIMARY,fecha   | PRIMARY | 4  | db_resumenes.terminosnoticia.noticia_id |  1 | Using where | 
+----+-------------+-----------------+--------+-----------------------+------------+---------+-----------------------------------------+-------+-------------+ 

更改查詢和創建索引,解釋計劃,現在是:

+----+-------------+-------+--------+-------------------------------------------+---------------------+---------+---------------------------+-------+-------------+ 
| id | select_type | table | type | possible_keys        | key     | key_len | ref      | rows | Extra  | 
+----+-------------+-------+--------+-------------------------------------------+---------------------+---------+---------------------------+-------+-------------+ 
| 1 | SIMPLE  | T  | ref | noticia_id,termino_id,terminosnoticia_cpx | terminosnoticia_cpx | 4  | const      | 60600 | Using index | 
| 1 | SIMPLE  | N  | eq_ref | PRIMARY,fecha        | PRIMARY    | 4  | db_resumenes.T.noticia_id |  1 | Using where | 
+----+-------------+-------+--------+-------------------------------------------+---------------------+---------+---------------------------+-------+-------------+ 

但執行時間不會變化太多...

有什麼想法?

+3

'LEFT JOIN x WHERE x IS NOT NULL'與'INNER JOIN x ...'相同 – Strawberry

+0

'noticia_id'是兩個表中的索引列嗎? –

+0

noticia_id在terminosnoticia中有索引,noticia.id是主要的 – yoprogramo

回答

4

草莓指出,由具有「和」你的where子句NOT NULL 是相同的常規INNER JOIN和可降至。

SELECT 
     N.id as noticia_id, 
     T.termino_id 
    FROM 
     noticia N USING INDEX (fecha) 
     JOIN terminosnoticia T 
      on N.id = T.noticia_id 
      AND T.termino_id IN (7818,12345) 
    WHERE 
     N.fecha BETWEEN '2016-09-16 00:00' AND '2016-09-16 10:00' 

現在,說和應用的別名,我建議以下覆蓋索引作爲

table   index 
Noticia   (fecha, id) 
terminosnoticia (noticia_id, termino_id) 

這樣的查詢可以得到所有的直接指標,而不是結果有去原始數據頁面來限定其他字段。

+0

謝謝,但以這種方式創建索引並執行查詢不會改變查詢的時間,因爲mysql選擇了noticias的主鍵而不是fecha鍵。我修改了在「noticia N」附近添加「USING INDEX(fecha)」的查詢,並且這樣做了...現在查詢速度更快。 我選擇這個作爲正確的答案(如果可以,請在te查詢中添加USING INDEX)。 – yoprogramo

1

假設noticia_idnoticia的主鍵,我想補充以下指標:

create index noticia_fecha_idx on noticia(fecha); 
create index terminosnoticia_id_noticia_idx on terminosnoticia(noticia_id); 

並再次嘗試查詢。

是否包含查詢的當前執行計劃。這可能有助於幫助你找出這個問題。

+0

在問題中添加執行計劃...每個字段都有其索引 – yoprogramo

+0

「terminosnoticia」表上的(noticia_id,termino_id)是否有索引?您是否在使用'terminosnoticia'來執行全文搜索? –

+0

你的意思是創建一個複合索引? 在terminosnoticia上創建索引terminosnoticia_cpx(noticia_id,termino_id) ? – yoprogramo

0

試試這個:

SELECT tbl1.noticia_id, tbl1.termino_id FROM 
(SELECT FROM terminosnoticia WHERE 
terminosnoticia.termino_id IN (7818,12345) 
AND terminosnoticia.noticia_id is not null 
) tbl1 INNER JOIN 
(SELECT id FROM noticia 
    WHERE noticia.fecha 
    BETWEEN '2016-09-16 00:00' AND '2016-09-16 10:00' 
) tbl2 ON tbl1.id=tbl2.noticia.id 
+0

在mysql中,子選擇不能使用索引... – yoprogramo

+0

由於MySQL將實現內聯視圖,很難弄清楚這將如何提供更好的性能。考慮到實現派生表的開銷,以及針對這些表內容以及舊版本MySQL的外部查詢,派生表永遠不會編入索引。當你說「嘗試這個」時,你可以給出一些理由,爲什麼OP應該這樣做? – spencer7593

0

我們假設noticia_idtermino_idterminosnoticia表中的列。 (如果所有的列引用都是合格的與表名稱或短表別名,我們不必猜測)。

爲什麼這是外連接? WHERE子句中的謂詞將從terminosnoticia的列中排除具有NULL值的行。這將消除連接的「外部性」。

如果我們把它作爲一個內部連接來寫,WHERE子句中的那些謂詞是多餘的。我們已經知道noticia_id不會是NULL(如果它滿足ON子句中的相等謂詞)。與termino_id相同,如果它等於IN列表中的值,則不會爲NULL。

我相信這個查詢將返回同樣的結果:

SELECT t.noticia_id 
     , t.termino_id 
    FROM noticia n 
    JOIN terminosnoticia t 
     ON t.noticia_id = n.id 
    AND t.termino_id IN (7818,12345) 
    WHERE n.fecha BETWEEN '2016-09-16 00:00' AND '2016-09-16 10:00' 

現在剩下的就是搞清楚,如果有任何隱式數據類型轉換。我們看不到termino_id的數據類型。所以我們不知道這是否定義爲數字。如果不是這樣,這是個壞消息,因爲MySQL必須對錶中的每一行執行一次數字轉換,因此它可以對數字文字進行比較。

我們看不到noticia_id的數據類型,以及它是否與它正在比較的列的數據類型匹配,id列與noticia表匹配。我們也看不到數據類型fecha。基於謂詞之間的字符串文字,它看起來可能是DATETIME或TIMESTAMP。但這只是一個猜測。我們不知道,因爲我們沒有可用的表格定義。

一旦我們證實有沒有那麼要咬我們的任何隱式數據類型轉換...

與內加入(如上)查詢,以合理的性能最好的射手很可能會與MySQL有效利用覆蓋索引。 (A 覆蓋索引允許的MySQL直接從索引塊滿足查詢,而無需查找底層表頁。)

由於DRApp的答案已經指出,對於覆蓋索引,爲這個特殊的最佳人選查詢時,會是:

... ON noticia (fecha, id) 
... ON terminosnoticia (noticia_id, termino_id) 

已經在相同的順序也適合那些相同的前導列,因而可能會使這些指標多餘的索引。

這些索引的添加會使其他索引變得冗餘。

第一個索引將與... ON noticia (fecha)重複。假設索引沒有強制執行一個UNIQUE約束,它可能會被丟棄。任何有效使用該索引的查詢都可以使用新索引,因爲fecha是新索引中的主要列。

類似地,索引... ON terminosnoticia (noticia_id)將是多餘的。同樣,假設它不是唯一索引,強制執行UNIQUE約束,那麼該索引也可以被刪除。