Как атомарно заменить данные таблицы в PostgreSQL

14

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

Вариант использования - иметь таблицу, в которой хранится информация о почтовом ящике, которая регулярно извлекается и должна храниться в таблице PostgreSQL. Многие клиенты используют приложение, которое постоянно запрашивает ту же таблицу.

Обычно я бы сделал что-то вроде (входящий псевдокод) ...

BEGIN TRANSACTION
TRUNCATE TABLE
INSERT INTO
COMMIT

Но, к сожалению, таблица не может быть прочитана во время этого процесса; из-за времени, необходимого INSERT INTOдля завершения. Стол заблокирован.

В MySQL я бы использовал их атомарную RENAME TABLEкоманду, чтобы избежать этих проблем ...

CREATE TABLE table_new LIKE table; 
INSERT INTO table_new;
RENAME TABLE table TO table_old, table_new TO table; *atomic operation*
DROP TABLE table_old;

Как я могу добиться этого в PostgreSQL?

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

Clarkey
источник
Почему вы думаете, что таблица не может быть прочитана при вставке в нее строк? Усеченная таблица будет иметь немедленный эффект во всех сеансах; однако вставки (если они выполняются внутри транзакции, которая их оборачивает, как подсказывает ваш псевдокод) не будут видны другим сеансам, пока вы не подтвердите их. Другие сеансы смогут выбирать из таблицы и будут видеть пустую таблицу, пока вы не подтвердите.
zgguy
2
@zgguy TRUNCATEкоманда получит блокировку AccessExclusive для таблицы, поэтому никто другой не сможет читать из таблицы, пока эта транзакция не будет зафиксирована или откатана.
Джош Купершмидт
2
Если вы используете deleteвместо truncateэтого будет медленнее, но без блокировки читателей. Сколько строк нужно удалить?
a_horse_with_no_name
@a_horse_with_no_name Обычно между 200-300 тысячами строк и множеством столбцов varchar. Время ожидания DELETEи INSERTбудет слишком долго.
Кларки

Ответы:

20

Правильно, TRUNCATE TABLE команда вы выполняете «... получает блокировку ACCESS EXCLUSIVE на каждом столе она работает на », так что в первом блоке SQL вы публикуемую любые другие клиенты , пытающиеся получить доступ к таблице после этого времени не будет заблокирован , пока ваш INSERTотделок и ты COMMIT.

Вы можете использовать тот же обходной путь, что и в вашем специфичном для MySQL коде; Postgres поддерживает примерно такой же синтаксис и будет иметь похожее поведение блокировки. Для остроумия:

BEGIN;
-- You probably want to make sure that no one else is
-- INSERT / UPDATE / DELETE'ing from the original table, otherwise
-- those changes may be lost during this switchover process. One way
-- to do that would be via:
-- LOCK TABLE "table" IN SHARE ROW EXCLUSIVE mode;
CREATE TABLE "table_new" (LIKE "table");
INSERT INTO "table_new" ...;

-- The ALTER TABLE ... RENAME TO command takes an Access Exclusive lock on "table",
-- but these final few statements should be fast.
ALTER TABLE "table" RENAME TO "table_old";
ALTER TABLE "table_new" RENAME TO "table";
DROP TABLE "table_old";

COMMIT;

Дополнительный бонус: Postgres на самом деле поддерживает транзакционный DDL, в отличие от MySQL, поэтому в случае, если вам нужно откатить вышеупомянутую транзакцию, вы можете смело делать это.

Джош Купершмидт
источник
Я собираюсь провести некоторое тестирование по этому вопросу, спасибо за ваш ответ. Если бы я использовал LOCK TABLEпредложенный вами метод, нужно ли мне снова COMMITего разблокировать, или он сам разблокируется?
Кларки
1
РЕДАКТИРОВАТЬ: нашел в этой документации следующую оценку : «Нет команды UNLOCK TABLE; блокировки всегда снимаются в конце транзакции».
Кларки
2
Единственное, чего здесь не хватает, так это всех приложенных ограничений, которые все еще принадлежат_old
Intellix
@Intellix, можешь ли ты уточнить это? Означает ли это , что ограничения просто названы в старой таблице , или что они только относятся к старой таблице ( это означают , что ограничения эффективно опускают)?
Maerics
Комментарий перед созданием таблицы ( -- LOCK TABLE "table" IN ROW EXCLUSIVE mode;) кажется недостаточным для защиты от обновления / вставки в исходную таблицу в соответствии со спецификациями. Две ROW EXCLUSIVEблокировки могут быть получены без какого-либо конфликта (см. Таблицу 13.2 в postgresql.org/docs/10/explicit-locking.html#LOCKING-TABLES ). Для предотвращения обновления данных вам нужна как минимум SHAREблокировка.
Пилу