Сложный запрос MySQL все еще использует filesort, хотя существуют индексы

У меня есть таблица Joomla с тысячами строк контента (около 3 миллионов). У меня возникли проблемы с переписыванием запросов базы данных как можно быстрее при запросе таблиц.

Вот мой полный запрос:

SELECT cc.title AS category, a.id, a.title, a.alias, a.title_alias, a.introtext, a.fulltext, a.sectionid, a.state, a.catid, a.created, a.created_by, a.created_by_alias, a.modified, a.modified_by, a.checked_out, a.checked_out_time, a.publish_up, a.publish_down, a.attribs, a.hits, a.images, a.urls, a.ordering, a.metakey, a.metadesc, a.access, CASE WHEN CHAR_LENGTH(a.alias) THEN CONCAT_WS(":", a.id, a.alias) ELSE a.id END AS slug, CASE WHEN CHAR_LENGTH(cc.alias) THEN CONCAT_WS(":", cc.id, cc.alias) ELSE cc.id END AS catslug, CHAR_LENGTH( a.`fulltext` ) AS readmore, u.name AS author, u.usertype, g.name AS groups, u.email AS author_email
FROM j15_content AS a
LEFT JOIN j15_categories AS cc
ON a.catid = cc.id
LEFT JOIN j15_users AS u
ON u.id = a.created_by
LEFT JOIN j15_groups AS g
ON a.access = g.id
WHERE 1
AND a.access <= 0
AND a.catid = 108
AND a.state = 1
AND ( publish_up = '0000-00-00 00:00:00' OR publish_up <= '2012-02-08 00:16:26' )
AND ( publish_down = '0000-00-00 00:00:00' OR publish_down >= '2012-02-08 00:16:26' )
ORDER BY a.title, a.created DESC
LIMIT 0, 10

Вот результат EXPLAIN:

 +----+-------------+-------+--------+-------------------------------------------------------+-----------+---------+---------------------------+---------+-----------------------------+
| id | select_type | table | type   | possible_keys                                         | key       | key_len | ref                       | rows    | Extra                       |
+----+-------------+-------+--------+-------------------------------------------------------+-----------+---------+---------------------------+---------+-----------------------------+
|  1 | SIMPLE      | a     | ref    | idx_access,idx_state,idx_catid,idx_access_state_catid | idx_catid | 4       | const                     | 3108187 | Using where; Using filesort |
|  1 | SIMPLE      | cc    | const  | PRIMARY                                               | PRIMARY   | 4       | const                     |       1 |                             |
|  1 | SIMPLE      | u     | eq_ref | PRIMARY                                               | PRIMARY   | 4       | database.a.created_by     |       1 |                             |
|  1 | SIMPLE      | g     | eq_ref | PRIMARY                                               | PRIMARY   | 1       | database.a.access         |       1 |                             |
+----+-------------+-------+--------+-------------------------------------------------------+-----------+---------+---------------------------+---------+-----------------------------+

И чтобы показать, какие индексы существуют, SHOW INDEX FROM j15_content:

+-------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table       | Non_unique | Key_name               | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| j15_content |          0 | PRIMARY                |            1 | id          | A         |     3228356 |     NULL | NULL   |      | BTREE      |         |
| j15_content |          1 | idx_section            |            1 | sectionid   | A         |           2 |     NULL | NULL   |      | BTREE      |         |
| j15_content |          1 | idx_access             |            1 | access      | A         |           1 |     NULL | NULL   |      | BTREE      |         |
| j15_content |          1 | idx_checkout           |            1 | checked_out | A         |           2 |     NULL | NULL   |      | BTREE      |         |
| j15_content |          1 | idx_state              |            1 | state       | A         |           2 |     NULL | NULL   |      | BTREE      |         |
| j15_content |          1 | idx_catid              |            1 | catid       | A         |           6 |     NULL | NULL   |      | BTREE      |         |
| j15_content |          1 | idx_createdby          |            1 | created_by  | A         |           1 |     NULL | NULL   |      | BTREE      |         |
| j15_content |          1 | title                  |            1 | title       | A         |      201772 |        4 | NULL   |      | BTREE      |         |
| j15_content |          1 | idx_access_state_catid |            1 | access      | A         |           1 |     NULL | NULL   |      | BTREE      |         |
| j15_content |          1 | idx_access_state_catid |            2 | state       | A         |           2 |     NULL | NULL   |      | BTREE      |         |
| j15_content |          1 | idx_access_state_catid |            3 | catid       | A         |           7 |     NULL | NULL   |      | BTREE      |         |
| j15_content |          1 | idx_title_created      |            1 | title       | A         |     3228356 |        8 | NULL   |      | BTREE      |         |
| j15_content |          1 | idx_title_created      |            2 | created     | A         |     3228356 |     NULL | NULL   |      | BTREE      |         |
+-------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

