2015-07-30 2 views
2

У меня есть таблица, которая имеет более чем 100,000,000 строк и у меня есть запрос, который выглядит следующим образом:Возможно ли повысить производительность этого SQL-запроса?

SELECT 
    COUNT(IF(created_at >= '2015-07-01 00:00:00', 1, null)) AS 'monthly', 
    COUNT(IF(created_at >= '2015-07-26 00:00:00', 1, null)) AS 'weekly', 
    COUNT(IF(created_at >= '2015-06-30 07:57:56', 1, null)) AS '30day', 
    COUNT(IF(created_at >= '2015-07-29 17:03:44', 1, null)) AS 'recent' 
FROM 
    items 
WHERE 
    user_id = 123456; 

таблица выглядит так:

CREATE TABLE `items` (
    `user_id` int(11) NOT NULL, 
    `item_id` int(11) NOT NULL, 
    `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', 
    PRIMARY KEY (`user_id`,`item_id`), 
    KEY `user_id` (`user_id`,`created_at`), 
    KEY `created_at` (`created_at`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 

Explain, выглядит довольно безобидно, минус массивная row count:

1 SIMPLE items ref PRIMARY,user_id user_id 4 const 559864 Using index 

Я использую запрос для подсчета количества отсчетов для определенного пользователя в течение 4 сегментов времени. Есть ли более умный/быстрый способ получения одних и тех же данных или это моя единственная возможность их подсчета, поскольку новые строки помещаются в эту таблицу?

+0

есть индекс на столбце created_at? Если да, то какой индекс? Сколько времени требуется для выполнения запроса? Как часто это выполняется? Как полезная помощь: я не совсем уверен, но это должно помочь добавить еще один оператор в оператор 'WHERE', что-то вроде' AND created_at> = '2015-06-30 07: 57: 56', так что только релевантные данные на самом деле «подсчитано» –

ответ

1

Я хотел бы добавить индекс created_at поле:

ALTER TABLE items ADD INDEX idx_created_at (created_at) 

или (как это было предложено Thomas), поскольку вы также фильтрации для user_id составного индекса created_at и user_id:

ALTER TABLE items ADD INDEX idx_user_created_at (user_id, created_at) 

, а затем Я хотел бы написать ваш запрос:

SELECT 'monthly' as description, COUNT(*) AS cnt FROM items 
WHERE created_at >= '2015-07-01 00:00:00' AND user_id = 123456 

UNION ALL 

SELECT 'weekly' as description, COUNT(*) AS cnt FROM items 
WHERE created_at >= '2015-07-26 00:00:00' AND user_id = 123456 

UNION ALL 

SELECT '30day' as description, COUNT(*) AS cnt FROM items 
WHERE created_at >= '2015-06-30 07:57:56' AND user_id = 123456 

UNION ALL 

SELECT 'recent' as description, COUNT(*) AS cnt FROM items 
WHERE created_at >= '2015-07-29 17:03:44' AND user_id = 123456 

Да, выход немного отличается. Или вы можете использовать встроенные запросы:

SELECT 
    (SELECT COUNT(*) FROM items WHERE created_at>=... AND user_id=...) AS 'monthly', 
    (SELECT COUNT(*) FROM items WHERE created_at>=... AND user_id=...) AS 'weekly', 
    ... 

, и если вы хотите, в среднем, вы можете использовать подзапрос:

SELECT 
    monthly, 
    weekly, 
    monthly/total, 
    weekly/total 
FROM (
    SELECT 
    (SELECT COUNT(*) FROM items WHERE created_at>=... AND user_id=...) AS 'monthly', 
    (SELECT COUNT(*) FROM items WHERE created_at>=... AND user_id=...) AS 'weekly', 
    ..., 
    (SELECT COUNT(*) FROM items WHERE user_id=...) AS total 
) s 
+0

было бы интересно увидеть, что это быстрее, чем делать то, что я написал в своем комментарии (просто расширяя предложение where where). –

+1

Было бы неплохо использовать составной индекс '(created_at, user_id)'? –

+0

Я только что протестировал это и то, что вы предложили @SteffenWinkler, и оба очень близки по производительности и оба сбрили мой 5-секундный запрос до <300 мс! – prgrmnerd

2

Если у вас есть индекс по created_at, я бы также поставить в ИНЕКЕ created_at> = '2015-06-30 07:57:56', которая является самой низкой датой в вашем сегменте.

Кроме того, с тем же индексом может работать расщепление в 4-х запросов:

select count(*) AS '30day' 
FROM 
items 
WHERE 
    user_id = 123456 
and created_at >= '2015-06-30 07:57:56' 
union .... 

И так далее

+0

Боже мой ... ты, сэр, гений, добавляя лишнее место, где предложение больше, чем самая ранняя дата в моих сегментах. Любить это! – prgrmnerd

0
  • INDEX(user_id, created_at) - оптимальное
  • AND created_at >= '2015-06-30 07:57:56' - помогает, потому что она сокращает количество указательных записей для контакта
  • Выполнение UNION не помогает, поскольку оно приводит к увеличению в 4 раза больше работы.
  • Выполнение для подзапроса SELECTs не помогает по той же причине.

Также

COUNT(IF(created_at >= '2015-07-29 17:03:44', 1, null)) 

может быть сокращен до

SUM(created_at >= '2015-07-29 17:03:44') 

(Но, вероятно, не ускорить его много)

Если данные не изменяются с течением времени, только новые строки добавляются, то сводные таблицы прошлых данных приведут к значительному ускорению, но только если вы можете избежать таких вещей, как '07: 57: 56 'для' 30day '. (Почему у них есть «00: 00: 00» только для некоторых из них?) Возможно, ускорение будет еще одним фактором, равным 10, помимо других изменений. Хотите обсудить дальше?

(я не вижу каких-либо преимуществ в использовании PARTITION.)

+0

Я бы не сказал, что UNION всегда медленнее, это зависит от того, как структурированы данные. UNION делает работу больше времени, но COUNT (*) быстрее, чем COUNT (IIF (...)) - на этом конкретный контекст Я считаю, что использование только И - хорошая идея, вероятно, лучше, чем UNION, но если мы рассчитывали, например, «еженедельно в этом месяце», «еженедельно в прошлом месяце», «еженедельно в прошлом году» и т. д., я ожидал бы UNION запрос выполнить лучше – fthiella

+0

'UNION' _can_ be ** быстрее **, когда каждый' SELECT' использует другой индекс. В противном случае «UNION» будет «вероятно» медленнее ** из-за дополнительных накладных расходов. В этом случае я предсказываю «медленнее». –

Смежные вопросы