Оптимизатор Postgres: почему речь идет о затратах? [ПРАВКА] Как выбрать random_page_cost? - PullRequest
1 голос
/ 12 марта 2019

У меня возникла следующая проблема с Postgres:

Получил две таблицы A и B:

A got 64 mln records
B got 16 mln records
A got b_id field which is indexed --> ix_A_b_id
B got datetime_field which is indexed --> ix_B_datetime

Получил следующий запрос:

SELECT 
  A.id, 
  B.some_field 
FROM 
  A 
JOIN 
  B 
ON A.b_id = B.id
WHERE
  B.datetime_field BETWEEN 'from' AND 'to'

Этот запрос хорош, когда разница между from и to небольшая, в этом случае postgres использует оба индекса, и я получаю результаты довольно быстро

Когда разница между датами больше, запрос сильно замедляется, потому что postgres решает использовать только ix_B_datetimeа затем полное сканирование таблицы с 64 М записями ... что просто глупо

Я нашел точку, когда оптимизатор решает, что использование полного сканирования быстрее.Для дат между

2019-03-10 17:05:00 и 2019-03-15 01:00:00

он получил такую ​​же стоимость, как и для

2019-03-10 17:00:00 и 2019-03-15 01:00:00.

Но время выборки для первого запроса составляет около 50 мс, а для второго - почти 2 минуты.

Планы ниже

Nested Loop  (cost=1.00..3484455.17 rows=113057 width=8)
  ->  Index Scan using ix_B_datetime on B  (cost=0.44..80197.62 rows=28561 width=12)
        Index Cond: ((datetime_field >= '2019-03-10 17:05:00'::timestamp without time zone) AND (datetime_field < '2019-03-15 01:00:00'::timestamp without time zone))
  ->  Index Scan using ix_A_b_id on A  (cost=0.56..112.18 rows=701 width=12)
        Index Cond: (b_id = B.id)
Hash Join  (cost=80615.72..3450771.89 rows=113148 width=8)
  Hash Cond: (A.b_id = B.id)
  ->  Seq Scan on spot  (cost=0.00..3119079.50 rows=66652050 width=12)
  ->  Hash  (cost=80258.42..80258.42 rows=28584 width=12)
        ->  Index Scan using ix_B_datetime on B  (cost=0.44..80258.42 rows=28584 width=12)
              Index Cond: ((datetime_field >= '2019-03-10 17:00:00'::timestamp without time zone) AND (datetime_field < '2019-03-15 01:00:00'::timestamp without time zone))

Итак, мой вопрос, почему мой Postgres лжет о расходах?Почему он рассчитывает что-то более дорогое, чем оно есть на самом деле?Как это исправить?

Временно мне пришлось переписать запрос, чтобы всегда использовать индекс для таблицы A, но мне не нравится следующее решение, потому что оно хакерское, непонятное и медленное для небольших кусков данных, но гораздо быстрее для большихчанки

with cc as (
     select id, some_field from B WHERE B.datetime_field >= '2019-03-08'
  AND B.datetime_field < '2019-03-15'
  )
SELECT X.id, Y.some_field
FROM (SELECT b_id, id from A where b_id in (SELECT id from cc)) X
JOIN (SELECT id, some_field FROM cc) Y ON X.b_id = Y.id

РЕДАКТИРОВАТЬ: так как @a_horse_with_no_name предположил, что я играл с RANDOM_PAGE_COST

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

SELECT count(*) FROM (
SELECT 
  A.id, 
  B.some_field 
FROM 
  A 
JOIN 
  B 
ON A.b_id = B.id
WHERE
  B.datetime_field BETWEEN '2019-03-01 00:00:00' AND '2019-03-15 01:00:00'
) A

И я протестировал разные уровни стоимости

RANDOM_PAGE_COST = 0.25

Aggregate  (cost=3491773.34..3491773.35 rows=1 width=8) (actual time=4166.998..4166.999 rows=1 loops=1)
  Buffers: shared hit=1939402
  ->  Nested Loop  (cost=1.00..3490398.51 rows=549932 width=0) (actual time=0.041..3620.975 rows=2462836 loops=1)
        Buffers: shared hit=1939402
        ->  Index Scan using ix_B_datetime_field on B  (cost=0.44..24902.79 rows=138927 width=8) (actual time=0.013..364.018 rows=313399 loops=1)
              Index Cond: ((datetime_field >= '2019-03-01 00:00:00'::timestamp without time zone) AND (datetime_field < '2019-03-15 01:00:00'::timestamp without time zone))
              Buffers: shared hit=311461
        ->  Index Only Scan using A_b_id_index on A  (cost=0.56..17.93 rows=701 width=8) (actual time=0.004..0.007 rows=8 loops=313399)
              Index Cond: (b_id = B.id)
              Heap Fetches: 2462836
              Buffers: shared hit=1627941
