Лучший способ удалить миллионы строк по идентификатору - PullRequest
60 голосов
/ 28 ноября 2011

Мне нужно удалить около 2 миллионов строк из моей базы данных PG.У меня есть список идентификаторов, которые мне нужно удалить.Однако, любой способ, которым я пытаюсь сделать это, занимает дни.

Я попытался поместить их в таблицу и сделать это партиями по 100 штук. Через 4 дня это все еще выполняется с удалением только 297268 строк.(Мне пришлось выбрать 100 идентификаторов из таблицы идентификаторов, удалить, где В этом списке, удалить из таблицы идентификаторов 100 я выбрал).

Я пытался:

DELETE FROM tbl WHERE id IN (select * from ids)

Это займет вечность,тоже.Трудно определить, как долго, так как я не могу видеть его прогресс до выполнения, но запрос все еще выполнялся через 2 дня.

Просто отчасти ищу наиболее эффективный способ удаления из таблицы, когда я знаюконкретные идентификаторы для удаления, и существуют миллионы идентификаторов.

Ответы [ 7 ]

78 голосов
/ 28 ноября 2011

Все зависит ...

  • Удалить все индексы (кроме того, на идентификаторе которого вам нужно удалить)
    Воссоздать их потом (= намного быстрее, чем инкрементный)обновления индексов)

  • Проверьте, есть ли у вас триггеры, которые можно безопасно удалить / временно отключить

  • Имеют ли внешние ключи ссылку на вашу таблицу?Могут ли они быть удалены?Временно удален?

  • В зависимости от ваших настроек автоочистки может помочь запустить VACUUM ANALYZE перед операцией.

  • Предполагая, нет одновременного доступа на запись к задействованным таблицам, или вам может потребоваться заблокировать таблицы исключительно, или этот маршрут может вообще не быть для вас.

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

  • Если вы удаляете большие части таблицы, а остальная часть помещается в ОЗУ, самый быстрый и простой способ будет следующим:

SET temp_buffers = '1000MB'; -- or whatever you can spare temporarily

CREATE TEMP TABLE tmp AS
SELECT t.*
FROM   tbl t
LEFT   JOIN del_list d USING (id)
WHERE  d.id IS NULL;      -- copy surviving rows into temporary table

TRUNCATE tbl;             -- empty table - truncate is very fast for big tables

INSERT INTO tbl
SELECT * FROM tmp;        -- insert back surviving rows.

Таким образом, вам не нужно пересматривать представления, внешние ключи или другие зависимые объекты.Прочтите о настройке temp_buffers в руководстве .Этот метод быстр, пока таблица помещается в память или, по крайней мере, большую ее часть.Имейте в виду, что вы можете потерять данные, если ваш сервер выйдет из строя в середине этой операции.Вы можете заключить все это в транзакцию, чтобы сделать ее более безопасной.

Запустите ANALYZE впоследствии.Или VACUUM ANALYZE, если вы не пошли по усеченному маршруту, или VACUUM FULL ANALYZE, если вы хотите приблизить его к минимальному размеру.Для больших таблиц рассмотрим альтернативы CLUSTER / pg_repack:

Для небольших таблиц простой DELETE вместо TRUNCATE часто быстрее:

DELETE FROM tbl t
USING  del_list d
WHERE  t.id = d.id;

Чтение Примечания раздел для TRUNCATE вруководство .В частности (как Педро также указал в своем комментарии ):

TRUNCATE нельзя использовать для таблицы, имеющей ссылки на внешние ключи из других таблиц, если только все такиетаблицы также усекаются в одной команде.[...]

И:

TRUNCATE не будет запускать триггеры ON DELETE, которые могут существовать для таблиц.

4 голосов
/ 28 ноября 2011

Мы знаем, что производительность обновления / удаления PostgreSQL не такая мощная, как у Oracle.Когда нам нужно удалить миллионы или десятки миллионов строк, это действительно сложно и занимает много времени.

Тем не менее, мы все еще можем сделать это в производстве базы данных.Моя идея заключается в следующем:

Сначала мы должны создать таблицу журнала с 2 столбцами - id & flag (id относится к идентификатору, который вы хотите удалить; flag может быть Y или null, где Y означает, что запись успешно удалена).

Позже мы создадим функцию.Мы выполняем задачу удаления каждые 10000 строк.Вы можете увидеть более подробную информацию на мой блог .Хотя это на китайском языке, вы все равно можете получить нужную информацию из кода SQL.

Убедитесь, что столбец id обеих таблиц является индексами, поскольку он будет работать быстрее.

2 голосов
/ 28 ноября 2011

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

100 одновременно кажется слишком маленьким. Попробуйте 1000 или 10000.

Нет необходимости удалять что-либо из таблицы идентификаторов удаления. Добавьте новый столбец для номера партии и заполните его 1000 для партии 1, 1000 для партии 2 и т. Д. И убедитесь, что запрос на удаление содержит номер партии.

2 голосов
/ 28 ноября 2011

Два возможных ответа:

  1. Ваша таблица может иметь множество ограничений или триггеров, прикрепленных к ней, когда вы пытаетесь удалить запись. Это потребует много циклов процессора и проверки из других таблиц.

  2. Возможно, вам понадобится поместить это утверждение в транзакцию.

2 голосов
/ 28 ноября 2011

Вы можете попробовать скопировать все данные в таблице , за исключением идентификаторов, которые вы хотите удалить, в новую таблицу, затем переименовать и поменять местами таблицы (если у вас достаточно ресурсов для этого).

Это не совет специалиста.

1 голос
/ 28 ноября 2011

Самый простой способ сделать это - сбросить все ограничения и затем удалить.

0 голосов
/ 10 ноября 2017

Если на таблицу, из которой вы удаляете, ссылается some_other_table (и вы не хотите удалять внешние ключи даже временно), убедитесь, что у вас есть индекс для столбца , ссылающегося на вsome_other_table!

У меня была похожая проблема, и я использовал auto_explain с auto_explain.log_nested_statements = true, что показало, что delete фактически выполняет seq_scans на some_other_table:

    Query Text: SELECT 1 FROM ONLY "public"."some_other_table" x WHERE $1 OPERATOR(pg_catalog.=) "id" FOR KEY SHARE OF x    
    LockRows  (cost=[...])  
      ->  Seq Scan on some_other_table x  (cost=[...])  
            Filter: ($1 = id)

По-видимому, он пытается заблокировать ссылочные строки в другой таблице (которая не должна существовать, иначе удаление не удастся).После того, как я создал индексы для ссылочных таблиц, удаление было на несколько порядков быстрее.

...