2011-03-29 71 views
2

這個查詢是非常簡單的,所有我想做的事,就是讓所有在給定的類別由last_updated字段排序的文章:如何避免在多對多查詢中使用「臨時」?

SELECT 
    `articles`.* 
FROM 
    `articles`, 
    `articles_to_categories` 
WHERE 
     `articles`.`id` = `articles_to_categories`.`article_id` 
     AND `articles_to_categories`.`category_id` = 1 
ORDER BY `articles`.`last_updated` DESC 
LIMIT 0, 20; 

但它運行很慢。這裏解釋什麼說:

select_type table     type  possible_keys   key   key_len ref        rows Extra 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
SIMPLE  articles_to_categories ref  article_id,category_id article_id 5  const        5016 Using where; Using temporary; Using filesort 
SIMPLE  articles    eq_ref PRIMARY     PRIMARY  4  articles_to_categories.article_id 1 

有沒有辦法重寫此查詢或添加額外的邏輯來我的PHP腳本,以避免Using temporary; Using filesort和速度的東西呢?

表結構:

*articles* 
id | title | content | last_updated 

*articles_to_categories* 
article_id | category_id 

UPDATE

我已經last_updated索引。我想我的情況在d ocumentation解釋說:

在某些情況下,MySQL不能使用 索引來解決ORDER BY, 儘管它仍然使用索引來找到 WHERE子句匹配的行。 這些情況包括:

用於提取行的鍵不同於ORDER BY中使用的鍵: SELECT * FROM t1 WHERE key2 =常量ORDER BY key1;

要加入很多表,並在ORDER BY的 列不是從第一個非恆定表 用於檢索所有行 。 (這是EXPLAIN輸出 第一表 沒有一個const連接類型。)

,但我仍然不知道如何解決這個問題。

+0

速度有多慢?你使用了什麼引擎? – 2011-03-29 13:29:47

+0

@ f00查詢運行3-5秒,我正在使用innodb(可以在標籤中看到) – 2011-03-29 13:51:51

+0

也許檢查我的例子 - 這是重要的聚類PK的順序。 – 2011-03-29 13:57:12

回答

4

這裏有一個簡單的例子,我有時前做了一個類似的性能相關的問題,它利用的InnoDB的聚集主鍵索引(顯然只適用於InnoDB的!!)

您有3個表格:類別,產品和產品類別如下:

drop table if exists product; 
create table product 
(
prod_id int unsigned not null auto_increment primary key, 
name varchar(255) not null unique 
) 
engine = innodb; 

drop table if exists category; 
create table category 
(
cat_id mediumint unsigned not null auto_increment primary key, 
name varchar(255) not null unique 
) 
engine = innodb; 

drop table if exists product_category; 
create table product_category 
(
cat_id mediumint unsigned not null, 
prod_id int unsigned not null, 
primary key (cat_id, prod_id) -- **note the clustered composite index** !! 
) 
engine = innodb; 

最重要的是product_catgeory集羣組合主鍵的順序,因爲此場景的典型查詢始終由(x,y,z ...)中的cat_id = x或cat_id引導。

我們有500K類別,百萬產品和1.25億產品類別。

select count(*) from category; 
+----------+ 
| count(*) | 
+----------+ 
| 500000 | 
+----------+ 

select count(*) from product; 
+----------+ 
| count(*) | 
+----------+ 
| 1000000 | 
+----------+ 

select count(*) from product_category; 
+-----------+ 
| count(*) | 
+-----------+ 
| 125611877 | 
+-----------+ 

那麼讓我們來看看這個模式如何執行類似於你的查詢。所有的查詢都在空的緩衝區中運行,並且沒有查詢緩存。

select 
p.* 
from 
product p 
inner join product_category pc on 
    pc.cat_id = 4104 and pc.prod_id = p.prod_id 
order by 
p.prod_id desc -- sry dont a date field in this sample table - wont make any difference though 
limit 20; 

