PostgreSQL: плохая производительность запроса, вызванная неиспользованным индексом? - PullRequest
2 голосов
/ 08 апреля 2020

у нас есть запрос, чтобы получить все работы с изменениями в течение определенного периода времени. В зависимости от выбранного периода производительность снижается от <100 миллисекунд в день до ~ 7 секунд в неделю. </p>

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

Сервер работает с версией 9.2.

Почему это вызвано и как решить эту проблему?

Создать скрипт:

CREATE TABLE IF NOT EXISTS "Job" 
(
    "id" serial PRIMARY KEY,
    "serial" TEXT NOT NULL
);
CREATE UNIQUE INDEX "index_Job_serial" ON "Job" ("serial" ASC);

CREATE TABLE IF NOT EXISTS "Property" 
(
    "id" serial PRIMARY KEY,
    "name" TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS "Timestamp"
(
    "id" serial PRIMARY KEY,
    "usSince1970" BIGINT NOT NULL ,
    "localTime" TEXT
);
CREATE INDEX "index_Timestamp_usSince1970" ON "Timestamp" USING btree ("usSince1970");

CREATE  TABLE IF NOT EXISTS "Changes" 
(
    "idJob" INTEGER  NOT NULL ,
    "idProperty" INTEGER  NOT NULL ,
    "idTimestamp" INTEGER  NOT NULL ,
    "value1" decimal(25,5),
    "value2" INTEGER ,
    "value3" TEXT ,
    PRIMARY KEY ("idJob", "idProperty", "idTimestamp") ,
    FOREIGN KEY ("idJob" ) REFERENCES "Job" ("id" ) ,
    FOREIGN KEY ("idProperty" ) REFERENCES "Property" ("id" ) ,
    FOREIGN KEY ("idTimestamp" ) REFERENCES "Timestamp" ("id" )
);
CREATE INDEX "index_Changes_idJob" ON "Changes" ("idJob" ASC);
CREATE INDEX "index_Changes_idProperty" ON "Changes" ("idProperty" ASC);
CREATE INDEX "index_Changes_idTimestamp" ON "Changes" ("idTimestamp" DESC);

Быстрый запрос:

-- fast query (1 day)
SELECT DISTINCT "idJob"
FROM "Changes" 
INNER JOIN "Timestamp" ON "Timestamp"."id" = "Changes"."idTimestamp" 
WHERE "Timestamp"."usSince1970" between 1584831600000000 and 1584745200000000 

-- explain
HashAggregate  (cost=26383.48..26444.33 rows=6085 width=4) (actual time=8.039..8.078 rows=179 loops=1)
  ->  Nested Loop  (cost=0.00..26368.26 rows=6085 width=4) (actual time=0.031..7.059 rows=6498 loops=1)
        ->  Index Scan using "index_Timestamp_usSince1970" on "Timestamp"  (cost=0.00..96.25 rows=2510 width=4) (actual time=0.022..0.514 rows=2671 loops=1)
              Index Cond: (("usSince1970" >= 1584745200000000::bigint) AND ("usSince1970" <= 1584831600000000::bigint))
        ->  Index Scan using "index_Changes_idTimestamp" on "Changes"  (cost=0.00..10.27 rows=20 width=8) (actual time=0.002..0.002 rows=2 loops=2671)
              Index Cond: ("idTimestamp" = "Timestamp".id)
Total runtime: 8.204 ms

Медленный запрос:

-- slow query (7 days)
SELECT distinct "idJob"
FROM "Changes" 
INNER JOIN "Timestamp" ON "Timestamp"."id" = "Changes"."idTimestamp" 
WHERE "Timestamp"."usSince1970" between 1583708400000000 and 1584313200000000

-- explain
Unique  (cost=570694.82..571824.16 rows=92521 width=4) (actual time=8869.569..8930.545 rows=3695 loops=1)
  ->  Sort  (cost=570694.82..571259.49 rows=225867 width=4) (actual time=8869.568..8915.372 rows=260705 loops=1)
        Sort Key: "Changes"."idJob"
        Sort Method: external merge  Disk: 3552kB
        ->  Hash Join  (cost=4926.44..547518.97 rows=225867 width=4) (actual time=6325.494..8734.353 rows=260705 loops=1)
              Hash Cond: ("Changes"."idTimestamp" = "Timestamp".id)
              ->  Seq Scan on "Changes"  (cost=0.00..250722.43 rows=16238343 width=8) (actual time=0.004..2505.794 rows=16238343 loops=1)
              ->  Hash  (cost=3397.79..3397.79 rows=93172 width=4) (actual time=42.392..42.392 rows=107093 loops=1)
                    Buckets: 4096  Batches: 4  Memory Usage: 948kB
                    ->  Index Scan using "index_Timestamp_usSince1970" on "Timestamp"  (cost=0.00..3397.79 rows=93172 width=4) (actual time=0.006..20.831 rows=107093 loops=1)
                          Index Cond: (("usSince1970" >= 1583708400000000::bigint) AND ("usSince1970" <= 1584313200000000::bigint))
Total runtime: 8932.374 ms

Заранее спасибо.

Ответы [ 2 ]

2 голосов
/ 08 апреля 2020

Медленный запрос обрабатывает больше данных (100000 против 2500 строк из "Timestamp"), поэтому неудивительно, что он медленнее.

Вы можете заставить PostgreSQL использовать вложенную l oop присоединиться также к медленному запросу:

BEGIN;
SET LOCAL enable_hashjoin = off;
SET LOCAL enable_mergejoin = off;
SELECT ...;
COMMIT;

Попробуйте и посмотрите, правильно ли было PostgreSQL и действительно ли соединение ha sh медленнее.

Я подозреваю, что PostgreSQL делает все правильно, и лучший способ улучшить производительность - увеличить work_mem.

Если вы хотите добавить еще один индекс и к VACUUM "Changes" часто достаточно, вы могли бы получить еще лучшую производительность при сканировании только по индексу:

CREATE INDEX ON "Changes" ("idTimestamp") INCLUDE ("idJob");

В старых версиях PostgreSQL это было бы

CREATE INDEX ON "Changes" ("idTimestamp", "idJob");

Тогда лучше отбросить ненужное сейчас index "index_Changes_idTimestamp".

Кстати, вы излишне усложняете свою жизнь, используя верблюжий кейс и идентификаторы в кавычках.

0 голосов
/ 08 апреля 2020

Кстати: ваш запрос эквивалентен:


SELECT DISTINCT "idJob"
FROM "Changes" ch
WHERE EXISTS ( 
        SELECT * FROM "Timestamp"  ts
        WHERE ts.id = ch."idTimestamp" 
        AND ts."usSince1970" between 1584831600000000 and 1584745200000000 
        );
...