В PostgreSQL 9.2, у меня есть таблица элементов, которые оцениваемые пользователи:Slow GroupAggregate в PostgreSQL
id | userid | itemid | rating | timestamp | !update_time
--------+--------+--------+---------------+---------------------+------------------------
522241 | 3991 | 6887 | 0.1111111111 | 2005-06-20 03:13:56 | 2013-10-11 17:50:24.545
522242 | 3991 | 6934 | 0.1111111111 | 2005-04-05 02:25:21 | 2013-10-11 17:50:24.545
522243 | 3991 | 6936 | -0.1111111111 | 2005-03-31 03:17:25 | 2013-10-11 17:50:24.545
522244 | 3991 | 6942 | -0.3333333333 | 2005-03-24 04:38:02 | 2013-10-11 17:50:24.545
522245 | 3991 | 6951 | -0.5555555556 | 2005-06-20 03:15:35 | 2013-10-11 17:50:24.545
... | ... | ... | ... | ... | ...
Я хочу выполнить очень простой запрос: для каждого пользователя, выберите общее количество оценок в базе данных.
Я использую следующий простой подход:
SELECT userid, COUNT(*) AS rcount
FROM ratings
GROUP BY userid
Таблица содержит 10M записи. Запрос занимает ... ну, примерно 2 или 3 минуты. Честно говоря, я не доволен этим, и я считаю, что 10M не так много для того, чтобы запрос занял так много времени. (Или это .. ??)
Отныне я попросил PostgreSQL показать мне план выполнения:
EXPLAIN SELECT userid, COUNT(*) AS rcount
FROM ratings
GROUP BY userid
Это приводит к:
GroupAggregate (cost=1756177.54..1831423.30 rows=24535 width=5)
-> Sort (cost=1756177.54..1781177.68 rows=10000054 width=5)
Sort Key: userid
-> Seq Scan on ratings (cost=0.00..183334.54 rows=10000054 width=5)
Я прочитал это следующим образом: Во-первых, , вся таблица считывается с диска (seq scan). Во-вторых, он сортируется по идентификатору пользователя в n*log(n)
(сортировка). Наконец, отсортированная таблица считывается по строкам и агрегируется в линейном времени. Ну, не совсем оптимальный алгоритм, я думаю, если бы я сам его реализовал, я бы использовал хеш-таблицу и построил результат в первом проходе. Неважно.
Похоже, что это сортировка по userid
, которая занимает так много времени. Добавлен индекс:
CREATE INDEX ratings_userid_index ON ratings (userid)
К сожалению, это не помогло, и производительность не изменилась. Я определенно не считаю себя продвинутым пользователем, и я считаю, что делаю что-то принципиально неправильное. Однако здесь я застрял. Я был бы признателен за любые идеи о том, как выполнить запрос в разумные сроки. Еще одно примечание: рабочий процесс PostgreSQL использует 100% одного из моих ядер процессора во время выполнения, что указывает на то, что доступ к диску не является основным узким местом.
EDIT
В соответствии с просьбой @a_horse_with_no_name. Ничего себе, довольно передовые для меня:
EXPLAIN (analyze on, buffers on, verbose on)
SELECT userid,COUNT(userid) AS rcount
FROM movielens_10m.ratings
GROUP BY userId
Выходы:
GroupAggregate (cost=1756177.54..1831423.30 rows=24535 width=5) (actual time=110666.899..127168.304 rows=69878 loops=1)
Output: userid, count(userid)
Buffers: shared hit=906 read=82433, temp read=19358 written=19358
-> Sort (cost=1756177.54..1781177.68 rows=10000054 width=5) (actual time=110666.838..125180.683 rows=10000054 loops=1)
Output: userid
Sort Key: ratings.userid
Sort Method: external merge Disk: 154840kB
Buffers: shared hit=906 read=82433, temp read=19358 written=19358
-> Seq Scan on movielens_10m.ratings (cost=0.00..183334.54 rows=10000054 width=5) (actual time=0.019..2889.583 rows=10000054 loops=1)
Output: userid
Buffers: shared hit=901 read=82433
Total runtime: 127193.524 ms
EDIT 2
@ комментарий a_horse_with_no_name в решить эту проблему.Я чувствую себя счастливым, чтобы поделиться своими выводами:
SET work_mem = '1MB';
EXPLAIN SELECT userid,COUNT(userid) AS rcount
FROM movielens_10m.ratings
GROUP BY userId
производит то же самое, что и выше:
GroupAggregate (cost=1756177.54..1831423.30 rows=24535 width=5)
-> Sort (cost=1756177.54..1781177.68 rows=10000054 width=5)
Sort Key: userid
-> Seq Scan on ratings (cost=0.00..183334.54 rows=10000054 width=5)
Однако
SET work_mem = '10MB';
EXPLAIN SELECT userid,COUNT(userid) AS rcount
FROM movielens_10m.ratings
GROUP BY userId
дает
HashAggregate (cost=233334.81..233580.16 rows=24535 width=5)
-> Seq Scan on ratings (cost=0.00..183334.54 rows=10000054 width=5)
Запрос теперь только принимает около 3,5 секунд для завершения ,
Пожалуйста, опубликуйте вывод 'объяснять (анализировать, буферизировать, многословно) ..' –
. Одна вещь, которую вы можете рассмотреть, заключается в том, чтобы этот запрос в «материализованное представление» и (иногда) обновлял представление как часть вашей вставки/запуска рейтинга. То есть кеширование этого запроса. –
Основная проблема заключается не в сканировании seq, а в том, что делается на диске. Вы можете попробовать 'set work_mem = '250MB'' (или даже выше) перед запуском запроса - если у вас достаточно памяти. –