Улучшить ответ на запрос PostgreSQL

#postgresql

#postgresql

Вопрос:

У меня есть одна таблица, которая включает около 100 тысяч строк и их рост и рост. У моего ответа на запрос плохое время отклика, и это влияет на мой пользовательский интерфейс.

Я хочу попросить вас о помощи, чтобы улучшить мое время отклика от базы данных. Сегодня PostgreSQL работает на моем локальном компьютере, Macbook pro 13 2019 16 RAM и I5 Core.

В будущем я загружу эту базу данных в docker и запущу ее на лучшем сервере. Что я могу сделать, чтобы улучшить его на данный момент?

Структура таблицы:

 CREATE TABLE dots
(
    dot_id INT,
    site_id INT,
    latitude float ( 6 ),
    longitude float ( 6 ),
    rsrp float ( 6 ),
    dist INT,
    project_id INT,
    dist_from_site INT,
    geom geometry,
    dist_from_ref INT,
    file_name VARCHAR
);
 

dot_id Сброс выполняется после вставки основной части данных и каждого столбца для столбца «имя_файла».

Точки таблицы:

Точки таблицы

Запросы:

Запрос №1:

 await db.query(
      `select MAX(rsrp) FROM dots where site_id=$1 and ${table}=$2 and project_id = $3 and file_name ilike $4`,
      [site_id, dist, project_id, filename]
    );
 

Время отклика: 200 мс

                                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=37159.88..37159.89 rows=1 width=4) (actual time=198.416..201.762 rows=1 loops=1)
   Buffers: shared hit=16165 read=16031
   ->  Gather  (cost=37159.66..37159.87 rows=2 width=4) (actual time=198.299..201.752 rows=3 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         Buffers: shared hit=16165 read=16031
         ->  Partial Aggregate  (cost=36159.66..36159.67 rows=1 width=4) (actual time=179.009..179.010 rows=1 loops=3)
               Buffers: shared hit=16165 read=16031
               ->  Parallel Seq Scan on dots  (cost=0.00..36150.01 rows=3861 width=4) (actual time=122.889..178.817 rows=1088 loops=3)
                     Filter: (((file_name)::text ~~* 'BigFile'::text) AND (site_id = 42047) AND (dist_from_ref = 500) AND (project_id = 1))
                     Rows Removed by Filter: 157073
                     Buffers: shared hit=16165 read=16031
 Planning Time: 0.290 ms
 Execution Time: 201.879 ms
(14 rows)
 

Запрос №2:

 await db.query(
      `SELECT DISTINCT (${table}) FROM dots where site_id=$1 and project_id = $2 and file_name ilike $3 order by ${table}`,
      [site_id, project_id, filename]
    );
 

Время отклика: 1100 мс

                                                         QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=41322.12..41322.31 rows=77 width=4) (actual time=1176.071..1176.077 rows=66 loops=1)
   Sort Key: dist_from_ref
   Sort Method: quicksort  Memory: 28kB
   Buffers: shared hit=16175 read=16021
   ->  HashAggregate  (cost=41318.94..41319.71 rows=77 width=4) (actual time=1176.024..1176.042 rows=66 loops=1)
         Group Key: dist_from_ref
         Batches: 1  Memory Usage: 24kB
         Buffers: shared hit=16175 read=16021
         ->  Seq Scan on dots  (cost=0.00..40499.42 rows=327807 width=4) (actual time=0.423..1066.316 rows=326668 loops=1)
               Filter: (((file_name)::text ~~* 'BigFile'::text) AND (site_id = 42047) AND (project_id = 1))
               Rows Removed by Filter: 147813
               Buffers: shared hit=16175 read=16021
 Planning:
   Buffers: shared hit=5 dirtied=1
 Planning Time: 0.242 ms
 Execution Time: 1176.125 ms
(16 rows)
 

Запрос #3:

 await db.query(
      `SELECT count(*) FROM dots WHERE site_id = $1 AND ${table} = $2 and project_id = $3 and file_name ilike $4`,
      [site_id, dist, project_id, filename]
    );
 

