2015-03-08 5 views
6

EDIT: После изучения некоторых из ответов здесь и часов исследований моя команда пришла к выводу, что, скорее всего, нет возможности оптимизировать это дальше, чем за 4,5 секунды, которые мы смогли достичь (если, возможно, с разделением на предложениях_clicks, но это будет иметь некоторые уродливые побочные эффекты). В конце концов, после многого мозгового штурма, мы решили разделить оба запроса, создать два набора идентификаторов пользователей (один из таблицы пользователей и один из предложений_clicks) и сравнить их с набором в Python. Набор идентификаторов из таблицы пользователей по-прежнему вытаскивается из SQL, но мы решили перенести offer_clicks на Lucene, а также добавили некоторые кеширования поверх него, так что теперь вызывается другой набор идентификаторов. Конечным результатом является то, что он составляет примерно половину секунды с кешем и 0,9 с без кеша.Оптимизация медленного MySQL select query

Начало оригинальной публикации: У меня проблемы с оптимизацией запроса. Первая версия запроса прекрасна, но момент предложения_clicks соединяется во втором запросе, запрос становится довольно медленным. Таблица пользователей содержит 10 миллионов строк, contains_clicks содержит 53 миллиона строк.

Приемлемая производительность:

SELECT count(distinct(users.id)) AS count_1 
FROM users USE index (country_2) 
WHERE users.country = 'US' 
    AND users.last_active > '2015-02-26'; 
1 row in set (0.35 sec) 

Bad:

SELECT count(distinct(users.id)) AS count_1 
FROM offers_clicks USE index (user_id_3), users USE index (country_2) 
WHERE users.country = 'US' 
    AND users.last_active > '2015-02-26' 
    AND offers_clicks.user_id = users.id 
    AND offers_clicks.date > '2015-02-14' 
    AND offers_clicks.ranking_score < 3.49 
    AND offers_clicks.ranking_score > 0.24; 
1 row in set (7.39 sec) 

Вот как это выглядит без specificying никаких индексов (что еще хуже):

SELECT count(distinct(users.id)) AS count_1 
FROM offers_clicks, users 
WHERE users.country IN ('US') 
    AND users.last_active > '2015-02-26' 
    AND offers_clicks.user_id = users.id 
    AND offers_clicks.date > '2015-02-14' 
    AND offers_clicks.ranking_score < 3.49 
    AND offers_clicks.ranking_score > 0.24; 
1 row in set (17.72 sec) 

Объяснение:

explain SELECT count(distinct(users.id)) AS count_1 FROM offers_clicks USE index (user_id_3), users USE index (country_2) WHERE users.country IN ('US') AND users.last_active > '2015-02-26' AND offers_clicks.user_id = users.id AND offers_clicks.date > '2015-02-14' AND offers_clicks.ranking_score < 3.49 AND offers_clicks.ranking_score > 0.24; 
+----+-------------+---------------+-------+---------------+-----------+---------+------------------------------+--------+--------------------------+ 
| id | select_type | table   | type | possible_keys | key  | key_len | ref       | rows | Extra     | 
+----+-------------+---------------+-------+---------------+-----------+---------+------------------------------+--------+--------------------------+ 
| 1 | SIMPLE  | users   | range | country_2  | country_2 | 14  | NULL       | 245014 | Using where; Using index | 
| 1 | SIMPLE  | offers_clicks | ref | user_id_3  | user_id_3 | 4  | dejong_pointstoshop.users.id | 270153 | Using where; Using index | 
+----+-------------+---------------+-------+---------------+-----------+---------+------------------------------+--------+--------------------------+ 

Объяснить без указания каких-либо индексов:

mysql> explain SELECT count(distinct(users.id)) AS count_1 FROM offers_clicks, users WHERE users.country IN ('US') AND users.last_active > '2015-02-26' AND offers_clicks.user_id = users.id AND offers_clicks.date > '2015-02-14' AND offers_clicks.ranking_score < 3.49 AND offers_clicks.ranking_score > 0.24; 
+----+-------------+---------------+-------+------------------------------------------------------------------------+-----------+---------+------------------------------+--------+--------------------------+ 
| id | select_type | table   | type | possible_keys               | key  | key_len | ref       | rows | Extra     | 
+----+-------------+---------------+-------+------------------------------------------------------------------------+-----------+---------+------------------------------+--------+--------------------------+ 
| 1 | SIMPLE  | users   | range | PRIMARY,last_active,country,last_active_2,country_2     | country_2 | 14  | NULL       | 221606 | Using where; Using index | 
| 1 | SIMPLE  | offers_clicks | ref | user_id,user_id_2,date,date_2,date_3,ranking_score,user_id_3,user_id_4 | user_id_2 | 4  | dejong_pointstoshop.users.id |  3 | Using where    | 
+----+-------------+---------------+-------+------------------------------------------------------------------------+-----------+---------+------------------------------+--------+--------------------------+ 

Вот целая куча индексов я пытался с не слишком большим успехом:

