Оптимизация условия WHERE для поля TIMESTAMP в операторе MySQL SELECT

8

Я работаю над схемой для аналитической системы, которая отслеживает время использования, и необходимо видеть общее время использования в определенном диапазоне дат.

Чтобы привести простой пример, этот тип запроса будет выполняться часто:

select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");

Этот запрос обычно занимает около 7 секунд для таблицы с большим количеством пользователей. Он содержит ~ 35 миллионов строк, MyISAM на MySQL работает на Amazon RDS (db.m3.xlarge).

Избавление от предложения WHERE заставляет запрос занимать всего 4 секунды, а добавление второго предложения (time_off> XXX) добавляет дополнительные 1,5 секунды, в результате чего время запроса увеличивается до 8,5 секунд.

Поскольку я знаю, что эти типы запросов будут обычно выполняться, я бы хотел оптимизировать их, чтобы они выполнялись быстрее, в идеале - менее 5 секунд.

Я начал с добавления индекса для time_on, и, хотя это резко ускорило запрос WHERE "=", это не повлияло на запрос ">". Есть ли способ создать индекс, который ускорил бы запросы WHERE ">" или "<"?

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

Примечание: я использую поле "diff_ms" в качестве шага денормализации (оно равно time_off - time_on), что повышает производительность агрегации примерно на 30% -40%.

Я создаю индекс с помощью этой команды:

ALTER TABLE writetest_table ADD INDEX time_on (time_on) USING BTREE;

Выполнение «объяснения» в исходном запросе (с «time_on>») говорит, что time_on - это «возможный_ключ», а select_type - «ПРОСТОЙ». В столбце «extra» написано «Using where», а «type» - «ALL». После добавления индекса в таблице говорится, что «time_on» - это тип ключа «MUL», что кажется правильным, поскольку одно и то же время может присутствовать дважды.

Вот схема таблицы:

CREATE TABLE `writetest_table` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `sessionID` int(11) DEFAULT NULL,
  `time_on` timestamp NULL DEFAULT NULL,
  `time_off` timestamp NULL DEFAULT NULL,
  `diff_ms` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `time_on` (`time_on`)
) ENGINE=MyISAM AUTO_INCREMENT=50410902 DEFAULT CHARSET=latin1;

ОБНОВЛЕНИЕ: я создал следующий индекс на основе ответа ypercube, но это увеличивает время запроса для первого запроса примерно до 17 секунд!

ALTER TABLE writetest_table  ADD INDEX time_on__diff_ms__ix (time_on, diff_ms) ;

ОБНОВЛЕНИЕ 2: ОБЪЯСНИТЕ вывод

mysql> explain select sum(diff_ms) from writetest_table where time_on > '2015-07-13 15:11:56';
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
| id | select_type | table               | type  | possible_keys        | key                  | key_len | ref  | rows     | Extra                    |
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
|  1 | SIMPLE      | writetest_table_old | index | time_on__diff_ms__ix | time_on__diff_ms__ix | 10      | NULL | 35831102 | Using where; Using index |
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
1 row in set (0.00 sec)

Обновление 3: результат запрошенного запроса

mysql> SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;
+---------------------+
| time_on             |
+---------------------+
| 2015-07-13 15:11:56 |
+---------------------+
1 row in set (0.01 sec)
Locksleyu
источник
У вас есть пустые значения в этих 2 столбцах ( time_onи diff_ms)? Что произойдет, если вы добавите в запрос WHERE ... AND diff_ms IS NOT NULL?
ypercubeᵀᴹ
Можете ли вы показать нам выводSELECT COUNT(*), COUNT(diff_ms) FROM writetest_table;
ypercubeᵀᴹ
Также объяснение в вашем «Обновлении 2» показывает « таблицу:writetest_table_old », в то время как запрос имеет from writetest_table. Это опечатка или вы запускаете запрос в другой таблице?
ypercubeᵀᴹ

Ответы:

3

Я думаю, что начинаю понимать.

Когда я попросил тебя бежать

SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;

Вы сказали, что это было 2015-07-13 15:11:56в вашей WHEREстатье

Когда вы сделали запрос

select sum(diff_ms) from writetest_table;

Было выполнено полное сканирование таблицы из 35,8 миллионов строк.

Когда вы сделали запрос

select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");

Было выполнено полное сканирование индекса в 35,8 миллионов строк.

Вполне понятно, что запрос без предложения WHERE выполняется быстрее. Почему ?