+---------+----------------+ 
| prod_id | name   | 
+---------+----------------+ 
| 993561 | Product 993561 | 
| 991215 | Product 991215 | 
| 989222 | Product 989222 | 
| 986589 | Product 986589 | 
| 983593 | Product 983593 | 
| 982507 | Product 982507 | 
| 981505 | Product 981505 | 
| 981320 | Product 981320 | 
| 978576 | Product 978576 | 
| 973428 | Product 973428 | 
| 959384 | Product 959384 | 
| 954829 | Product 954829 | 
| 953369 | Product 953369 | 
| 951891 | Product 951891 | 
| 949413 | Product 949413 | 
| 947855 | Product 947855 | 
| 947080 | Product 947080 | 
| 945115 | Product 945115 | 
| 943833 | Product 943833 | 
| 942309 | Product 942309 | 
+---------+----------------+ 
20 rows in set (0.70 sec) 

explain 
select 
p.* 
from 
product p 
inner join product_category pc on 
    pc.cat_id = 4104 and pc.prod_id = p.prod_id 
order by 
p.prod_id desc -- sry dont a date field in this sample table - wont make any diference though 
limit 20; 

+----+-------------+-------+--------+---------------+---------+---------+------------------+------+----------------------------------------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra          | 
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+----------------------------------------------+ 
| 1 | SIMPLE  | pc | ref | PRIMARY  | PRIMARY | 3  | const   | 499 | Using index; Using temporary; Using filesort | 
| 1 | SIMPLE  | p  | eq_ref | PRIMARY  | PRIMARY | 4  | vl_db.pc.prod_id | 1 |            | 
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+----------------------------------------------+ 
2 rows in set (0.00 sec) 

因此,這是0.70秒冷。。

希望這有助於:)

編輯

剛剛讀給我的評論您的回覆上面看來你有兩個選擇,使:

create table articles_to_categories 
(
article_id int unsigned not null, 
category_id mediumint unsigned not null, 
primary key(article_id, category_id), -- good for queries that lead with article_id = x 
key (category_id) 
) 
engine=innodb; 

或。

create table categories_to_articles 
(
article_id int unsigned not null, 
category_id mediumint unsigned not null, 
primary key(category_id, article_id), -- good for queries that lead with category_id = x 
key (article_id) 
) 
engine=innodb; 

取決於你典型查詢,爲你如何定義你的集羣PK。

+0

謝謝你這樣詳細的答案。我建議您按照您的建議創建一個索引 - 這兩個PRIMARY鍵現在都在用戶的查詢中,就像您的示例中一樣。但是,不幸的是,查詢仍然需要3秒鐘,並使用臨時表。 – 2011-03-29 14:26:03

+0

你的意思是你已經把你的主鍵從article_id,category_id改成了category_id,article_id?在EDIT中查看我的categories_to_articles表。如果一切都失敗,請發佈您的表格定義... – 2011-03-29 14:37:03

0

我假設你已經在你的分貝以下:

1)文章 - > ID是主鍵

2)articles_to_categories - > article_id的是文章的外鍵 - > ID

3),你可以創建CATEGORY_ID

+0

根據EXPLAIN category_id已經是一個可能的密鑰。 – Jacob 2011-03-29 12:21:31

1

指數你應該能夠通過避免文件排序上articles.last_updated增加的關鍵。 MySQL需要ORDER BY操作的filesort,但只要您通過索引列進行排序(有一些限制),就可以不使用filesort。

對於更多的信息,請看這裏:http://dev.mysql.com/doc/refman/5.0/en/order-by-optimization.html

+0

Actualy,我有last_updated索引。我不知道爲什麼索引不被使用。也許MySQL希望看到像(id,last_updated)這樣的東西? – 2011-03-29 12:26:59

+0

你確實是對的,刪除ORDER BY查詢非常快。現在我只需要了解如何使MYSQL使用索引:) – 2011-03-29 12:31:29

+0

我已經嘗試創建(id,last_updated)索引,但MySQL仍然使用主要的:/ – 2011-03-29 12:43:06

0
ALTER TABLE articles ADD INDEX (last_updated); 
ALTER TABLE articles_to_categories ADD INDEX (article_id); 

應該這樣做。正確的計劃是使用第一個索引找到前幾個記錄,並使用第二個索引進行JOIN。如果它不起作用,請嘗試使用STRAIGHT_JOIN或其他方法來強制執行正確的索引使用。

+0

這兩列已經索引。 – 2011-03-29 12:44:14

+0

然後強制使用它們。但是,由於條件爲'articles_to_categories.category_id = 1',它可能無法正常工作。對於5k行使用臨時和文件可能是最佳的。 – maaartinus 2011-03-29 14:45:16

相關問題