Как вы можете видеть, из базы данных извлекается несколько данных. Теперь я проверил, упростив запрос, что реальная проблема заключается в предложении ORDER BY. Без упорядочения результатов запрос довольно отзывчив, вот объяснение:

+----+-------------+-------+--------+-------------------------------------------------------+-----------+---------+---------------------------+---------+-------------+
| id | select_type | table | type   | possible_keys                                         | key       | key_len | ref                       | rows    | Extra       |
+----+-------------+-------+--------+-------------------------------------------------------+-----------+---------+---------------------------+---------+-------------+
|  1 | SIMPLE      | a     | ref    | idx_access,idx_state,idx_catid,idx_access_state_catid | idx_catid | 4       | const                     | 3108187 | Using where |
|  1 | SIMPLE      | cc    | const  | PRIMARY                                               | PRIMARY   | 4       | const                     |       1 |             |
|  1 | SIMPLE      | u     | eq_ref | PRIMARY                                               | PRIMARY   | 4       | database.a.created_by     |       1 |             |
|  1 | SIMPLE      | g     | eq_ref | PRIMARY                                               | PRIMARY   | 1       | database.a.access         |       1 |             |
+----+-------------+-------+--------+-------------------------------------------------------+-----------+---------+---------------------------+---------+-------------+

Как вы можете видеть, это фатальный файлопорт, который убивает сервер. С помощью этого множества строк я стараюсь оптимизировать все по индексам, но с этим все еще не так. Любой вход был бы оценен.

Пробовал использовать FORCE INDEX безрезультатно:

