Удаление строк со столбцом, который ссылается на одну и ту же таблицу, занимает много времени - PullRequest
2 голосов
/ 26 мая 2009

Извините за довольно конкретный вопрос.

У меня есть таблица (см. Внизу), и когда я пытаюсь удалить из нее много записей, мой PostgreSQL 8.2.5 тратит 98% времени на выполнение родительских и дочерних ограничений. Я пытаюсь выяснить, какой индекс мне добавить, чтобы он шел быстро. Я должен сказать, что все столбцы в этой таблице имеют либо 0, либо ноль как parent_block_id: это элементарно.

Я пытался добавить разные индексы: just (parent_block_id); WHERE parent_block_id = 0; WHERE parent_block_id равен NULL; WHERE parent_block_id! = 0. Ни один из них не принес серьезного выигрыша в производительности.

varshavka=> explain analyze delete from infoblocks where template_id = 112;
                                                 QUERY PLAN
-------------------------------------------------------------------------------------------------------------
 Seq Scan on infoblocks  (cost=0.00..1234.29 rows=9 width=6) (actual time=13.271..40.888 rows=40000 loops=1)
   Filter: (template_id = 112)
 Trigger for constraint $1: time=4051.219 calls=40000
 Trigger for constraint $2: time=1616.194 calls=40000
 Trigger for constraint cs_ibrs: time=2810.144 calls=40000
 Trigger for constraint cs_ibct: time=4026.305 calls=40000
 Trigger for constraint cs_ibbs: time=3517.640 calls=40000
 Trigger for constraint cs_ibreq: time=774344.010 calls=40000
 Total runtime: 790760.168 ms
(9 rows)



varshavka=> \d infoblocks
                                      Table "public.infoblocks"
     Column      |            Type             |                      Modifiers
-----------------+-----------------------------+------------------------------------------------------
 id              | integer                     | not null default nextval(('IB_SEQ'::text)::regclass)
 parent_block_id | integer                     |
 nm_id           | integer                     | default 0
 template_id     | integer                     | not null
 author_id       | integer                     |
 birthdate       | timestamp without time zone | not null
Indexes:
    "infoblocks_pkey" PRIMARY KEY, btree (id)
    "zeroparent" btree (parent_block_id) WHERE parent_block_id <> 0
Foreign-key constraints:
    "$2" FOREIGN KEY (nm_id) REFERENCES newsmakers(nm_id) ON DELETE RESTRICT
    "$5" FOREIGN KEY (author_id) REFERENCES users(user_id) ON DELETE RESTRICT
    "cs_ibreq" FOREIGN KEY (parent_block_id) REFERENCES infoblocks(id) ON DELETE CASCADE

Ответы [ 3 ]

2 голосов
/ 26 мая 2009

Если вы можете справиться с блокировкой всех остальных на некоторое время, возможно, отмените ограничение cs_ibreq, удалите и снова добавьте ограничение?

Возможно, потому что есть только одно ненулевое значение для parent_block_id, он не использует индекс при проверке ограничения? Хотя это кажется немного странным.

2 голосов
/ 26 мая 2009

Прежде всего: первое (ноль!), Что вы должны сделать, заметив некрасивое время запроса, - убедиться, что у вас недавно было VACUUM ANALYZE d.

Если вам просто нужно одноразовое удаление, см. ответ araqnid . Но если вам нужно что-то, что продолжит работать в будущем, когда некоторые строки имеют ненулевое, ненулевое поле parent_block_id, читайте дальше.

Я предполагаю, что PostgreSQL не объединяет удаления, вызванные ON DELETE CASCADE, в один запрос - тот факт, что вывод EXPLAIN показывает их как триггеры, предполагает, что удаление каждой дочерней строки фактически будет выполняться отдельно , Предположительно, каждая строка будет найдена с помощью индексированного поиска на parent_block_id, но это все равно будет намного медленнее, чем один проход по таблице.

Таким образом, вы, вероятно, могли бы добиться большого ускорения, изменив ON DELETE CASCADE на ON DELETE RESTRICT и вручную составив список всех удалений, которые необходимо выполнить во временной таблице, а затем удалив их все сразу. Этот подход будет очень быстрым, если максимальная глубина вашей иерархии мала. Вот некоторый псевдокод:

# Insert the top-level rows as "seed" rows.
INSERT INTO rows_to_delete
    SELECT id, 0 FROM infoblocks WHERE template_id = 112

# Gather all rows that are children of any row at depth curLevel,
# advancing curLevel until no more children are found.
curLevel = 0
while (nRowsReturnedFromLastInsert > 0) {
    INSERT INTO rows_to_delete
        SELECT ib.id, rtd.level + 1
        FROM infoblocks ib
        JOIN rows_to_delete rtd ON (ib.parent_block_id = rtd.id)
        WHERE rtd.level = curLevel

    curLevel = curLevel + 1
}

DELETE FROM infoblocks
    JOIN rows_to_delete rtd ON (infoblocks.id = rtd.id)

(я не уверен, но на самом деле вам может понадобиться использовать ON DELETE NO ACTION вместо ON DELETE RESTRICT для того, чтобы окончательный DELETE был успешным - мне не ясно, допустимо ли одно DELETE утверждение удалить родителя и всех его потомков, когда действует ON DELETE RESTRICT. Если по какой-то причине это неприемлемо, вы всегда можете просмотреть несколько операторов DELETE, сначала удалив самый нижний уровень, затем следующий-самый нижний и т. д.)

2 голосов
/ 26 мая 2009

Вы пытались добавить индекс к template_id?

...