2011-03-29 65 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

相关问题