Сканирование таблицы будет считывать 35,8 миллиона строк за один линейный проход.

Объяснение запроса с WHERE также обнаружило 35,8 миллионов строк. Сканирование индекса будет вести себя немного иначе. Хотя BTREE следит за порядком клавиш, это ужасно для сканирования диапазона. В вашем конкретном случае вы выполняете сканирование наихудшего из возможных диапазонов, которое будет иметь такое же количество записей BTREE, как и строк в таблице. MySQL должен пройти страницы BTREE (по крайней мере через конечные узлы), чтобы прочитать значения. Кроме того, time_onстолбец необходимо сравнивать по пути в порядке, определяемом индексом. Следовательно, неконечные узлы BTREE также должны быть пройдены.

Пожалуйста, смотрите мои сообщения на BTREEs

Если запрос был по состоянию на полночь сегодня

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 00:00:00");

или даже сегодня в полдень

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 12:00:00");

это должно занять меньше времени.

МОРАЛЬ ИСТОРИИ. Не используйте предложение WHERE, которое выполняет сканирование в упорядоченном диапазоне, равное количеству строк в целевой таблице.

RolandoMySQLDBA
источник
Моя единственная проблема заключается в том, как идти отсюда. Я сделал запрос с датой, в результате которого отфильтровалось только 1 миллион строк, а сумма заняла всего 1 секунду. Но иногда мне, возможно, придется собирать суммы по большей части данных. Любые предложения, как справиться с этим? Я надеялся, что MySQL будет достаточно умен, чтобы знать, когда использовать индекс, а когда нет, но я полагаю, что в этом случае у него недостаточно информации.
Локслию
Мне бы очень хотелось, чтобы был какой-то индекс, который был бы организован так, чтобы пункты WHERE быстро определяли диапазоны дат, и, кажется, это технически возможно реализовать, но я думаю, что это не поддерживается.
Локслию
У вас слишком много данных на таком коротком расстоянии. Никакое предложение WHERE не может быть компенсировано. Почему ? Проблема не в индексе. Это мнение индекса по MySQL Query Optimizer. Когда вы начнете собирать гораздо больше данных (скажем, за две недели), статистика индекса должна выровняться, и вы увидите улучшение производительности. Только не делайте полное сканирование индекса.
RolandoMySQLDBA
4

Для конкретного запроса:

select sum(diff_ms) 
from writetest_table 
where time_on > '2015-07-13 15:11:56' ;     -- use single quotes, not double

индекс на (time_on, diff_ms)будет лучшим вариантом. Итак, если запрос выполняется достаточно часто или его эффективность имеет решающее значение для вашего приложения, добавьте этот индекс:

ALTER TABLE writetest_table 
  ADD INDEX time_on__diff_ms__ix      -- pick a name for the index
    (time_on, diff_ms) ;

(Не относится к вопросу)
И действительно, измените движок таблицы на InnoDB. Это 2015 год, и похороны MyISAM были несколько лет назад.
(/ напыщенная)

ypercubeᵀᴹ
источник
Я создал точный указатель, который вы предложили, а затем выполнил точный запрос, который вы упомянули первым в своем ответе, но сейчас время намного хуже и занимает примерно 17 секунд (я пробовал несколько раз).
Локслею
Я понятия не имею, что вызывает это. В случае, если в таблице есть только 3671 различное значение time_on (это связано с тем, как мой тестовый скрипт заполняет данные).
Локслею
Вы должны сделать три (3) вещи: 1. запустить ALTER TABLE writetest_table DROP INDEX time_on;, 2) выполнить ANALYZE TABLE writetest_table;и 3) повторно выполнить запрос. Время возвращается к 7 секундам?
RolandoMySQLDBA
1
Вы также должны бежать EXPLAIN select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");. Используется ли новый индекс? Если он не используется, я бы сказал, что это ваша ключевая совокупность, особенно если ваш самый ранний time_on всего несколько дней назад. Поскольку число строк увеличивается с увеличением числа различных дней, распределение ключей должно выравниваться, и ОБЪЯСНЕНИЕ должно быть лучше ,
RolandoMySQLDBA
RolandoMySQLDBA - я попробовал три ваших шага, и да, время возвращается к 7 секундам. Я сделал объяснение, и он говорит, что индекс используется. Я до сих пор не понимаю, почему добавление такого индекса может ухудшить производительность в 2 раза.
Локслею