2012-12-13 59 views
15

我正在使用Drupal 6與MySQL版本5.0.95並處於僵局,其中顯示基於最近文章日期的內容的一個查詢速度變慢並且由於正在使用的頻率共同殺害現場表演。有問題的查詢如下:無法優化使用ORDER BY子句的MySQL查詢

 SELECT n.nid, 
      n.title, 
      ma.field_article_date_format_value, 
      ma.field_article_summary_value 
     FROM node n 
INNER JOIN content_type_article ma ON n.nid=ma.nid 
INNER JOIN term_node tn   ON n.nid=tn.nid 
     WHERE tn.tid= 153 
     AND n.status=1 
    ORDER BY ma.field_article_date_format_value DESC 
     LIMIT 0, 11; 

查詢的EXPLAIN顯示如下的結果:

+----+-------------+-------+--------+--------------------------+---------+---------+----------------------+-------+---------------------------------+ 
| id | select_type | table | type | possible_keys   | key  | key_len | ref     | rows | Extra       | 
+----+-------------+-------+--------+--------------------------+---------+---------+----------------------+-------+---------------------------------+ 
| 1 | SIMPLE  | tn | ref | PRIMARY,nid    | PRIMARY | 4  | const    | 19006 | Using temporary; Using filesort | 
| 1 | SIMPLE  | ma | ref | nid,ix_article_date  | nid  | 4  | drupal_mm_stg.tn.nid |  1 |         | 
| 1 | SIMPLE  | n  | eq_ref | PRIMARY,node_status_type | PRIMARY | 4  | drupal_mm_stg.ma.nid |  1 | Using where      | 
+----+-------------+-------+--------+--------------------------+---------+---------+----------------------+-------+---------------------------------+ 

該查詢似乎相對簡單和直接,並檢索屬於一個類別的文章(項)153並且是狀態1(已發佈)。但顯然使用臨時表和使用filesort意味着查詢必然會失敗,我所學到的瀏覽它。

從ORDER BY子句中刪除field_article_date_format_value可解決Using temporary;使用filesort可以減少查詢執行時間,但是這是必需的,不能交易,不幸的是,對於站點性能同樣如此。

我的預感是,大部分麻煩來自term_node表,它將文章映射到類別,並且是一個多關係表,這意味着如果文章X與5個類別C1關聯...... C5它將有5個條目在該表中,此表來自開箱即用的drupal。

重DB內容打交道是通過一些類似的查詢( When ordering by date desc, "Using temporary" slows down queryMySQL performance optimization: order by datetime field)我試圖創建的,其時間字段在ORDER沿着BY子句中使用content_type_article一個綜合指數的新的東西給我,去與另一個關鍵(NID),並試圖強制INDEX。

SELECT n.nid, n.title, 
      ma.field_article_date_format_value, 
      ma.field_article_summary_value 
     FROM node n 
INNER JOIN content_type_article ma FORCE INDEX (ix_article_date) ON n.nid=ma.nid 
INNER JOIN term_node tn ON n.nid=tn.nid 
    WHERE tn.tid= 153 
     AND n.status=1 
    ORDER BY ma.field_article_date_format_value DESC 
    LIMIT 0, 11; 

結果和下面的EXPLAIN查詢似乎沒有多大幫助

+----+-------------+-------+--------+--------------------------+-----------------+---------+----------------------+-------+---------------------------------+ 
| id | select_type | table | type | possible_keys   | key    | key_len | ref     | rows | Extra       | 
+----+-------------+-------+--------+--------------------------+-----------------+---------+----------------------+-------+---------------------------------+ 
| 1 | SIMPLE  | tn | ref | PRIMARY,nid    | PRIMARY   | 4  | const    | 18748 | Using temporary; Using filesort | 
| 1 | SIMPLE  | ma | ref | ix_article_date   | ix_article_date | 4  | drupal_mm_stg.tn.nid |  1 |         | 
| 1 | SIMPLE  | n  | eq_ref | PRIMARY,node_status_type | PRIMARY   | 4  | drupal_mm_stg.ma.nid |  1 | Using where      | 
+----+-------------+-------+--------+--------------------------+-----------------+---------+----------------------+-------+---------------------------------+ 

