Заставить Postgresql использовать Merge Append - PullRequest
0 голосов
/ 29 октября 2018

Скажите, у меня есть следующие таблицы и индексы:

 create table inbound_messages(id int, user_id int, received_at timestamp);
 create table outbound_messages(id int, user_id int, sent_at timestamp);
 create index on inbound_messages(user_id, received_at);
 create index on outbound_messages(user_id, sent_at);

Теперь я хочу извлечь последние 20 сообщений для пользователя, входящих или исходящих за определенный промежуток времени. Я могу сделать следующее, и по объяснению это выглядит так, как будто PG возвращается к обоим индексам «параллельно», поэтому он минимизирует количество строк, необходимых для сканирования.

explain select * from (select id, user_id, received_at as time from inbound_messages union all select id, user_id, sent_at as time from outbound_messages) x where user_id = 5 and time between '2018-01-01' and '2020-01-01' order by user_id,time desc limit 20;

 Limit  (cost=0.32..16.37 rows=2 width=16)
   ->  Merge Append  (cost=0.32..16.37 rows=2 width=16)
         Sort Key: inbound_messages.received_at DESC
         ->  Index Scan Backward using inbound_messages_user_id_received_at_idx on inbound_messages  (cost=0.15..8.17 rows=1 width=16)
               Index Cond: ((user_id = 5) AND (received_at >= '2018-01-01 00:00:00'::timestamp without time zone) AND (received_at <= '2020-01-01 00:00:00'::timestamp without time zone))
         ->  Index Scan Backward using outbound_messages_user_id_sent_at_idx on outbound_messages  (cost=0.15..8.17 rows=1 width=16)
               Index Cond: ((user_id = 5) AND (sent_at >= '2018-01-01 00:00:00'::timestamp without time zone) AND (sent_at <= '2020-01-01 00:00:00'::timestamp without time zone))

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

Теперь мы видим, что postgres поддерживает эту операцию для двух разных таблиц, однако возможно ли заставить Postgres использовать эту оптимизацию для одной таблицы.

Допустим, я хотел получить последние 20 inbound messages для user_id = 5 или user_id = 6.

explain select * from inbound_messages where user_id in (6,7) order by received_at desc limit 20; 

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

 Limit  (cost=15.04..15.09 rows=18 width=16)
   ->  Sort  (cost=15.04..15.09 rows=18 width=16)
         Sort Key: received_at DESC
         ->  Bitmap Heap Scan on inbound_messages  (cost=4.44..14.67 rows=18 width=16)
               Recheck Cond: (user_id = ANY ('{6,7}'::integer[]))
               ->  Bitmap Index Scan on inbound_messages_user_id_received_at_idx  (cost=0.00..4.44 rows=18 width=0)
                     Index Cond: (user_id = ANY ('{6,7}'::integer[]))

Мы могли бы подумать о том, чтобы просто добавить (received_at) в качестве индекса на таблицу, и тогда будет выполнено то же самое обратное сканирование. Однако, если у нас большое количество пользователей, мы упускаем потенциально большое ускорение, потому что сканируем множество записей индекса, которые не соответствуют запросу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...