Тем не менее, подзапрос для вычисления рейтингов должен выполняться для каждой темы во всей таблице posts, независимо от того, сколько записей фактически обновляется.
Этот подзапрос занимает около 40 секунд, и при этом он находится внутри вложенного цикла, который должен выполниться для соединения, прежде чем мы даже дойдём до предложения WHERE. Именно это, по-видимому, приводит к резкому росту стоимости запроса.
SELECT id, percent_rank()
OVER (PARTITION BY topic_id ORDER BY SCORE DESC) as percent_rank
FROM posts
Я посмотрел на код, и теперь всё понятно. Этот запрос выполняется repeatedly, пока не будут обработаны все посты, ранг которых нужно обновить. Поэтому, когда я наблюдал за процессами SQL, я видел множество запусков этого запроса в течение нескольких часов: один запуск завершался, и начинался следующий.
Однако, похоже, что LIMIT не так уж сильно влияет на сложность запроса. На самом деле, я обнаружил, что если увеличить лимит до 200000, план запроса значительно улучшится.
При значении 20000 я вижу огромный вложенный цикл с стоимостью почти 200 миллионов:
Если же я просто увеличу лимит до 200000, то будет сгенерирован следующий план запроса, который выглядит гораздо чище:
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------
Update on posts (cost=7334394.89..8873863.82 rows=200000 width=833)
-> Nested Loop (cost=7334394.89..8873863.82 rows=200000 width=833)
-> Subquery Scan on x (cost=7334394.45..7370147.82 rows=200000 width=48)
-> Limit (cost=7334394.45..7368147.82 rows=200000 width=12)
-> Hash Join (cost=7334394.45..11837188.36 rows=26680560 width=12)
Hash Cond: (posts_1.id = y.id)
Join Filter: ((posts_1.percent_rank IS NULL) OR (y.percent_rank <> posts_1.percent_rank))
-> Hash Join (cost=35403.20..2845221.73 rows=26703700 width=12)
Hash Cond: (posts_1.topic_id = topics.id)
-> Seq Scan on posts posts_1 (cost=0.00..2739721.00 rows=26703700 width=16)
-> Hash (cost=23918.11..23918.11 rows=918807 width=4)
-> Index Only Scan using topics_pkey on topics (cost=0.42..23918.11 rows=918807 width=4)
-> Hash (cost=6834805.00..6834805.00 rows=26703700 width=12)
-> Subquery Scan on y (cost=6033694.00..6834805.00 rows=26703700 width=12)
-> WindowAgg (cost=6033694.00..6567768.00 rows=26703700 width=24)
-> Sort (cost=6033694.00..6100453.25 rows=26703700 width=16)
Sort Key: posts_2.topic_id, posts_2.score DESC
-> Seq Scan on posts posts_2 (cost=0.00..2739721.00 rows=26703700 width=16)
-> Index Scan using posts_pkey on posts (cost=0.44..7.52 rows=1 width=789)
Index Cond: (id = x.id)
JIT:
Functions: 30
Options: Inlining true, Optimization true, Expressions true, Deforming true
(23 rows)
Интересно, что вызывает такое переключение. Возможно, стоит увеличить этот лимит хотя бы после крупного импорта, как мы сделали, пока очередь не будет обработана.