字段n.nid,ca.nid,ma.field_article_date_format_value都編入索引。使用Limit 0,11查詢數據庫需要大約7-10秒的ORDER BY子句,但是如果沒有它,查詢幾乎不需要一秒鐘。數據庫引擎是MyISAM。任何幫助,將不勝感激。

任何答案都可以幫助我像普通的那樣獲得這個查詢(與查詢沒有按日期排序的速度相同),那將是很棒的。我嘗試創建組合查詢nidfield_article_date_format_value並在查詢中使用並沒有幫助原因。我願意提供有關該問題的更多信息和任何新建議。

回答

2

MySQL正在「優化」您的查詢,以便它首先從term_node表中進行選擇,即使您指定先從節點中進行選擇。不知道數據,我不確定哪個是最佳方式。 term_node表肯定是您的性能問題,因爲從那裏選擇了約19,000條記錄。

沒有ORDER BY的限制幾乎總是更快,因爲MySQL一找到指定的限制就會停止。使用ORDER BY,它首先必須找到所有記錄並對它們進行排序,然後獲得指定的限制。

簡單的事情就是將您的WHERE條件移動到JOIN子句中,這是它應該在的位置。該過濾器特定於正在連接的表格。這將確保MySQL不會錯誤地優化它。

INNER JOIN term_node tn ON n.nid=tn.nid AND tn.tid=153 

一個更復雜的事情是在term_node表上做一個SELECT並對其進行JOIN。這就是所謂的DERIVED TABLE,你會在EXPLAIN中看到它的定義。既然你說過它是多對多的,我添加了DISTINCT參數來減少要加入的記錄數量。

SELECT ... 
FROM node n 
INNER JOIN content_type_article ma FORCE INDEX (ix_article_date) ON n.nid=ma.nid 
INNER JOIN (SELECT DISTINCT nid FROM term_node WHERE tid=153) tn ON n.nid=tn.nid 
WHERE n.status=1 
ORDER BY ma.field_article_date_format_value DESC 
LIMIT 0,11 

MySQL 5.0對派生表有一些限制,所以這可能不起作用。雖然有解決辦法。

+0

感謝您的迴應,儘管term_node是N:N,對於特定的術語,所得到的節點在我的情況下將是截然不同的。我之前嘗試過派生表方法,但查詢執行與傳統方法幾乎相同。 – optimusprime619

4

Using temporary; Using filesort只意味着MySQL需要構造一個臨時結果表並對其進行排序以獲得您需要的結果。這通常是您用於獲取最新發布的ORDER BY ... DESC LIMIT 0,n構造的結果。它本身並不是失敗的標誌。看到這個:http://www.mysqlperformanceblog.com/2009/03/05/what-does-using-filesort-mean-in-mysql/

這裏有一些事情要嘗試。我不完全相信他們會工作。沒有你的數據進行實驗就很難知道。

content_type_article.field_article_date_format_value上有BTREE索引嗎?如果是這樣,那可能有幫助。

您是否需要顯示最近的11篇文章?或者你能顯示最近一週或最近一個月出現的11篇最近的文章嗎?如果是這樣,你可以添加這條線到你的WHERE條款。它會按日期過濾你的東西,而不必一直追溯到匹配文章的時間。如果您有一個老的Drupal站點,這將特別有用。

AND ma.field_article_date_format_value >= (CURRENT_TIME() - INTERVAL 1 MONTH) 

首先,嘗試翻轉INNER JOIN操作的順序。其次,將tid = 153納入連接標準。這可能會減少您需要排序的臨時表的大小。總之我的建議如下:

SELECT n.nid, 
      n.title, 
      ma.field_article_date_format_value, 
      ma.field_article_summary_value 
     FROM node n 
INNER JOIN term_node tn   ON (n.nid=tn.nid AND tn.tid = 153) 
INNER JOIN content_type_article ma ON n.nid=ma.nid 
    WHERE n.status=1 
     AND ma.field_article_date_format_value >= (CURRENT_TIME() - INTERVAL 1 MONTH) 
    ORDER BY ma.field_article_date_format_value DESC 
    LIMIT 0, 11; 

