У меня есть таблица с данными временных рядов и с первичным ключом - на самом деле несколько больших таблиц, но это подойдет в качестве примера:
postgres=# create table foo (x varchar, y varchar, z text, updated timestamptz, primary key(x, y, updated));
CREATE TABLE
postgres=# \d foo
Table "public.foo"
Column | Type | Collation | Nullable | Default
---------+--------------------------+-----------+----------+---------
x | character varying | | not null |
y | character varying | | not null |
z | text | | |
updated | timestamp with time zone | | not null |
Indexes:
Это автоматически создаст обновленный индекс для x. Однако многие из моих запросов ищут «самый последний foo для различных значений x», а не самый старый, поэтому индекс является неправильным для эффективных запросов.
Например, если я это сделаю:
explain analyze select distinct on (x,y,updated) * from foo order by x,y,updated;
Затем он прекрасно использует индекс:
----------------------------------------------------------------------------------------------------------------------------------------
Unique (cost=0.43..145739.21 rows=158334 width=38) (actual time=0.031..889.771 rows=1583344 loops=1)
-> Index Scan using foo_pkey on foo (cost=0.43..133864.13 rows=1583344 width=38) (actual time=0.030..534.995 rows=1583344 loops=1)
Planning Time: 0.074 ms
Execution Time: 958.419 ms
(4 rows)
но, конечно, показывает мне самые старые элементы.
Если я это сделаю:
userdb=> explain analyze select distinct on (x,y,updated) * from foo order by x, y, updated desc;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------
Unique (cost=278945.67..294779.11 rows=158334 width=38) (actual time=3823.677..4808.844 rows=1583344 loops=1)
-> Sort (cost=278945.67..282904.03 rows=1583344 width=38) (actual time=3823.674..4482.405 rows=1583344 loops=1)
Sort Key: x, y, updated DESC
Sort Method: external merge Disk: 79536kB
-> Seq Scan on foo (cost=0.00..29314.44 rows=1583344 width=38) (actual time=0.013..142.781 rows=1583344 loops=1)
Planning Time: 0.075 ms
Execution Time: 4888.878 ms
(7 rows)
затем он дает мне самые последние данные, а это то, что мне нужно, но это занимает в пять раз больше времени.
Я хотел бы получить следующее:
Table "public.foo"
Column | Type | Collation | Nullable | Default
---------+--------------------------+-----------+----------+---------
x | character varying | | not null |
y | character varying | | not null |
z | integer | | |
updated | timestamp with time zone | | not null |
Indexes:
"foo_pkey" PRIMARY KEY, btree (x, y, updated DESC)
Это просто приводит к синтаксической ошибке:
create table foo (x varchar, y varchar, z text, updated timestamptz, primary key(x, y, updated desc));
Я могу сделать это:
postgres=# create index on foo (x, y, updated desc);
CREATE INDEX
postgres=#
который исправляет запрос, но затем я получаю два индекса, которые явно менее производительны, чем один. Я могу удалить первичный ключ, но это выглядит довольно некрасиво. Есть ли лучший способ получить то, что я хочу? Есть ли веская причина, по которой ПК генерирует индекс с определенным порядком и почему обратный порядок может быть проблематичным?
Единственные ссылки, которые я вижу в других местах, - это ветка (https://www.postgresql.org/message-id/[email protected]), в которой предполагается, что уникальный индекс и отсутствие PK - лучший вариант, но это 15 лет. Есть ли что-нибудь лучше сейчас?
то есть действительно ли это лучшее, что я могу сделать?
=> alter table foo drop constraint foo_pkey;
ALTER TABLE
=> create unique index on foo(x,y,updated desc);
CREATE INDEX
=> explain analyze select distinct on (x,y,updated) * from foo order by x, y, updated desc;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------
Unique (cost=0.43..126019.21 rows=158334 width=38) (actual time=0.025..867.984 rows=1583344 loops=1)
-> Index Scan using foo_x_y_updated_idx on foo (cost=0.43..114144.13 rows=1583344 width=38) (actual time=0.024..521.720 rows=1583344 loops=1)
Planning Time: 0.171 ms
Execution Time: 935.838 ms
(4 rows)
уникальный индекс и отсутствие ПК - лучший вариант... Есть ли что-нибудь лучше сейчас? Нет, все еще в силе и для того, чтобы вы хотели лучшего.
Какую проблему оно должно решить? И почему ваш текущий запрос не может выбрать правильный план запроса? Пожалуйста, поделитесь результатом explain(analyze, verbose, buffers, settings)
для этого оператора SQL и самого оператора.
@AdrianKlaver Да, мой вопрос в том, могу ли я заставить его использовать другой индекс для выполнения той же работы, и Belayer звучит так, как будто ответ по-прежнему отрицательный.
1) Это достаточно легко проверить: запустите запрос, используя EXPLAIN ANALYZE
без create index on foo (x, updated desc);
и с EXPLAIN ANALYZE
, и посмотрите, каковы результаты? 2) Согласно комментарию @FrankHeikens, вам следует сделать это с вашей текущей настройкой и добавить результаты в виде текста к тексту вашего вопроса.
Сравнивали ли вы производительность (x, updated)
и (x, updated DESC)
, используя реальные запросы, или это всего лишь предположения? Может быть разница в производительности, если DISTINCT ON (x) ... ORDER BY x, updated DESC
используется для определения самого последнего обновления для каждого x; однако SELECT x, MAX(updated) ... GROUP BY x
, скорее всего, будет работать сравнимо с любым вариантом.
Первичный ключ и индексный сервер имеют разные цели. Первый обеспечивает уникальность строк, а второй помогает повысить производительность запросов. В некоторых случаях один из них может служить обеим целям. Однако, похоже, это не так.
Какую версию вы используете? Имея только индекс pk, я получаю инкрементную сортировку, которая должна быть довольно хорошей.
@jjanes Aurora бессерверная — «PostgreSQL 14.9 на aarch64-unknown-linux-gnu, скомпилированная aarch64-unknown-linux-gnu-gcc (GCC) 9.5.0, 64-разрядная версия»
Похоже, что возможность использовать инкрементальную сортировку не была расширена до DISTINCT (и, очевидно, не до DISTINCT ON) до версии 16.
Вы не можете создать первичный ключ с индексом, который сортирует столбец по убыванию.
Но для вашего случая использования это может и не потребоваться: PostgreSQL может сканировать индекс в порядке убывания так же эффективно, как и в порядке возрастания, поэтому вам следует переписать запрос следующим образом:
SELECT DISTINCT ON (x, y, updated) *
FROM foo
ORDER BY x DESC, y DESC, updated DESC;
Это позволит просканировать индекс первичного ключа назад и не потребует сортировки.
Если для вас важно, чтобы результат был отсортирован по возрастанию для x
и y
, вам придется либо стиснуть зубы и создать второй индекс, либо добавить еще одну сортировку после DISTINCT
, что должно быть немного дешевле, чем сортировка до этого. DISTINCT
:
SELECT *
FROM (SELECT DISTINCT ON (x, y, updated) *
FROM foo
ORDER BY x DESC, y DESC, updated DESC
) AS q
ORDER BY x, y;
Я обновил вопрос, добавив «отдельные» примеры, которые показывают, что это не обязательно так.
Он работает для «объяснения анализа выбора x, y, max (обновлено) из группы foo на 1,2;» против «объясните анализ выбора x, y, min(обновлено) из группы foo на 1,2;» и я не совсем понимаю, почему отдельные элементы ведут себя так по-другому.
Итак, «объясните анализ, выберите различные по (x,y,updated) * from foo в порядке x desc, y desc, обновленного desc;» дает мне «Сканирование индекса назад», так что этот вид работает, и его полезно знать, но опция уникального индекса работает в два раза быстрее, так что это то, что я собираюсь сделать. Спасибо. Исходное утверждение: «Вы не можете создать первичный ключ с индексом, который сортируется по убыванию столбца». это правильный ответ (хотя он мне не нужен)
Верно, но я хотел показать вам выход, а не просто сказать: «Вы не можете этого получить». DISTINCT ON
работает следующим образом: результат сортируется в соответствии с ORDER BY
, и для каждой группы, определенной в предложении DISTINCT ON
, используется первая строка результата. Таким образом, индекс можно использовать для DISTINCT ON
, если его можно использовать для предложения ORDER BY
.
Потому что CREATE TABLE: ограничение PRIMARY KEY указывает, что столбец или столбцы таблицы могут содержать только уникальные (не повторяющиеся), ненулевые значения. и добавление ограничения PRIMARY KEY автоматически создаст уникальный индекс btree. Таким образом, ПК является ограничением, а не индексом, хотя для него создан общий индекс. Если вам нужно что-то менее общее, вам нужно будет создать специальный индекс для столбцов.