Planning time: 0.316 ms
Execution time: 4167.040 ms

RANDOM_PAGE_COST = 1

Aggregate  (cost=3918191.39..3918191.40 rows=1 width=8) (actual time=281236.100..281236.101 rows=1 loops=1)
"  Buffers: shared hit=7531789 read=2567818, temp read=693 written=693"
  ->  Merge Join  (cost=102182.07..3916816.56 rows=549932 width=0) (actual time=243755.551..280666.992 rows=2462836 loops=1)
        Merge Cond: (A.b_id = B.id)
"        Buffers: shared hit=7531789 read=2567818, temp read=693 written=693"
        ->  Index Only Scan using A_b_id_index on A  (cost=0.56..3685479.55 rows=66652050 width=8) (actual time=0.010..263635.124 rows=64700055 loops=1)
              Heap Fetches: 64700055
              Buffers: shared hit=7220328 read=2567818
        ->  Materialize  (cost=101543.05..102237.68 rows=138927 width=8) (actual time=523.618..1287.145 rows=2503965 loops=1)
"              Buffers: shared hit=311461, temp read=693 written=693"
              ->  Sort  (cost=101543.05..101890.36 rows=138927 width=8) (actual time=523.616..674.736 rows=313399 loops=1)
                    Sort Key: B.id
                    Sort Method: external merge  Disk: 5504kB
"                    Buffers: shared hit=311461, temp read=693 written=693"
                    ->  Index Scan using ix_B_datetime_field on B  (cost=0.44..88589.92 rows=138927 width=8) (actual time=0.013..322.016 rows=313399 loops=1)
                          Index Cond: ((datetime_field >= '2019-03-01 00:00:00'::timestamp without time zone) AND (datetime_field < '2019-03-15 01:00:00'::timestamp without time zone))
                          Buffers: shared hit=311461
Planning time: 0.314 ms
Execution time: 281237.202 ms

RANDOM_PAGE_COST = 2

Aggregate  (cost=4072947.53..4072947.54 rows=1 width=8) (actual time=166896.775..166896.776 rows=1 loops=1)
"  Buffers: shared hit=696849 read=2067171, temp read=194524 written=194516"
  ->  Hash Join  (cost=175785.69..4071572.70 rows=549932 width=0) (actual time=29321.835..166332.812 rows=2462836 loops=1)
        Hash Cond: (A.B_id = B.id)
"        Buffers: shared hit=696849 read=2067171, temp read=194524 written=194516"
        ->  Seq Scan on A  (cost=0.00..3119079.50 rows=66652050 width=8) (actual time=0.008..108959.789 rows=64700055 loops=1)
              Buffers: shared hit=437580 read=2014979
        ->  Hash  (cost=173506.11..173506.11 rows=138927 width=8) (actual time=29321.416..29321.416 rows=313399 loops=1)
              Buckets: 131072 (originally 131072)  Batches: 8 (originally 2)  Memory Usage: 4084kB
"              Buffers: shared hit=259269 read=52192, temp written=803"
              ->  Index Scan using ix_B_datetime_field on B  (cost=0.44..173506.11 rows=138927 width=8) (actual time=1.676..29158.413 rows=313399 loops=1)
                    Index Cond: ((datetime_field >= '2019-03-01 00:00:00'::timestamp without time zone) AND (datetime_field < '2019-03-15 01:00:00'::timestamp without time zone))
                    Buffers: shared hit=259269 read=52192
Planning time: 7.367 ms
Execution time: 166896.824 ms

Тем не менее, для меня неясно, стоимость 0,25 лучше для меня, но везде я могу прочитать, что для диска ssd это должно быть 1-1,5.(Я использую экземпляр AWS с ssd)

Что странного в плане стоимости 1 хуже, чем в 2 и 0,25 Так какое значение выбрать?Есть ли возможность рассчитать это?Затраты 0,25> 2> 1 в этом случае, а как в других случаях?Как я могу быть уверен, что 0,25, что хорошо для моего запроса, не сломает другие запросы.Нужно ли писать тесты производительности для каждого полученного запроса?

...