Задать вопрос
opium
@opium
Просто люблю качественно работать

Как оптимизировать вот такой mysql запрос

В таблице миллионы строк, запрос выполняется больше 3 секунд

SELECT COUNT(DISTINCT(ipaddress)) as uniquenotcloaked FROM accounts a WHERE date_visited BETWEEN STR_TO_DATE('2013-05-01 00:00:01', '%Y-%m-%d %H:%i:%s') AND STR_TO_DATE('2013-07-18 23:59:59', '%Y-%m-%d %H:%i:%s') AND a.server_id IN (2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,28,30,32) AND cloaked = 0;

+------------------+
| uniquenotcloaked |
+------------------+
| 574720 |
+------------------+
1 row in set (3.98 sec)

Есть много индексов в том числе и составной по date_visited + server_id + cloacked

EXPLAIN SELECT COUNT( DISTINCT ( ipaddress ) ) AS uniquenotcloaked FROM accounts a IGNORE INDEX ( Index_cloaked ) WHERE date_visited BETWEEN STR_TO_DATE( '2013-05-01 00:00:01', '%Y-%m-%d %H:%i:%s' ) AND STR_TO_DATE( '2013-07-18 23:59:59', '%Y-%m-%d %H:%i:%s' ) AND a.server_id IN ( 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 28, 30, 32 ) AND cloaked =0;
+----+-------------+-------+------+----------------------------------------------------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+----------------------------------------------------------+------+---------+------+---------+-------------+
| 1 | SIMPLE | a | ALL | Index_date_visited,Index_8,date_server,date_server_cloak | NULL | NULL | NULL | 3252392 | Using where |
+----+-------------+-------+------+----------------------------------------------------------+------+---------+------+---------+-------------+
1 row in set (0.00 sec)

Если убрать DISTINCT то запрос работает точно также, если считать count( account_id ) то запрос работает точно так же, то есть с distinct и ipaddress не использование индексов не связано.

Настройки mysql сервер выкручены по максимуму.

Данные из slow log

# Query_time: 5.030999 Lock_time: 0.000135 Rows_sent: 1 Rows_examined: 3269287 Rows_affected: 0 Rows_read: 3269287
# Bytes_sent: 76
SET timestamp=1374212229;
SELECT
COUNT(DISTINCT(ipaddress)) as uniquenotcloaked
FROM accounts a
WHERE date_visited BETWEEN STR_TO_DATE('2013-05-01 00:00:01', '%Y-%m-%d %H:%i:%s') AND STR_TO_DATE('2013-07-18 23:59:59', '%Y-%m-%d %H:%i:%s') AND a.server_id IN (2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,28,30,32) AND cloaked = 0;
  • Вопрос задан
  • 4808 просмотров
Подписаться 3 Оценить Комментировать
Пригласить эксперта
Ответы на вопрос 5
Melkij
@Melkij
PostgreSQL DBA
> составной по date_visited + server_id + cloacked
Именно в таком порядке?
Постройте с cloacked на первом месте (cloacked + date_visited и cloacked + server_id — посмотреть, что планировщику понравится больше), потому что поле константно. In, Between — это range запросы, части индекса после range mysql использовать не может.

> STR_TO_DATE( '2013-05-01 00:00:01', '%Y-%m-%d %H:%i:%s' )
Бесполезен же, mysql сам приведёт строку к дате. К теме не относится — просто запрос станет резко компактнее.
Ответ написан
opium
@opium Автор вопроса
Просто люблю качественно работать
1)Именно в таком.
2)Попробую
3)Без приведения как то не работали индексы по date_visited
Ответ написан
Комментировать
scu
@scu
Попробуйте
— разбивать таблицы на отдельные по месяцам (или неделям или дням — в зависимости от объема и скорости накопления информации в БД) — это значительно сократит скорость выборки.
— IP хранить как 4 части по 3 знака int. + сделать индекс: на каждую часть, на 1+2 часть и 2+3 часть.
— дату хранить и выбирать в timestamp

+ если есть желание, киньте дамб базы в приват (кусок) на поиграться, возможно что-то еще смогу подсказать.
Ответ написан
KEKSOV
@KEKSOV
Большой IN не айс… попробуйте сделать так, если, конечно, данные в IN всегда одни и те же.
Итак, превращаем AND a.server_id IN (2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,28,30,32) в (порядок важен)
AND a.server_id >1 AND a.server_id < 33 AND ( a.server_id < 23 OR a.server_id IN ( 28, 30, 32 ) )

и еще попробуйте перенести проверку cloaked =0 в начало WHERE
Ответ написан
ruFelix
@ruFelix
Предсказание будущего по руке, таро, кофе.
Избавьтесь от BETWEEN с ним всё всегда плохо, два варианта это сделать:
1) Добавьте колонку INT которую заполните UPDATE accounts SET new_col = YEAR(date_visited_timestamp ) * 1000 + DAYOFYEAR(date_visited_timestamp )
сделайте по ней индекс и дёргйте её через IN()
2) используете гео индекс, он хорошо решает проблему с BETWEEN dev.mysql.com/doc/refman/5.7/en/populating-spatial-columns.html

Другой вариант завидите новую таблицу
server_id | counter | crdate
И или на прошлую таблицу повесьте тригер на инсёрт или добавьте запрос в ваше приложение который будет инкрементить счётчик

Третий вариант, совместить первые два.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы