У нас в Postgres есть таблица объемом 2,2 ГБ с 7 801 611 строками. Мы добавляем к нему столбец uuid / guid, и мне интересно, как лучше заполнить этот столбец (поскольку мы хотим добавить NOT NULL
к нему ограничение).
Если я правильно понимаю Postgres, обновление - это технически удаление и вставка, так что это в основном перестраивает всю таблицу 2.2 ГБ. Также у нас работает раб, поэтому мы не хотим, чтобы это отставало.
Есть ли способ лучше, чем написать сценарий, который постепенно заполняет его?
postgresql
storage
ddl
Коллин Питерс
источник
источник
ALTER TABLE .. ADD COLUMN ...
или на эту часть тоже нужно ответить?Ответы:
Это очень зависит от деталей ваших требований.
Если у вас достаточно свободного места (не менее 110%
pg_size_pretty((pg_total_relation_size(tbl))
) на диске и вы можете позволить себе блокировку общего ресурса на некоторое время и эксклюзивную блокировку на очень короткое время , то создайте новую таблицу, включающую использованиеuuid
столбцаCREATE TABLE AS
. Зачем?В приведенном ниже коде используется функция из дополнительного
uuid-oss
модуля .Блокировка таблицы от одновременных изменений в
SHARE
режиме (все еще разрешая одновременные чтения). Попытки записи в таблицу будут ждать и в конечном итоге потерпят неудачу. Увидеть ниже.Скопируйте всю таблицу, заполняя новый столбец на лету - возможно, упорядочивая строки, находясь в нем.
Если вы собираетесь изменить порядок строк, убедитесь, что вы установили
work_mem
настолько высокий уровень, насколько можете себе позволить (только для вашего сеанса, а не в глобальном масштабе).Затем добавьте ограничения, внешние ключи, индексы, триггеры и т. Д. В новую таблицу. При обновлении больших частей таблицы намного быстрее создавать индексы с нуля, чем добавлять строки итеративно.
Когда новая таблица будет готова, отбросьте старую и переименуйте новую, чтобы сделать ее заменой. Только этот последний шаг получает эксклюзивную блокировку старой таблицы для остальной части транзакции, которая сейчас должна быть очень короткой.
Это также требует, чтобы вы удалили любой объект в зависимости от типа таблицы (представления, функции, использующие тип таблицы в подписи, ...) и затем воссоздали их.
Делайте все это за одну транзакцию, чтобы избежать незавершенных состояний.
Это должно быть быстрее всего. Любой другой способ обновления на месте должен также переписать всю таблицу, только более дорогим способом. Вы пошли бы по этому пути, только если у вас недостаточно свободного места на диске или вы не можете позволить себе заблокировать всю таблицу или генерировать ошибки для одновременных попыток записи.
Что происходит с одновременными записями?
Другая транзакция (в других сеансах), пытающаяся
INSERT
/UPDATE
/DELETE
в той же таблице после того, как ваша транзакция взялаSHARE
блокировку, будет ждать, пока блокировка не будет снята или не истечет время ожидания, в зависимости от того, что наступит раньше. Они потерпят неудачу в любом случае, так как таблица, в которую они пытались записать, была удалена из-под них.Новая таблица имеет новый OID таблицы, но в параллельной транзакции имя таблицы уже преобразовано в OID предыдущей таблицы . Когда блокировка наконец снята, они пытаются заблокировать таблицу самостоятельно, прежде чем писать в нее, и обнаруживают, что она исчезла. Постгрес ответит:
Где
123456
OID старой таблицы. Вам нужно перехватить это исключение и повторить запросы в коде приложения, чтобы избежать этого.Если вы не можете себе этого позволить, вы должны сохранить свой первоначальный стол.
Два варианта сохранения существующей таблицы
Обновите на месте (возможно, запустив обновление для небольших сегментов за раз), прежде чем добавить
NOT NULL
ограничение. Добавление нового столбца со значениями NULL и безNOT NULL
ограничений стоит дешево.Начиная с Postgres 9.2 вы также можете создать
CHECK
ограничение с помощьюNOT VALID
:Это позволяет обновлять строки peu à peu - в нескольких отдельных транзакциях . Это позволяет избежать слишком длительного блокирования строк и позволяет повторно использовать мертвые строки. (Вам придется запускать
VACUUM
вручную, если между автовакуумом не хватает времени.) Наконец, добавьтеNOT NULL
ограничение и удалитеNOT VALID CHECK
ограничение:Связанный ответ обсуждаем
NOT VALID
более подробно:Подготовьте новое состояние во временной таблице ,
TRUNCATE
исходное и пополните из временной таблицы. Все в одной транзакции . Вам все еще нужно взятьSHARE
блокировку перед подготовкой новой таблицы, чтобы предотвратить потерю одновременных записей.Подробности в этих связанных ответ на SO:
источник
LOCK
доDROP
. Я мог только произносить дикие и бесполезные догадки. Что касается 2., пожалуйста, рассмотрите дополнение к моему ответу.У меня нет «лучшего» ответа, но у меня есть «наименее плохой» ответ, который может позволить вам сделать все достаточно быстро.
Моя таблица имела 2-миллиметровые строки, и производительность обновления была неудовлетворительной, когда я попытался добавить дополнительный столбец отметки времени, который по умолчанию был первым.
После 40 минут ожидания я попробовал это на небольшой партии, чтобы понять, сколько времени это может занять - прогноз составлял около 8 часов.
Принятый ответ определенно лучше - но эта таблица интенсивно используется в моей базе данных. Есть несколько десятков столов, которые FKEY на него; Я хотел избежать переключения FOREIGN KEYS на очень многих столах. И тогда есть взгляды.
Немного поиска документов, тематических исследований и StackOverflow, и у меня было "A-Ha!" момент. Утечка была не в основном UPDATE, а во всех операциях INDEX. В моей таблице было 12 индексов - несколько для уникальных ограничений, несколько для ускорения планировщика запросов и несколько для полнотекстового поиска.
Каждая строка, которая была ОБНОВЛЕНА, не только работала над DELETE / INSERT, но также и накладными расходами на изменение каждого индекса и проверку ограничений.
Мое решение состояло в том, чтобы удалить все индексы и ограничения, обновить таблицу, а затем добавить все индексы / ограничения обратно.
Потребовалось около 3 минут, чтобы написать транзакцию SQL, которая сделала следующее:
Выполнение сценария заняло 7 минут.
Принятый ответ определенно лучше и правильнее ... и практически исключает необходимость простоев. В моем случае, однако, потребовалось бы значительно больше работы «Разработчика», чтобы использовать это решение, и у нас было 30-минутное окно запланированного простоя, в котором оно могло бы быть достигнуто. Наше решение решило эту проблему в 10.
источник