Время ответа: 200 мс

   QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=37159.88..37159.89 rows=1 width=8) (actual time=198.725..202.335 rows=1 loops=1)
   Buffers: shared hit=16160 read=16036
   ->  Gather  (cost=37159.66..37159.87 rows=2 width=8) (actual time=198.613..202.328 rows=3 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         Buffers: shared hit=16160 read=16036
         ->  Partial Aggregate  (cost=36159.66..36159.67 rows=1 width=8) (actual time=179.182..179.183 rows=1 loops=3)
               Buffers: shared hit=16160 read=16036
               ->  Parallel Seq Scan on dots  (cost=0.00..36150.01 rows=3861 width=0) (actual time=119.340..179.020 rows=1088 loops=3)
                     Filter: (((file_name)::text ~~* 'BigFile'::text) AND (site_id = 42047) AND (dist_from_ref = 500) AND (project_id = 1))
                     Rows Removed by Filter: 157073
                     Buffers: shared hit=16160 read=16036
 Planning Time: 0.109 ms
 Execution Time: 202.377 ms
(14 rows)
 

Таблицы не имеют никаких индексов.

Комментарии:

1. Не могли бы вы, пожалуйста, добавить результаты (в виде обычного текста) из EXPLAIN (ANALYZE, BUFFERS) your_sql ; ? И вы создали какой-либо индекс в этой таблице?

2. Надеюсь, теперь все в порядке, и спасибо.

3. Отсутствие индекса означает, что у вас всегда будет полное сканирование таблицы. И да, это может быть медленным.

4. Какой индекс вы предлагаете сделать?

5. По крайней мере, site_id, потому что это тот, который вы всегда используете. Другие условия также могут быть полезны. Проверьте план запроса на наличие результатов explain.depesz.com/s/KwfN

Ответ №1:

Я добавил индекс, и это немного помогло … создайте индекс idx1 для точек (site_id, project_id, имя_файла, dist_from_site, dist_from_ref)

Хорошо, это немного перебор.

Индекс по столбцам (a, b) полезен для «где a = …», а также для «где a = … и b = …», но он не очень полезен для «где b = …». Создание индекса со многими столбцами занимает больше места на диске и выполняется медленнее, чем сменьше столбцов, что является пустой тратой времени, если дополнительные столбцы в индексе не ускоряют ваши запросы. Оба столбца dist_ в индексе, вероятно, не будут использоваться.

Индексы — это компромисс: если в вашей таблице маленькие строки, например, два столбца с целыми числами, и вы создаете индекс для этих двух столбцов, тогда он будет таким же большим, как таблица, поэтому вам лучше убедиться, что он вам нужен. Но в вашем случае ваши строки довольно большие — около 5 КБ, а количество строк невелико, поэтому добавление индекса или нескольких в небольшие столбцы int требует очень небольших затрат.

Итак, поскольку вы очень часто используете условия WHERE как для site_id, так и для project_id, вы можете создать индекс для site_id, project_id . Это также будет работать для условия WHERE только для site_id. Если вы иногда используете только project_id, вы можете поменять порядок столбцов, чтобы он отображался первым, или даже создать другой индекс.

Вы говорите, что количество элементов в этих столбцах составляет около 30, поэтому выбор одного значения site_id или project_id должен составлять 1/30 или 3,3% таблицы, а выбор в обоих столбцах должен составлять 0,1% таблицы, если они некоррелированы и равномерно распределены. Это уже должно привести к существенному ускорению.

Вы также можете добавить индекс в dist_from_site, а другой — в dist_on_ref, если они обладают хорошей избирательностью (т. Е. Высокой мощностью в этих столбцах). Postgres может комбинировать индексы с растровым индексным сканированием. Но, если, скажем, 50% строк в таблице имеют одинаковое значение для dist_from_site , тогда индекс будет бесполезен для этого значения из-за недостаточной избирательности.

Вы также можете заменить предыдущий индекс из 2 столбцов на 2 индекса для site_id, project_id, dist_from_site и site_id,project_id,dist_from_ref. Вы можете попробовать, посмотреть, стоит ли это дополнительных ресурсов.

Также есть столбец filename и ILIKE. ILIKE не может использовать индекс, что означает, что он медленный. Одним из решений является использование индекса выражения

 CREATE INDEX dots_filename ON dots( lower(file_name) );
 

и замените ваше условие where на:

 lower(file_name) like lower($4)
 

При этом будет использоваться индекс, если параметр $4 не начинается с «%». И если вы никогда не используете подстановочные знаки LIKE ‘%’, а просто используете ILIKE для сравнения без учета регистра, то вы можете заменить LIKE на оператор = . По сути, lower(a) = lower (b) — это сравнение без учета регистра.

Аналогичным образом вы могли бы заменить предыдущий индекс из 2 столбцов индексом site_id,project_id,lower(filename), если вы часто используете эти три вместе в условии WHERE . Но, как было сказано выше, это не оптимизирует поиск только по имени файла.

Поскольку ваши строки огромны, даже добавление 1 КБ индекса на строку добавит к вашей таблице только 20% накладных расходов, так что вы можете переусердствовать без особых проблем. Так что продолжайте и экспериментируйте, вы увидите, что работает лучше всего.