Оптимизация полей Datetime, где индексы не используются должным образом - PullRequest
1 голос
/ 20 октября 2011

У меня есть большая быстрорастущая таблица журнала в приложении, работающем с MySQL 5.0.77. Я пытаюсь найти лучший способ оптимизировать запросы, которые подсчитывают количество экземпляров за последние X дней в соответствии с типом сообщения:

CREATE TABLE `counters` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `kind` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_counters_on_kind` (`kind`),
  KEY `index_counters_on_created_at` (`created_at`)
) ENGINE=InnoDB AUTO_INCREMENT=302 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Для этого набора тестов в таблице 668521 строк. Я пытаюсь оптимизировать запрос:

SELECT kind, COUNT(id) FROM counters WHERE created_at >= ? GROUP BY kind;

Сейчас этот запрос занимает 3-5 секунд и оценивается следующим образом:

+----+-------------+----------+-------+----------------------------------+------------------------+---------+------+---------+-------------+
| id | select_type | table    | type  | possible_keys                    | key                    | key_len | ref  | rows    | Extra       |
+----+-------------+----------+-------+----------------------------------+------------------------+---------+------+---------+-------------+
|  1 | SIMPLE      | counters | index | index_counters_on_created_at_idx | index_counters_on_kind | 258     | NULL | 1185531 | Using where | 
+----+-------------+----------+-------+----------------------------------+------------------------+---------+------+---------+-------------+
1 row in set (0.00 sec)

С удаленным индексом create_at это выглядит так:

+----+-------------+----------+-------+---------------+------------------------+---------+------+---------+-------------+
| id | select_type | table    | type  | possible_keys | key                    | key_len | ref  | rows    | Extra       |
+----+-------------+----------+-------+---------------+------------------------+---------+------+---------+-------------+
|  1 | SIMPLE      | counters | index | NULL          | index_counters_on_kind | 258     | NULL | 1185531 | Using where | 
+----+-------------+----------+-------+---------------+------------------------+---------+------+---------+-------------+
1 row in set (0.00 sec)

(Да, по какой-то причине оценка строк превышает количество строк в таблице.)

Так что, по-видимому, нет никакого смысла в этом индексе.

Неужели нет лучшего способа сделать это? Я пробовал столбец как метку времени, и он просто оказался медленнее.

Редактировать: я обнаружил, что изменение запроса на использование интервала вместо конкретной даты заканчивается использованием индекса, сокращая оценку строки примерно до 20% от запроса выше:

SELECT kind, COUNT(id) FROM counters WHERE created_at >= 
    (NOW() - INTERVAL 7 DAY) GROUP BY kind;

Я не совсем уверен, почему это происходит, но я вполне уверен, что если бы я понял это, то проблема в целом имела бы гораздо больше смысла.

Ответы [ 2 ]

0 голосов
/ 20 октября 2011

Почему бы не использовать составной индекс?

CREATE INDEX idx_counters_created_kind ON counters(created_at, kind);

Следует использовать сканирование только по индексу (упоминание «Использование индекса» в «Дополнительная информация», так как COUNT (ID) в любом случае НЕ равно NULL).

Ссылки:

0 голосов
/ 20 октября 2011

После прочтения последней редакции вопроса, похоже, проблема в том, что параметр, используемый в предложении WHERE, интерпретировался MySQL как строка, а не как значение datetime. Это объясняет, почему оптимизатор не выбирал индекс index_counters_on_created_at, а вместо этого приводил к сканированию, чтобы преобразовать значения created_at в строковое представление, а затем выполнить сравнение. Я думаю, что это может быть предотвращено явным приведением к datetime в предложении where:

where `created_at` >= convert({specific_date}, datetime)

Мои оригинальные комментарии по-прежнему применимы к части оптимизации.

Настоящим убийцей производительности здесь является столбец kind. Потому что при выполнении GROUP BY ядру базы данных сначала необходимо определить все различные значения в столбце kind, что приведет к сканированию таблицы или индекса. Вот почему оцениваемые строки больше, чем общее количество строк в таблице, за один проход он определит различные значения в столбце kind, а на втором проходе определит, какие строки соответствуют условию create_at >= ?. Что еще хуже, столбец kind - это varchar (255), который слишком велик, чтобы быть эффективным, добавьте к этому, что он использует набор символов utf8 и параметры сортировки utf8_unicode_ci, что увеличивает сложность сравнений, необходимых для определения уникальные значения в этом столбце.

Это будет работать намного лучше, если вы измените тип столбца kind на int. Потому что целочисленные сравнения более эффективны и проще, чем сравнения символов Юникода. Также было бы полезно иметь таблицу каталогов для kind сообщений, в которых вы храните kind_id и description. Затем выполните группировку по объединению таблицы каталога вида и подзапросу таблицы журнала, которая сначала фильтрует по дате:

select k.kind_id, count(*)
from
    kind_catalog k
    inner join (
        select kind_id
        from counters
        where create_at >= ?
    ) c on k.kind_id = c.kind_id
group by k.kind_id

Это сначала отфильтрует таблицу counters по create_at >= ? и может извлечь выгоду из индекса по этому столбцу. Затем он соединит это с таблицей kind_catalog и, если оптимизатор SQL работает хорошо, будет сканировать меньшую таблицу kind_catalog для выполнения группировки вместо таблицы counters.

...