Я выполняю обновление, где мне требуется точное равенство для tstzrange
переменной. Изменено ~ 1M строк, а запрос занимает ~ 13 минут. Результат EXPLAIN ANALYZE
можно увидеть здесь , и фактические результаты чрезвычайно отличаются от тех, которые оцениваются планировщиком запросов. Проблема заключается в том, что при сканировании индекса t_range
ожидается возврат одной строки.
Похоже, это связано с тем, что статистика по типам диапазонов хранится не так, как статистика других типов. Глядя на pg_stats
представление для столбца, n_distinct
это -1, а другие поля (например most_common_vals
, most_common_freqs
) пусты.
Тем не менее, где-то должна храниться статистика t_range
. Чрезвычайно похожее обновление, где я использую 'inside' для t_range вместо точного равенства, занимает около 4 минут и использует существенно другой план запросов (см. Здесь ). Второй план запроса имеет смысл для меня, потому что будет использоваться каждая строка во временной таблице и значительная часть таблицы истории. Что еще более важно, планировщик запросов прогнозирует приблизительно правильное количество строк для фильтра t_range
.
Распределение t_range
немного необычно. Я использую эту таблицу для хранения исторического состояния другой таблицы, и изменения в другой таблице происходят сразу в больших дампах, поэтому не так много разных значений t_range
. Вот количество, соответствующее каждому из уникальных значений t_range
:
t_range | count
-------------------------------------------------------------------+---------
["2014-06-12 20:58:21.447478+00","2014-06-27 07:00:00+00") | 994676
["2014-06-12 20:58:21.447478+00","2014-08-01 01:22:14.621887+00") | 36791
["2014-06-27 07:00:00+00","2014-08-01 07:00:01+00") | 1000403
["2014-06-27 07:00:00+00",infinity) | 36791
["2014-08-01 07:00:01+00",infinity) | 999753
Подсчеты для различных t_range
выше полны, поэтому количество элементов составляет ~ 3M (из которых ~ 1M будет зависеть от любого запроса на обновление).
Почему запрос 1 выполняется намного хуже, чем запрос 2? В моем случае, запрос 2 является хорошей заменой, но если действительно требуется точное равенство диапазонов, как я могу заставить Postgres использовать более разумный план запросов?
Определение таблицы с индексами (отбрасывание ненужных столбцов):
Column | Type | Modifiers
---------------------+-----------+------------------------------------------------------------------------------
history_id | integer | not null default nextval('gtfs_stop_times_history_history_id_seq'::regclass)
t_range | tstzrange | not null
trip_id | text | not null
stop_sequence | integer | not null
shape_dist_traveled | real |
Indexes:
"gtfs_stop_times_history_pkey" PRIMARY KEY, btree (history_id)
"gtfs_stop_times_history_t_range" gist (t_range)
"gtfs_stop_times_history_trip_id" btree (trip_id)
Запрос 1:
UPDATE gtfs_stop_times_history sth
SET shape_dist_traveled = tt.shape_dist_traveled
FROM gtfs_stop_times_temp tt
WHERE sth.trip_id = tt.trip_id
AND sth.stop_sequence = tt.stop_sequence
AND sth.t_range = '["2014-08-01 07:00:01+00",infinity)'::tstzrange;
Запрос 2:
UPDATE gtfs_stop_times_history sth
SET shape_dist_traveled = tt.shape_dist_traveled
FROM gtfs_stop_times_temp tt
WHERE sth.trip_id = tt.trip_id
AND sth.stop_sequence = tt.stop_sequence
AND '2014-08-01 07:00:01+00'::timestamptz <@ sth.t_range;
Q1 обновляет 999753 строки и Q2 обновляет 999753 + 36791 = 1036544 (т. Е. Временная таблица такова, что обновляется каждая строка, соответствующая условию временного диапазона).
Я попробовал этот запрос в ответ на комментарий @ ypercube :
Запрос 3:
UPDATE gtfs_stop_times_history sth
SET shape_dist_traveled = tt.shape_dist_traveled
FROM gtfs_stop_times_temp tt
WHERE sth.trip_id = tt.trip_id
AND sth.stop_sequence = tt.stop_sequence
AND sth.t_range <@ '["2014-08-01 07:00:01+00",infinity)'::tstzrange
AND '["2014-08-01 07:00:01+00",infinity)'::tstzrange <@ sth.t_range;
План запроса и результаты (см. Здесь ) были промежуточными между двумя предыдущими случаями (~ 6 минут).
2016/02/05 РЕДАКТИРОВАТЬ
Больше не имея доступа к данным через 1,5 года, я создал тестовую таблицу с той же структурой (без индексов) и схожим количеством элементов. В ответе jjanes предполагалось, что причиной может быть упорядочение временной таблицы, используемой для обновления. Я не смог проверить гипотезу напрямую, потому что у меня нет доступа к ней track_io_timing
(с помощью Amazon RDS).
Общие результаты были намного быстрее (в несколько раз). Я предполагаю, что это из-за удаления индексов, в соответствии с ответом Эрвина .
В этом тестовом примере запросы 1 и 2 в основном занимали одинаковое количество времени, поскольку они оба использовали объединение слиянием. То есть я не смог вызвать то, что заставило Postgres выбрать хеш-соединение, поэтому у меня нет ясности относительно того, почему Postgres выбрал плохо работающее хеш-соединение.
(a = b)
два «содержит» условия:(a @> b AND b @> a)
? Меняется ли план?(lower(t_range),upper(t_range))
поскольку вы проверяете равенство.Ответы:
Самая большая разница во времени в ваших планах выполнения находится на верхнем узле, самом ОБНОВЛЕНИИ. Это говорит о том, что большая часть вашего времени уходит на IO во время обновления. Вы можете убедиться в этом, включив
track_io_timing
и выполнив запросы сEXPLAIN (ANALYZE, BUFFERS)
Различные планы представляют строки, которые будут обновлены в разных порядках. Один в
trip_id
порядке, а другой - в любом порядке, в котором они физически присутствуют в таблице временных параметров.Обновляемая таблица, кажется, имеет свой физический порядок, соотнесенный со столбцом trip_id, и обновление строк в этом порядке приводит к эффективным шаблонам ввода-вывода с чтением вперед / последовательным чтением. В то время как физический порядок временной таблицы, кажется, приводит к большому количеству случайных чтений.
Если вы можете добавить
order by trip_id
в оператор, который создал временную таблицу, это может решить проблему для вас.PostgreSQL не учитывает эффекты упорядочения ввода-вывода при планировании операции UPDATE. (В отличие от операций SELECT, где он учитывает их). Если бы PostgreSQL был умнее, он либо понял бы, что один план создает более эффективный порядок, либо вставил бы явный узел сортировки между обновлением и его дочерним узлом, чтобы обновление получало строки в порядке ctid.
Вы правы в том, что PostgreSQL плохо справляется с оценкой селективности соединений равенства по диапазонам. Однако это только косвенно связано с вашей фундаментальной проблемой. Более эффективный запрос для выбранной части вашего обновления может случайно привести строки в собственно обновление в лучшем порядке, но если это так, то это в основном неудача.
источник
track_io_timing
, и (поскольку прошло полтора года!) У меня больше нет доступа к исходным данным. Однако я проверил вашу теорию, создав таблицы с одинаковой схемой и одинаковым размером (миллионы строк) и запустив два разных обновления - одно, в котором таблица временных обновлений была отсортирована как исходная таблица, и другое, в котором она была отсортирована квазислучайный. К сожалению, два обновления занимают примерно одинаковое количество времени, подразумевая, что порядок таблицы обновлений не влияет на этот запрос.Я не совсем уверен, почему избирательность предиката равенства так радикально завышена индексом GiST на
tstzrange
столбце. Хотя это само по себе интересно, похоже, оно не имеет отношения к вашему конкретному случаю.Так как ваша
UPDATE
модификация одной трети (!) Всех существующих строк 3M, индекс не поможет вообще . Напротив, постепенное обновление индекса в дополнение к таблице значительно увеличит ваши расходыUPDATE
.Просто оставьте свой простой запрос 1 . Простое, радикальное решение - сбросить индекс до
UPDATE
. Если вам это нужно для других целей, создайте его заново послеUPDATE
. Это все равно будет быстрее, чем поддерживать индекс во время большогоUPDATE
.Для
UPDATE
одной трети всех строк, вероятно, придется заплатить и за удаление всех других индексов - и воссоздание их послеUPDATE
. Единственный недостаток: вам нужны дополнительные привилегии и эксклюзивная блокировка на столе (только на короткое время, если вы используетеCREATE INDEX CONCURRENTLY
).Идея @ ypercube использовать btree вместо индекса GiST в принципе хороша. Но не для одной трети всех строк (где ни один индекс не годится для начала), и не для просто
(lower(t_range),upper(t_range))
, так как этоtstzrange
не дискретный тип диапазона.Большинство типов дискретных диапазонов имеют каноническую форму, что упрощает концепцию «равенства»: ее определяют нижняя и верхняя границы значения в канонической форме. Документация:
Это не тот случай
tstzrange
, когда необходимо учитывать инклюзивность верхней и нижней границ на равенство. Возможный индекс btree должен быть включен:И запросы должны будут использовать те же выражения в
WHERE
предложении.Может возникнуть соблазн просто индексировать все значение, приведенное к
text
:- но это выражение не(cast(t_range AS text))
IMMUTABLE
так, поскольку текстовое представлениеtimestamptz
значений зависит от текущейtimezone
настройки. Вам нужно будет добавить дополнительные шаги в функцию-IMMUTABLE
оболочку, которая создает каноническую форму, и создать функциональный индекс для этого ...Дополнительные меры / альтернативные идеи
Если у вас
shape_dist_traveled
уже может быть то же значение, что иtt.shape_dist_traveled
для нескольких обновленных строк (и вы не полагаетесь на какие-либо побочные эффекты вашихUPDATE
подобных триггеров ...), вы можете ускорить запрос, исключив пустые обновления:Конечно, все общие советы по оптимизации производительности применимы. Postgres Wiki - хорошая отправная точка.
VACUUM FULL
будет ядом для вас, так как некоторые мертвые кортежи (или место, зарезервированное дляFILLFACTOR
) полезно дляUPDATE
производительности.С таким количеством обновленных строк, и если вы можете себе это позволить (без одновременного доступа или других зависимостей), может быть даже быстрее написать совершенно новую таблицу вместо обновления на месте. Инструкции в этом связанном ответе:
источник