那些被

+0

感謝您的反饋意見,是BTree的地方,我擔心的是,有什麼辦法可以否定臨時表的使用和索引排序..業務邏輯確實需要顯示最近的文章而不是一週或一個月 – optimusprime619

6

在查詢縱觀和解釋,好像具有where子句中n.status = 1是使搜索很效率低下,因爲您需要返回由聯接定義的整個集合,然後應用status = 1.請嘗試從term_node表開始連接,該表由WHERE立即進行過濾,然後使聯接立即添加狀態條件。試試看,並告訴我它是怎麼回事。

SELECT n.nid, n.title, 
      ma.field_article_date_format_value, 
      ma.field_article_summary_value 
     FROM term_node tn 
INNER JOIN node n ON n.nid=tn.nid AND n.status=1 
INNER JOIN content_type_article ma FORCE INDEX (ix_article_date) ON n.nid=ma.nid 
    WHERE tn.tid= 153 
    ORDER BY ma.field_article_date_format_value DESC 
    LIMIT 0, 11; 
4

1)覆蓋索引

我想簡單的答案可能是 「覆蓋索引」。

特別是在content_type_article表中。 「覆蓋索引」在ORDER BY中具有表達式作爲前導列,並且包括查詢引用的所有列。下面是我創建的索引(在我的測試表):

CREATE INDEX ct_article_ix9 
    ON content_type_article 
     (field_article_date_format_value, nid, field_article_summary_value); 

下面是摘錄的解釋,我從查詢得到(後我建例如表,使用InnoDB引擎,包括對每個表覆蓋索引):

_type table type key    ref   Extra      
------ ----- ----- -------------- ----------- ------------------------ 
SIMPLE ma index ct_article_ix9 NULL   Using index 
SIMPLE n ref node_ix9   ma.nid  Using where; Using index 
SIMPLE tn ref term_node_ix9 n.nid,const Using where; Using index 

注意,有沒有在計劃中所示'Using filesort',並且計劃顯示'Using index'查詢中,這基本上意味着所有查詢所需的數據都從索引頁檢索引用的每個表,無需引用基礎表中的任何頁面。 (你的表比我的測試表中多了很多行,但如果你能得到一個解釋計劃看起來像這樣,你可能會得到更好的性能。)


爲了完整起見,這裏的整個EXPLAIN輸出:

+----+-------------+-------+-------+---------------+----------------+---------+---------------------+------+--------------------------+ 
| id | select_type | table | type | possible_keys | key   | key_len | ref     | rows | Extra     | 
+----+-------------+-------+-------+---------------+----------------+---------+-------- ------------+------+--------------------------+ 
| 1 | SIMPLE  | ma | index | NULL   | ct_article_ix9 | 27  | NULL    | 1 | Using index    | 
| 1 | SIMPLE  | n  | ref | node_ix9  | node_ix9  | 10  | testps.ma.nid,const | 11 | Using where; Using index | 
| 1 | SIMPLE  | tn | ref | term_node_ix9 | term_node_ix9 | 10  | testps.n.nid,const | 11 | Using where; Using index | 
+----+-------------+-------+-------+---------------+----------------+---------+---------------------+------+--------------------------+ 
3 rows in set (0.00 sec) 

我做任何更改您的查詢,除了省略FORCE INDEX提示。下面是我在查詢中引用其他兩個表創建了另外兩個「覆蓋索引」:

CREATE INDEX node_ix9 
    ON node (`nid`,`status`,`title`); 

CREATE INDEX term_node_ix9 
    ON term_node (nid,tid); 

(請注意,如果nid是在node表聚集鍵,你可能不需要覆蓋索引在節點表上)。


2)使用相關的子查詢來代替連接?

如果前面的想法沒有改善任何東西,那麼作爲另一種選擇,由於原始查詢最多返回11行,因此您可以嘗試重寫查詢以避免連接操作,而是使用關聯子查詢。類似於下面的查詢。

請注意,此查詢與原始查詢顯着不同。不同的是,使用此查詢,context_type_article表中的一行將只返回一次。通過使用連接的查詢,該表中的一行可以與來自nodeterm_node表的多個行匹配,這些表將不止一次返回同一行。這可能會被視爲可取或不可取,它實際上取決於基數,以及結果集是否符合規範。

