У меня есть таблица postgres, которая выглядит следующим образом:
id: int [PK]
scenario_id: int [FK]
node_id: int Optional[FK]
element_id: int Optional[FK]
result: char(20)
value: double
unit: char(12)
Где я сохраняю строки для различных результатов как для узлов, так и для элементов моего графика. Если результирующая переменная позволяет сказать, что x действителен только для узла, тогда строка выглядит следующим образом:
id scenario_id node_id element_id result value unit
1 1 100 [null] x 0.1 'MW'
Я понял, что после добавления различных сценариев эта таблица очень быстро становится очень большой (40 миллионов записей в 5000 сценариях). И мой запрос на получение всех результатов 1 сценария занимает в среднем 7 секунд, согласно pgadmin.
Как я могу сделать эту таблицу более эффективной, чтобы запрос не занимал 7 секунд?
ЗАПРОС:
SELECT * FROM simulation.scenario_results t
WHERE t.scenario_id = 5000
Объяснять:
СКРИПТ DLL:
-- Table: simulation.scenario_results
-- DROP TABLE IF EXISTS simulation.scenario_results;
CREATE TABLE IF NOT EXISTS simulation.scenario_results
(
id integer NOT NULL DEFAULT nextval('simulation.scenario_results_id_seq'::regclass),
scenario_id integer NOT NULL,
node_id integer,
element_id integer,
result character varying(20) COLLATE pg_catalog."default" NOT NULL,
value double precision NOT NULL,
unit character varying(12) COLLATE pg_catalog."default",
CONSTRAINT scenario_results_pkey PRIMARY KEY (id),
CONSTRAINT scenario_results_element_id_fkey FOREIGN KEY (element_id)
REFERENCES simulation.elements (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE,
CONSTRAINT scenario_results_node_id_fkey FOREIGN KEY (node_id)
REFERENCES simulation.nodes (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE,
CONSTRAINT scenario_results_scenario_id_fkey FOREIGN KEY (scenario_id)
REFERENCES simulation.scenarios (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS simulation.scenario_results
OWNER to postgres;
8 тыс. строк на сценарий — это не так уж много, и тот факт, что в общей сложности таблица имеет 40 млн, не должен иметь особого значения для отдельных сценариев, если вы используете адекватное индексирование, секционирование и кластеризацию. Помимо того, что NickW уже запросил, добавьте определения индексов и укажите, какая это версия PostgreSQL и на чем она работает. Если вы используете pgAdmin, вы можете щелкнуть правой кнопкой мыши по таблице>Scripts
>CREATE Script
, чтобы получить полный оператор DDL для таблицы вместе со всеми ее индексами и ограничениями. Это будет более информативно, а также позволит воспроизвести ваш случай.
Похоже, вы просто неправильно индексируете, поскольку 8 тысяч строк должны быть почти мгновенными в хорошо индексированной таблице.
@NickW обновлено!
Поместите индекс на сценарий_id
@NickW всегда ли полезно размещать индексы на всех внешних ключах? или актуально только для этого запроса?
Вы помещаете индексы в столбцы (в основном) для повышения производительности запросов - поэтому это зависит от того, какие запросы вы выполняете, какие индексы вам нужны (и есть затраты на поддержание индексов, поэтому вы не просто индексируете все). FK, скорее всего, будут использоваться в запросах, поэтому они обычно индексируются. Вы также можете рассмотреть возможность индексирования столбцов, используемых в предложениях WHERE, как в этом случае.
На самом деле все FK должны иметь индексы из-за проверок ограничений (когда вы удаляете родительскую строку, необходимо проверить, существуют ли какие-либо дочерние строки).
Похоже, единственный индекс, который у вас есть, — это unique
, который сопровождается primary key
. Даже простое btree по умолчанию для столбца, по которому вы фильтруете, ускорит процесс:
create index on simulation.scenario_results(scenario_id);
После этого не забудьте vacuum analyze simulation.scenario_results;
, а затем снова проверьте свой запрос, чтобы увидеть, что последовательное сканирование заменено гораздо более быстрым индексным сканированием. Первое означает, что вашим запросам приходилось каждый раз читать все целиком, чтобы найти ваш конкретный сценарий, второе означает, что теперь он проверяет, какие части таблицы можно полностью пропустить и где найти конкретный scenario_id
.
В этом тесте на 300 тысячах случайных строк parallel seq scan
требовалось 90ms
, а index scan
опускалось ниже 1ms
.
Вы можете ускорить этот процесс двумя способами: либо сделать индекс покрывающим индексом, что означает, что все данные, которые вам могут понадобиться из таблицы, будут находиться в нем, поэтому запрос сможет читать все прямо из индекса, даже не чтобы посетить таблицу, которая представляет собой сканирование только индекса, которое в тесте сводилось к 0.3ms
:
create index on simulation.scenario_results(scenario_id)
include(id,node_id,element_id,result,value,unit);
Или вы можете кластеризовать таблицу таким образом, чтобы ускорить переход от обычного легковесного индекса, который занимает всего лишь 0.4ms
, без необходимости дублировать данные между таблицей и индексом:
create index idx1 on simulation.scenario_results(scenario_id);
cluster verbose simulation.scenario_results using idx1;
analyze simulation.scenario_results ;
Пожалуйста, обновите свой вопрос, указав 1) запрос, который вы выполняете 2) подробное ОБЪЯСНЕНИЕ запроса 3) производительность, которую вы хотите достичь