+---------------+------------+-----------------------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 
| Table   | Non_unique | Key_name     | Seq_in_index | Column_name  | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | 
+---------------+------------+-----------------------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 
| offers_clicks |   1 | user_id_3     |   1 | user_id   | A   |   198 |  NULL | NULL |  | BTREE  |   |    | 
| offers_clicks |   1 | user_id_3     |   2 | ranking_score | A   |   198 |  NULL | NULL |  | BTREE  |   |    | 
| offers_clicks |   1 | user_id_3     |   3 | date   | A   |   198 |  NULL | NULL |  | BTREE  |   |    | 
| offers_clicks |   1 | user_id_2     |   1 | user_id   | A   | 17838712 |  NULL | NULL |  | BTREE  |   |    | 
| offers_clicks |   1 | user_id_2     |   2 | date   | A   | 53516137 |  NULL | NULL |  | BTREE  |   |    | 
| offers_clicks |   1 | user_id_4     |   1 | user_id   | A   |   198 |  NULL | NULL |  | BTREE  |   |    | 
| offers_clicks |   1 | user_id_4     |   2 | date   | A   |   198 |  NULL | NULL |  | BTREE  |   |    | 
| offers_clicks |   1 | user_id_4     |   3 | ranking_score | A   |   198 |  NULL | NULL |  | BTREE  |   |    | 
| users   |   1 | country_2     |   1 | country   | A   |   14 |  NULL | NULL |  | BTREE  |   |    | 
| users   |   1 | country_2     |   2 | last_active  | A   |  8048529 |  NULL | NULL |  | BTREE  |   |    | 

Упрощенных пользователей схема:

+---------------------------------+---------------+------+-----+---------------------+----------------+ 
| Field       | Type   | Null | Key | Default    | Extra   | 
+---------------------------------+---------------+------+-----+---------------------+----------------+ 
| id        | int(11)  | NO | PRI | NULL    | auto_increment | 
| country       | char(2)  | NO | MUL |      |    | 
| last_active      | datetime  | NO | MUL | 2000-01-01 00:00:00 |    | 

Упрощенными предлагает схему щелчков:

+-----------------+------------------+------+-----+---------------------+----------------+ 
| Field   | Type    | Null | Key | Default    | Extra   | 
+-----------------+------------------+------+-----+---------------------+----------------+ 
| id    | int(11)   | NO | PRI | NULL    | auto_increment | 
| user_id   | int(11)   | NO | MUL | 0     |    | 
| offer_id  | int(11) unsigned | NO | MUL | NULL    |    | 
| date   | datetime   | NO | MUL | 0000-00-00 00:00:00 |    | 
| ranking_score | decimal(5,2)  | NO | MUL | 0.00    |    | 
+1

Пожалуйста, разместите свою схему! –

+0

Eugen Rieck; сделанный! –

+1

Обратите внимание, что DISTINCT не является функцией – Strawberry

ответ

5

Это ваш запрос:

SELECT count(distinct u.id) AS count_1 
FROM offers_clicks oc JOIN 
    users u 
    ON oc.user_id = u.id 
WHERE u.country IN ('US') AND u.last_active > '2015-02-26' AND 
     oc.date > '2015-02-14' AND 
     oc.ranking_score > 0.24 AND oc.ranking_score < 3.49; 

Во-первых, вместо count(distinct), вы могли бы рассмотреть писать запрос как:

SELECT count(*) AS count_1 
FROM users u 
WHERE u.country IN ('US') AND u.last_active > '2015-02-26' AND 
     EXISTS (SELECT 1 
       FROM offers_clicks oc 
       WHERE oc.user_id = u.id AND 
        oc.date > '2015-02-14' AND 
        oc.ranking_score > 0.24 AND oc.ranking_score < 3.49 
      ) 

Затем лучшие показатели для этого запроса являются: users(country, last_active, id) и либо offers_clicks(user_id, date, ranking_score) или offers_clicks(user_id, ranking_score, date).

+0

Я пробовал это с пользователями (country, last_active) и offers_clicks (user_id, date, ranking_score). Скорость примерно такая же. 1 строка в наборе (6.45 с). Насколько важен идентификатор в составном индексе в таблице пользователей?Я хотел бы узнать, как воздействовать на запрос. Я могу попытаться добавить индекс на (страна, last_active и id) завтра и посмотреть, как это влияет на вещи. –

+0

Можете ли вы попробовать запрос, используя '= 'US'', а не' in'? Это может препятствовать оптимальному использованию индекса. 'user_id' не * это * важно. Он просто позволяет индексу быть индексом покрытия, поэтому движку не нужно извлекать данные со страниц данных. –

+0

Спасибо, Гордон; Я попробую добавить «id» в составной индекс в таблице пользователей завтра. Я раньше попробовал = 'US'; по-видимому, не имеет большого значения в какой-либо разнице (не полностью ее оценили, но скорость, похоже, примерно такая же). –