explain     SELECT cc.title AS category, a.id, a.title, a.alias, a.title_alias, a.introtext, a.fulltext, a.sectionid, a.state, a.catid, a.created, a.created_by, a.created_by_alias, a.modified, a.modified_by, a.checked_out, a.checked_out_time, a.publish_up, a.publish_down, a.attribs, a.hits, a.images, a.urls, a.ordering, a.metakey, a.metadesc, a.access, CASE WHEN CHAR_LENGTH(a.alias) THEN CONCAT_WS(":", a.id, a.alias) ELSE a.id END AS slug, CASE WHEN CHAR_LENGTH(cc.alias) THEN CONCAT_WS(":", cc.id, cc.alias) ELSE cc.id END AS catslug, CHAR_LENGTH( a.`fulltext` ) AS readmore, u.name AS author, u.usertype, g.name AS groups, u.email AS author_email
    ->     FROM bak_content AS a
    ->     FORCE INDEX (idx_title_created)
    ->     LEFT JOIN bak_categories AS cc
    ->     ON a.catid = cc.id
    ->     LEFT JOIN bak_users AS u
    ->     ON u.id = a.created_by
    ->     LEFT JOIN bak_groups AS g
    ->     ON a.access = g.id
    ->     WHERE 1
    ->     AND a.access <= 0
    ->     AND a.catid = 108
    ->     AND a.state = 1
    ->     AND ( publish_up = '0000-00-00 00:00:00' OR publish_up <= '2012-02-08
    ->     AND ( publish_down = '0000-00-00 00:00:00' OR publish_down >= '2012-0
    ->     ORDER BY a.title, a.created DESC
    ->     LIMIT 0, 10;

Выдает:

+----+-------------+-------+--------+---------------+---------+---------+-------
| id | select_type | table | type   | possible_keys | key     | key_len | ref
+----+-------------+-------+--------+---------------+---------+---------+-------
|  1 | SIMPLE      | a     | ALL    | NULL          | NULL    | NULL    | NULL
|  1 | SIMPLE      | cc    | const  | PRIMARY       | PRIMARY | 4       | const
|  1 | SIMPLE      | u     | eq_ref | PRIMARY       | PRIMARY | 4       | database
|  1 | SIMPLE      | g     | eq_ref | PRIMARY       | PRIMARY | 1       | database
+----+-------------+-------+--------+---------------+---------+---------+-------

Ответ 1

AFAIK это не может быть разумно разрешено с помощью индекса, намеков или реструктуризации самого запроса.

Причина, по которой это происходит медленно, заключается в том, что для нее требуется файловый массив из 2M строк, который действительно занимает много времени. Если вы увеличите масштаб заказа, указав его как ORDER BY a.title, a.created DESC. Проблема заключается в комбинации сортировки на более чем 1 столбец и части DESC. Mysql не поддерживает нисходящие индексы (ключевое слово DESC поддерживается в инструкции CREATE INDEX, но только для будущего использования).

Предлагаемое обходное решение - создать дополнительный столбец "reverse_created", который автоматически заполняется таким образом, что ваш запрос может использовать ORDER BY a.title, a.reverse_created. Таким образом, вы заполняете его max_time - created_time. Затем создайте индекс для этой комбинации и (при необходимости) укажите этот индекс как подсказку.

Есть несколько действительно хороших статей в блогах по этой теме, которые объясняют это намного лучше и с примерами:

-Update- Вы можете сделать быстрый тест на этом, удалив часть "DESC" из заказа по вашему запросу. Результаты будут функционально неправильными, но он должен использовать существующий индекс, который у вас есть (или иначе сила должна работать).

Ответ 2

Иногда MySQL не может найти правильный индекс. Вы можете решить это, указав правильный индекс.

Синтаксис подсказки: http://dev.mysql.com/doc/refman/4.1/en/index-hints.html

Убедитесь, что у вас есть нужный индекс и настройте его производительность, поэкспериментируя.

Ура!

Ответ 3

Можете ли вы попробовать эту вариацию:

SELECT cc.title AS category, ...
FROM 
    ( SELECT *
      FROM j15_content AS a 
               USE INDEX (title)             --- with and without the hint
      WHERE 1
        AND a.access <= 0
        AND a.catid = 108
        AND a.state = 1
        AND ( publish_up = '0000-00-00 00:00:00' 
           OR publish_up <= '2012-02-08 00:16:26' )
        AND ( publish_down = '0000-00-00 00:00:00' 
           OR publish_down >= '2012-02-08 00:16:26' )
      ORDER BY a.title, a.created DESC
      LIMIT 0, 10
    ) AS a
  LEFT JOIN j15_categories AS cc
    ON a.catid = cc.id
  LEFT JOIN j15_users AS u
    ON u.id = a.created_by
  LEFT JOIN j15_groups AS g
    ON a.access = g.id

Индекс на (catid, state, title) был бы еще лучше, я думаю.

Ответ 4

Возможно, это поможет:

CREATE INDEX idx_catid_title_created ON j15_content (catid,title(8),created);
DROP INDEX idx_catid ON j15_content;

Ответ 5

Вы пытались увеличить эти значения tmp_table_size и max_heap_table_size:

Ниже приведено краткое описание здесь, а также ссылки на детали каждого из них.

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

Ответ 6

Я надеюсь, что это синтаксически корректно

SELECT
    cc.title AS category,
    a.id, a.title, a.alias, a.title_alias,
    a.introtext, a.fulltext, a.sectionid,
    a.state, a.catid, a.created, a.created_by,
    a.created_by_alias, a.modified, a.modified_by,
    a.checked_out, a.checked_out_time,
    a.publish_up, a.publish_down, a.attribs,
    a.hits, a.images, a.urls, a.ordering, a.metakey,
    a.metadesc, a.access,
    CASE WHEN CHAR_LENGTH(a.alias) THEN CONCAT_WS(":", a.id, a.alias) ELSE a.id END AS slug,
    CASE WHEN CHAR_LENGTH(cc.alias) THEN CONCAT_WS(":", cc.id, cc.alias) ELSE cc.id END AS catslug, CHAR_LENGTH( a.`fulltext` ) AS readmore,
    u.name AS author, u.usertype, g.name AS groups, u.email AS author_email 
FROM
(
    SELECT aa.*
    FROM 
    (
        SELECT id FROM 
        FROM j15_content
        WHERE catid=108 AND state=1
        AND a.access <= 0 
        AND (publish_up   = '0000-00-00 00:00:00' OR   publish_up <= '2012-02-08 00:16:26')
        AND (publish_down = '0000-00-00 00:00:00' OR publish_down >= '2012-02-08 00:16:26')
        ORDER BY title,created DESC
        LIMIT 0,10
    ) needed_keys
    LEFT JOIN j15_content aa USING (id)
) a
LEFT JOIN j15_categories AS cc ON a.catid = cc.id 
LEFT JOIN j15_users AS u ON a.created_by = u.id
LEFT JOIN j15_groups AS g ON a.access = g.id;

Вам понадобится индекс поддержки для подзапроса need_keys

ALTER TABLE j15_content ADD INDEX subquery_ndx (catid,state,access,title,created);

Дайте ему попробовать!!!