Удаление секции без эксклюзивной блокировки на родительскую таблицу
Актуально для СУБД Pangolin версии 6.1.0 и выше в связи с добавлением доработки ALTER TABLE ... DETACH PARTITION ... CONCURRENTLY ядра PostgreSQL 14 версии.
Раздел содержит способ инициализации отсоединения секции без потери доступа к самой таблице с помощью конструкции DETACH PARTITION ... CONCURRENTLY.
Ручное секционирование
Тест 1. Выполнение команды SELECT при отсоединении секции
-
Создайте таблицы, секции, данные:
DROP TABLE IF EXISTS test_TABLE_mnl_part;CREATE TABLE test_TABLE_mnl_part(id bigint,someint bigint,txt text,isThat bool) PARTITION BY RANGE (someint);do $$DECLAREn_from int = 1;n_to int = 100000;BEGINFOR i IN 1..100 loopEXECUTE'CREATE TABLE test_table_mnl_part' || i || ' PARTITION OF test_table_mnl_partFOR VALUES FROM (' || n_from || ') TO (' || n_to || ');';n_from = n_to;n_to = n_from + 100000;END LOOP;END;$$;INSERT INTO test_table_mnl_part(id, someint, txt, isThat)SELECT x, x, md5((random() * 10)::text), false FROM generate_series(1,9999999) AS x; -
Подготовьте три терминала (
t1,t2,t3), в каждом из которых будут выполняться команды из тестов.-
Терминал
t1. Открытие транзакции и обращение к таблице:BEGIN;SELECT count(*) FROM test_table_mnl_part; -
Терминал
t2. Отсоединение секции в режиме concurrently:ALTER TABLE test_table_mnl_part DETACH PARTITION test_table_mnl_part10 concurrently;Заметьте, что при выполнении команды
ALTER TABLEпроизошло зависание, транзакция не завершится, покаt1не будет завершена («закомичена»). Тем не менее, в таблице блокировок можно убедиться, что блокировок на отношение (relation) нет, но есть наvirtualxid.А сама секция отмечена как
DETACH PENDING(\d+ test_table_mnl_part):...test_table_mnl_part10 FOR VALUES FROM ('900000') TO ('1000000') (DETACH PENDING),...*/ -
Терминал
t3:Проверка блокировок с помощью таблицы
pg_locks:SELECT pl.locktype, pl.relation, pl.virtualxid, pl.transactionid, pl.pid, pl.mode, psa.queryFROM pg_locks AS plLEFT JOIN pg_stat_activity AS psa ON pl.pid = psa.pidORDER BY psa.query;/*locktype | relation | virtualxid | transactionid | pid | mode | query------------+----------+------------+---------------+---------+-----------------+-----------------------------------------------------------------------------------------------virtualxid | | 16/18 | | 3149851 | ShareLock | ALTER TABLE test_table_mnl_part DETACH PARTITION test_table_mnl_part10 concurrently;virtualxid | | 14/19 | | 3149851 | ExclusiveLock | ALTER TABLE test_table_mnl_part DETACH PARTITION test_table_mnl_part10 concurrently;relation | 19340 | | | 3149849 | AccessShareLock | SELECT count(*) FROM test_table_mnl_part;relation | 19335 | | | 3149849 | AccessShareLock | SELECT count(*) FROM test_table_mnl_part;...*/Запрос количества строк у таблицы:
SELECT count(*) FROM test_table_mnl_part;/*count---------9899999(1 row)*/
-
-
Ознакомьтесь с результатом выполнения команд:
Несмотря на то, что секция
test_table_mnl_part10до сих пор числится в составе таблицыtest_table_mnl_part, она помечается какDETACH PENDINGи не учитывается в запросеSELECTв сессии №3.
Если по каким-то причинам команда ALTER TABLE ... DETACH PARTITION ... CONCURRENTLY была завершена некорректно или вовсе отменена, секция так и останется в состоянии DETACH PENDING. Для завершения операции по отсоединению необходимо будет выполнить команду ALTER TABLE ... DETACH PARTITION ... FINALIZE.
-
Заключительные действия произведите в терминале
t1. Завершите транзакции и удалите созданные ранее тестовые таблицы:END;DROP TABLE IF EXISTS test_table_mnl_part;DROP TABLE IF EXISTS test_table_mnl_part10;
Тест 2. Присоединение новой секции при отсоединении старой
-
Создайте таблицы, секции, данные:
DROP TABLE IF EXISTS test_table_mnl_part;CREATE TABLE test_table_mnl_part(id bigint,someint bigint,txt text,isThat bool) PARTITION BY RANGE (someint);do $$DECLAREn_from int = 1;n_to int = 100000;BEGINFOR i IN 1..100 loopEXECUTE'CREATE TABLE test_table_mnl_part' || i || ' PARTITION OF test_table_mnl_partFOR VALUES FROM (' || n_from || ') TO (' || n_to || ');';n_from = n_to;n_to = n_from + 100000;END LOOP;END;$$;INSERT INTO test_table_mnl_part(id, someint, txt, isThat)SELECT x, x, md5((random() * 10)::text), false FROM generate_series(1,9999999) AS x; -
Подготовьте три терминала (
t1,t2,t3), в каждом из которых будут выполняться команды из тестов.-
Терминал
t1. Открытие транзакции и обращение к таблице:BEGIN;SELECT count(*) FROM test_table_mnl_part; -
Терминал
t2. Отсоединение секции в режиме concurrently:ALTER TABLE test_table_mnl_part DETACH PARTITION test_table_mnl_part10 concurrently; -
Терминал
t3. Создание новой секции, наполнение данными и присоединение:DROP TABLE IF EXISTS test_table_mnl_part_n1000;CREATE TABLE test_table_mnl_part_n1000(id bigint,someint bigint,txt text,isThat bool);INSERT INTO test_table_mnl_part_n1000(id, someint, txt, isThat)SELECT x, x, md5((random() * 10)::text), false FROM generate_series(10000010,10000090) AS x;ALTER TABLE test_table_mnl_part ATTACH PARTITION test_table_mnl_part_n1000 FOR VALUES FROM ('10000000') TO ('10100000');
-
-
Ознакомьтесь с результатом выполнения команд:
Команда по присоединению новой секции выполнена успешно, секция вставлена несмотря на то, что
t1иt2не завершены. Для того, чтобы убедиться, что новая секция есть в таблице, можно выполнить следущий запрос:SELECT c.oid::pg_catalog.regclass, c.relkind, inhdetachpending, pg_catalog.pg_get_expr(c.relpartbound, c.oid)FROM pg_catalog.pg_class c, pg_catalog.pg_inherits iWHERE c.oid = i.inhrelid AND i.inhparent = 'test_table_mnl_part'::regclassORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT', c.oid::pg_catalog.regclass::pg_catalog.text;
Создание таблицы, как секции, сразу невозможно, пока t1 не будет завершена. В такой конструкции произойдет зависание:
CREATE TABLE test_table_mnl_part_n1000 PARTITION OF test_table_mnl_part FOR VALUES FROM ('10000000') TO ('10100000');
-
Произведите заключительные действия в терминале
t1. Завершите транзакции и удалите созданные ранее тестовые таблицы:END;DROP TABLE IF EXISTS test_table_mnl_part;DROP TABLE IF EXISTS test_table_mnl_part10;DROP TABLE IF EXISTS test_table_mnl_part_n1000;
Автоматическое секционирование
-
Создайте секционированную таблицу через генерацию данных и секций:
DROP TABLE IF EXISTS test_table_au_part;CREATE TABLE test_table_au_part(id bigint,someint bigint,txt text,isThat bool) auto PARTITION by range (someint) period (100000::bigint);INSERT INTO test_table_au_part(id, someint, txt, isThat)SELECT x, x, md5((random() * 10)::text), false FROM generate_series(1,9999999) AS x; -
Подготовить три терминала (
t1,t2,t3), в каждом из которых будут выполняться команды из тестов.-
Терминал
t1. Открытие транзакции и обращение к таблице:BEGIN;SELECT count(*) FROM test_TABLE_au_part; -
Терминал
t2. Отсоединение секции:ALTER TABLE test_table_au_part DETACH PARTITION test_table_au_part_p9 concurrently; -
Терминал
t3. Выполнение командыINSERT, которая должна сгенерировать новую секцию:INSERT INTO test_table_au_part(id, someint, txt, isThat)SELECT x, x, md5((random() * 10)::text), false FROM generate_series(10000010,10000090) AS x;
-
-
Ознакомьтесь с результатом выполнения команд:
Несмотря на то, что операция из терминала
t2не может быть до конца выполнена, пока открыта транзакция на терминалеt1, операция из третьего терминалаt3выполнилась успешно. Была сгенерирована новая секция, и в нее были добавлены новые строки.Существует возможность обращения к таблице
pg_locks, чтобы удостовериться, что блокировка установлена на виртуальный идентификатор (virtualxid), а не на таблицу (relation):SELECT pl.locktype, pl.relation, pl.virtualxid, pl.transactionid, pl.pid, pl.mode, psa.queryFROM pg_locks AS plLEFT JOIN pg_stat_activity AS psa ON pl.pid = psa.pidORDER BY psa.query;/*locktype |relation|virtualxid|transactionid|pid |mode |query |----------+--------+----------+-------------+-------+---------------+- ---------------------------------------------------------------------- -------------+virtualxid| |14/16 | |3149851|ExclusiveLock |ALTER TABLE test_table_au_part DETACH PARTITION test_table_au_part_p9 concurrently; |virtualxid| |16/10 | |3149851|ShareLock |ALTER TABLE test_table_au_part DETACH PARTITION test_table_au_part_p9 concurrently; |relation | 18721| | |3149849|AccessShareLock|SELECT count(*) FROM test_table_au_part; |relation | 18716| | |3149849|AccessShareLock|SELECT count(*) FROM test_table_au_part; |...*/ -
Произведите заключительные действия в терминале
t1. Завершите транзакции и удалите созданные ранее тестовые таблицы:END;DROP TABLE IF EXISTS test_table_au_part;DROP TABLE IF EXISTS test_table_au_part_p9;
Итоги тестирования
- При выполнении
DETACH CONCURRENTLYоперации на таблице не блокируются. - Если операция
DETACH CONCURRENTLYзавершена неудачно, потребуется повторить операцию с ключомfinalize. - Для создания новой секции нужно использовать конструкцию
CREATE TABLE -> ALTER TABLE ... ATTACH, конструкцияCREATE TABLE ... PARTITION OF ...будет зависеть от ранее открытых транзакций и ожидать их завершения. - При использовании автоматического секционирования создание новых секций проходит без проблем и задержек.