Как избежать «использования временного» в запросах «многие ко многим»? - PullRequest
2 голосов
/ 29 марта 2011

Этот запрос очень прост, все, что я хочу сделать, это получить все статьи в данной категории, упорядоченные по полю 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;

Но он работает очень медленно.Вот что говорит EXPLAIN:

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

ОБНОВЛЕНИЕ

У меня есть last_updated проиндексировано.Я предполагаю, что моя ситуация объяснена в d ocumentation :

В некоторых случаях MySQL не может использовать индексы для разрешения ORDER BY, хотя он все еще использует индексы для поиска строк, которые соответствуютГДЕ оговорка.Эти случаи включают в себя следующее:

Ключ, используемый для извлечения строк, не совпадает с ключом, используемым в ORDER BY: SELECT * FROM t1 WHERE key2 = константа ORDER BY key1;

Вы объединяете много таблиц, и столбцы в ORDER BY не все из первой непостоянной таблицы, которая используется для получения строк.(Это первая таблица в выводе EXPLAIN, которая не имеет типа соединения const.)

, но я все еще не знаю, как это исправить.

Ответы [ 4 ]

4 голосов
/ 29 марта 2011

Вот упрощенный пример, который я сделал для аналогичного вопроса, связанного с производительностью, когда-то назад, который использует преимущества кластерных индексов первичного ключа 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 , поскольку типичные запросы для этого сценария всегда приводят к cat_id = x или cat_id в (x, y, z ...).

У нас есть 500K категорий, 1 миллион продуктов и 125 миллионов категорий продуктов.

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

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

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

Итак, давайте посмотрим, как эта схема работает для запроса, аналогичного вашему. Все запросы выполняются в холодном режиме (после перезапуска mysql) с пустыми буферами и без кэширования запросов.

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 секунды холодно - ой.

Надеюсь, это поможет:)

EDIT

Только что прочитав ваш ответ на мой комментарий выше, кажется, у вас есть один из двух вариантов:

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.

1 голос
/ 29 марта 2011

Вы можете избежать сортировки файлов, добавив ключ на *1001* 1003 *.MySQL требуется файловая сортировка для операции ORDER BY, но она может делать это без файловой сортировки, если вы упорядочиваете по индексированному столбцу (с некоторыми ограничениями).

Для получения дополнительной информации см. Здесь: http://dev.mysql.com/doc/refman/5.0/en/order-by-optimization.html

0 голосов
/ 29 марта 2011
ALTER TABLE articles ADD INDEX (last_updated);
ALTER TABLE articles_to_categories ADD INDEX (article_id);

должен это сделать. Правильный план состоит в том, чтобы найти первые несколько записей, используя первый индекс, и выполнить JOIN, используя второй. Если это не работает, попробуйте STRAIGHT_JOIN или что-то еще, чтобы обеспечить правильное использование индекса.

0 голосов
/ 29 марта 2011

Я предполагаю, что вы сделали следующее в вашей базе данных:

1) статьи -> id является первичным ключом

2) article_to_categories -> article_id - внешний ключ статей -> id

3) вы можете создать индекс для category_id

...