SELECT (SELECT n2.nid 
      FROM node n2 
      WHERE n2.nid = ma.nid 
      AND n2.status = 1 
      LIMIT 1 
     ) AS `nid` 
     , (SELECT n3.title 
      FROM node n3 
      WHERE n3.nid = ma.nid 
      AND n3.status = 1 
      LIMIT 1 
     ) AS `title` 
     , ma.field_article_date_format_value 
     , ma.field_article_summary_value 
    FROM content_type_article ma 
    WHERE EXISTS 
     (SELECT 1 
      FROM node n1 
      WHERE n1.nid = ma.nid 
      AND n1.status = 1 
     )     
    AND EXISTS 
     (SELECT 1 
      FROM term_node tn 
      WHERE tn.nid = ma.nid 
      AND tn.tid = 153 
     ) 
    ORDER BY ma.field_article_date_format_value DESC 
    LIMIT 0,11 

(有時候,使用這種類型的「orrelated子查詢」的查詢可以比不加入操作的等效查詢相當差的表現,但在某些情況下,這樣的查詢可以實際,更好的表現尤其是考慮返回一個非常有限的行數)

下面是該查詢的解釋輸出:

+----+--------------------+-------+-------+---------------+----------------+---------+---------------------+------+--------------------------+ 
| id | select_type  | table | type | possible_keys | key   | key_len | ref     | rows | Extra     | 
+----+--------------------+-------+-------+---------------+----------------+---------+---------------------+------+--------------------------+ 
| 1 | PRIMARY   | ma | index | NULL   | ct_article_ix9 | 27  | NULL    | 11 | Using where; Using index | 
| 5 | DEPENDENT SUBQUERY | tn | ref | term_node_ix9 | term_node_ix9 | 10  | testps.ma.nid,const | 13 | Using where; Using index | 
| 4 | DEPENDENT SUBQUERY | n1 | ref | node_ix9  | node_ix9  | 10  | testps.ma.nid,const | 12 | Using where; Using index | 
| 3 | DEPENDENT SUBQUERY | n3 | ref | node_ix9  | node_ix9  | 10  | testps.ma.nid,const | 12 | Using where; Using index | 
| 2 | DEPENDENT SUBQUERY | n2 | ref | node_ix9  | node_ix9  | 10  | testps.ma.nid,const | 12 | Using where; Using index | 
+----+--------------------+-------+-------+---------------+----------------+---------+---------------------+------+--------------------------+ 
5 rows in set (0.00 sec) 

注意再次,每個接入是'Using index',這意味着查詢是滿足直接從索引頁,拉澤而不必訪問基礎表中的任何數據頁面。


例表

這裏有例子的表,我建立和填充(與指數一起)的基礎上,從你的問題的信息:

CREATE TABLE `node` (`id` INT PRIMARY KEY, `nid` INT, `title` VARCHAR(10),`status` INT); 
CREATE INDEX node_ix9 ON node (`nid`,`status`,`title`); 
INSERT INTO `node` VALUES (1,1,'foo',1),(2,2,'bar',0),(3,3,'fee',1),(4,4,'fi',0),(5,5,'fo',1),(6,6,'fum',0),(7,7,'derp',1); 
INSERT INTO `node` SELECT id+7,nid+7,title,`status` FROM node; 
INSERT INTO `node` SELECT id+14,nid+14,title,`status` FROM node; 
INSERT INTO `node` SELECT id+28,nid+28,title,`status` FROM node; 
INSERT INTO `node` SELECT id+56,nid+56,title,`status` FROM node; 

