Медленный отсчет окончательно решен - PullRequest
1 голос
/ 20 апреля 2020

tl; dr: |> Repo.aggregate(:count, :id) медленно, используйте |> Repo.aggregate(:count)

Я использую базу данных подкастов с> 5 миллионами эпизодов. После сохранения нового эпизода я подсчитываю эпизоды для данного подкаста для счетного кэша следующим образом:

episodes_count = where(Episode, podcast_id: ^podcast_id)
                 |> Repo.aggregate(:count, :id)

Получилось все медленнее и медленнее. Поэтому я начал копать глубже и понял, что в Postgres 12 only SELECT COUNT(*) делает сканирование только по индексу, а SELECT COUNT(e0.id) нет.

Для холодной базы данных (только перезапускается) даже первое сканирование индекса достаточно быстрое:

postgres=# \c pan_prod
Sie sind jetzt verbunden mit der Datenbank »pan_prod« als Benutzer »postgres«.
pan_prod=# EXPLAIN ANALYZE SELECT count(*) FROM "episodes" AS e0 WHERE (e0."podcast_id" = 35202);
                                                                         QUERY PLAN                                                                          
-------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=348.51..348.52 rows=1 width=8) (actual time=15.823..15.823 rows=1 loops=1)
   ->  Index Only Scan using episodes_podcast_id_index on episodes e0  (cost=0.43..323.00 rows=10204 width=0) (actual time=1.331..14.832 rows=10613 loops=1)
         Index Cond: (podcast_id = 35202)
         Heap Fetches: 0
 Planning Time: 2.994 ms
 Execution Time: 16.017 ms

Для второго сканирования оно становится еще быстрее:

pan_prod=# EXPLAIN ANALYZE SELECT count(*) FROM "episodes" AS e0 WHERE (e0."podcast_id" = 35202);
                                                                         QUERY PLAN                                                                         
------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=348.51..348.52 rows=1 width=8) (actual time=5.007..5.008 rows=1 loops=1)
   ->  Index Only Scan using episodes_podcast_id_index on episodes e0  (cost=0.43..323.00 rows=10204 width=0) (actual time=0.042..3.548 rows=10613 loops=1)
         Index Cond: (podcast_id = 35202)
         Heap Fetches: 0
 Planning Time: 0.304 ms
 Execution Time: 5.074 ms

В то время как первое сканирование кучи растрового изображения очень медленное:

pan_prod=# EXPLAIN ANALYZE SELECT count(e0.id) FROM "episodes" AS e0 WHERE (e0."podcast_id" = 35202);
                                                                    QUERY PLAN                                                                    
--------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=37181.71..37181.72 rows=1 width=8) (actual time=4098.525..4098.526 rows=1 loops=1)
   ->  Bitmap Heap Scan on episodes e0  (cost=219.51..37156.20 rows=10204 width=4) (actual time=6.508..4082.558 rows=10613 loops=1)
         Recheck Cond: (podcast_id = 35202)
         Heap Blocks: exact=6516
         ->  Bitmap Index Scan on episodes_podcast_id_index  (cost=0.00..216.96 rows=10204 width=0) (actual time=3.657..3.658 rows=10613 loops=1)
               Index Cond: (podcast_id = 35202)
 Planning Time: 0.412 ms
 Execution Time: 4098.719 ms

Второй тип обычно быстрее:

pan_prod=# EXPLAIN ANALYZE SELECT count(e0.id) FROM "episodes" AS e0 WHERE (e0."podcast_id" = 35202);
                                                                    QUERY PLAN                                                                    
--------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=37181.71..37181.72 rows=1 width=8) (actual time=18.857..18.857 rows=1 loops=1)
   ->  Bitmap Heap Scan on episodes e0  (cost=219.51..37156.20 rows=10204 width=4) (actual time=6.047..17.152 rows=10613 loops=1)
         Recheck Cond: (podcast_id = 35202)
         Heap Blocks: exact=6516
         ->  Bitmap Index Scan on episodes_podcast_id_index  (cost=0.00..216.96 rows=10204 width=0) (actual time=3.738..3.738 rows=10613 loops=1)
               Index Cond: (podcast_id = 35202)
 Planning Time: 0.322 ms
 Execution Time: 18.999 ms

Я не понимаю, почему SELECT count(e0.id) не использует индекс, и я хотел бы знать, почему. Я всегда думал, что должен предпочесть это, поскольку рассматривается только один столбец, но это не так, кажется.

1 Ответ

0 голосов
/ 20 апреля 2020

Я не понимаю, почему счетчик SELECT (e0.id) не использует индекс, и я хотел бы знать, почему. Я всегда думал, что должен предпочесть это, поскольку рассматривается только один столбец, но, похоже, дело не в этом.

Это использует индекс episodes_podcast_id_index оба раза.

Как только он может обойтись без дополнительного просмотра таблицы, в другой раз он заглядывает в таблицу.

Вы не указали определение индекса, но похоже, что id не является частью этого. Я также предположил бы, что это не not null способный столбец (он может содержать null значения). Таким образом, БД должна извлечь столбец id, чтобы узнать, является ли он null.

Почему?

Поскольку count(*) считает строки, а count(<expression>) считает ненулевые значения .

См .: https://modern-sql.com/concept/null#aggregates

...