0
SELECT count(users.id) AS count_1 
FROM users 
INNER JOIN 
    (SELECT 
    DISTINCT user_id 
    FROM 
    offers_clicks 
    WHERE offers_clicks.date > '2015-02-14' 
    AND offers_clicks.ranking_score < 3.49 
    AND offers_clicks.ranking_score > 0.24 
) as clicks 
ON clicks.user_id = users.id 
WHERE users.country IN ('US') 
    AND users.last_active > '2015-02-26' 

Возможно, вы представите sqlfiddle с некоторыми данными.

и могли бы вы сказать мне, что время выполнения этого запроса:

SELECT 
    DISTINCT user_id 
    FROM 
    offers_clicks 
    WHERE offers_clicks.date > '2015-02-14' 
    AND offers_clicks.ranking_score < 3.49 
    AND offers_clicks.ranking_score > 0.24 

EDIT ВОПРОС Как долго занимает этот?

SELECT 
    DISTINCT user_id 
    FROM 
    offers_clicks USE INDEX (user_id_4) 
    WHERE offers_clicks.date > '2015-02-14' 
    AND offers_clicks.ranking_score < 3.49 
    AND offers_clicks.ranking_score > 0.24 
+0

Я постараюсь настроить sqlfiddle завтра. Время выполнения just offers_clicks составляет около 4-5 секунд, почти так же медленно, как и ваш запрос, включая пользователей (который работает примерно через 5-6 секунд, примерно на 1-2 секунды быстрее исходного запроса). –

+0

Вот объяснение в запросе предложений_clicks btw: | 1 | ПРОСТОЙ | Предложения | диапазон | дата, дата_2, дата_3, rank_score | date_2 | 8 | NULL | 2738102 | Использование где; Использование временных | –

+0

, но приносит ли он правильный результат? лучше (5-6), чем раньше (17-18)? так что теперь я просто улучшил его, чтобы получить меньше 1 с? – Alex

1
SELECT count(distinct u.id) AS count_1 
FROM users u 
STRAIGHT_JOIN offers_clicks oc 
    ON oc.user_id = u.id 
WHERE 
    u.country IN ('US') 
    AND u.last_active > '2015-02-26' 
    AND oc.date > '2015-02-14' 
    AND oc.ranking_score > 0.24 
    AND oc.ranking_score < 3.49; 

Убедитесь, что индекс на пользователей - (id, last_active, country) колонны и offers_clicks - (user_id, date, ranking_score)

Или вы можете изменить порядок

SELECT count(distinct u.id) AS count_1 
FROM offers_clicks oc 
STRAIGHT_JOIN users u 
    ON oc.user_id = u.id 
WHERE 
    u.country IN ('US') 
    AND u.last_active > '2015-02-26' 
    AND oc.date > '2015-02-14' 
    AND oc.ranking_score > 0.24 
    AND oc.ranking_score < 3.49; 

Удостоверьтесь, что у вас есть указатель на offers_clicks - (user_id) столбцов и пользователей - (id, last_active, country)

0

Попробуйте делать это наоборот:

SELECT COUNT(users.id) 
    FROM users, offers_clicks 
    WHERE users.country = 'US' 
     AND users.last_active > '2015-02-26' 
     AND offers_clicks.user_id = users.id 
     AND offers_clicks.date > '2015-02-14' 
     AND offers_clicks.ranking_score < 3.49 
     AND offers_clicks.ranking_score > 0.24; 
0

Попробуйте это:

SELECT count(distinct users.id) AS count_1 
FROM users USE index (<see below>) 
JOIN offers_clicks USE index (<see below>) 
    ON offers_clicks.user_id = users.id 
    AND offers_clicks.date BETWEEN '2015-02-14' AND CURRENT_DATE 
    AND offers_clicks.ranking_score BETWEEN 0.24 AND 3.49 
WHERE users.country = 'US' 
AND users.last_active BETWEEN '2015-02-26' AND CURRENT_DATE 

Убедитесь есть индексы на users(country, last_active, id) и offers_clicks(user_id, ranking_score, date) и USE их.

Дайте мне знать, как это работает, и если это сработает, я объясню, почему.

0

Прежде всего, я также считаю, что вы должны использовать join и пытаться присоединиться только к строкам, которые вам действительно нужны в результате.
Что касается table Offers_clicks, я думаю, что вы не должны использовать index user_id_3 и использовать user_id_2 , потому что мощность пользователя____________________________________________________________на_выражения, потому что мощность user_id_2 выше, чем мощность user_id_3 (в соответствии с вашими индексами) , и она должна быть быстрее.

SELECT 
    count(distinct(users.id)) AS count_1 
FROM users USE INDEX (country_2) 
JOIN offers_clicks USE INDEX (user_id_2) 
    ON offers_clicks.user_id = users.id 
    AND offers_clicks.date > '2015-02-14' 
    AND offers_clicks.ranking_score < 3.49 
    AND offers_clicks.ranking_score > 0.24 
WHERE users.country = 'US' AND users.last_active > '2015-02-26' 
; 

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

Не уверен, что я буду вам полезен ...