CREATE TABLE content_type_article (id INT PRIMARY KEY, nid INT, field_article_date_format_value DATETIME, field_article_summary_value VARCHAR(10)); 
CREATE INDEX ct_article_ix9 ON content_type_article (field_article_date_format_value, nid, field_article_summary_value); 
INSERT INTO content_type_article VALUES (1001,1,'2012-01-01','foo'),(1002,2,'2012-01-02','bar'),(1003,3,'2012-01-03','fee'),(1004,4,'2012-01-04','fi'),(1005,5,'2012-01-05','fo'),(1006,6,'2012-01-06','fum'),(1007,7,'2012-01-07','derp'); 
INSERT INTO content_type_article SELECT id+7,nid+7, DATE_ADD(field_article_date_format_value,INTERVAL 7 DAY),field_article_summary_value FROM content_type_article; 
INSERT INTO content_type_article SELECT id+14,nid+14, DATE_ADD(field_article_date_format_value,INTERVAL 14 DAY),field_article_summary_value FROM content_type_article; 
INSERT INTO content_type_article SELECT id+28,nid+28, DATE_ADD(field_article_date_format_value,INTERVAL 28 DAY),field_article_summary_value FROM content_type_article; 
INSERT INTO content_type_article SELECT id+56,nid+56, DATE_ADD(field_article_date_format_value,INTERVAL 56 DAY),field_article_summary_value FROM content_type_article; 

CREATE TABLE term_node (id INT, tid INT, nid INT); 
CREATE INDEX term_node_ix9 ON term_node (nid,tid); 
INSERT INTO term_node VALUES (2001,153,1),(2002,153,2),(2003,153,3),(2004,153,4),(2005,153,5),(2006,153,6),(2007,153,7); 
INSERT INTO term_node SELECT id+7, tid, nid+7 FROM term_node; 
INSERT INTO term_node SELECT id+14, tid, nid+14 FROM term_node; 
INSERT INTO term_node SELECT id+28, tid, nid+28 FROM term_node; 
INSERT INTO term_node SELECT id+56, tid, nid+56 FROM term_node; 
1

你真的想避免如果可以通過利用預先排序的索引來進行排序操作。

要想知道這是否可行,可以將您的數據非規範化爲單個表,並確保必須包含在WHERE子句中的所有內容都可以用單值指定。例如如果您必須在其中一列上使用IN子句,那麼排序是不可避免的。

下面是一些樣本數據的截圖:

Sample data denormalised and sorted by tid, status DESC, date DESC

所以,如果你確實有你的數據去歸一化,你可以使用單值TID和狀態按日期倒序排列查詢,然後。這將意味着在這種情況下,以下指數將很好地工作:

create index ix1 on denormalisedtable(tid, status, date desc); 

如果你有這個,你的查詢將只命中了前10行,絕不會需要排序。

所以 - 你怎麼會得到相同的性能,而無需denormalising ...

我想你應該能夠使用STRAIGHT_JOIN條款迫使是,MySQL從表中選擇的順序 - 你要得到它從最後排列的表格中選擇。

試試這個:

SELECT n.nid, 
     n.title, 
     ma.field_article_date_format_value, 
     ma.field_article_summary_value 
FROM node n 
STRAIGHT_JOIN term_node tn   ON n.nid=tn.nid 
STRAIGHT_JOIN content_type_article ma ON n.nid=ma.nid 
WHERE tn.tid= 153 
    AND n.status=1 
ORDER BY ma.field_article_date_format_value DESC 
LIMIT 0, 11; 

的想法是讓MySQL從節點表從term_node表終於從content_type_article表中選擇,然後再(包含要排序的列的表) 。

這最後一次連接是您最重要的連接,您希望使用索引進行連接,以便LIMIT子句無需對數據進行排序即可工作。

這個單一指標可能做的伎倆:

create index ix1 on content_type_article(nid, field_article_date_format_value desc); 

create index ix1 on content_type_article(nid, field_article_date_format_value desc, field_article_summary_value); 

(爲覆蓋索引)

我說可能,因爲我不充分了解MySQL優化器知道它是否足夠聰明,可以處理多個'nid'列值,這些列值將被送入content_type_article而不需要求助於數據。從邏輯上講,它應該能夠快速工作 - 例如,如果5個nid值被輸入到最終的content_type_article表中,那麼它應該能夠直接從索引中獲得每個值的前10位,並將結果合併在一起,然後選擇最後的前10位,即從此讀取總共50行桌上放滿了你目前看到的完整19006。

讓我知道它是怎麼回事。

如果它適合您,可以使用其他表上的覆蓋索引進一步優化,以加速前兩個連接。