Удаление секции без эксклюзивной блокировки на родительскую таблицу
Актуально для СУБД 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 $$
DECLARE
n_from int = 1;
n_to int = 100000;
BEGIN
FOR i IN 1..100 loop
EXECUTE
'CREATE TABLE test_table_mnl_part' || i || ' PARTITION OF test_table_mnl_part
FOR 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.query
FROM pg_locks AS pl
LEFT JOIN pg_stat_activity AS psa ON pl.pid = psa.pid
ORDER 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 $$
DECLARE
n_from int = 1;
n_to int = 100000;
BEGIN
FOR i IN 1..100 loop
EXECUTE
'CREATE TABLE test_table_mnl_part' || i || ' PARTITION OF test_table_mnl_part
FOR 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 i
WHERE c.oid = i.inhrelid AND i.inhparent = 'test_table_mnl_part'::regclass
ORDER 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.query
FROM pg_locks AS pl
LEFT JOIN pg_stat_activity AS psa ON pl.pid = psa.pid
ORDER 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 ...будет зависеть от ранее открытых транзакций и ожидать их завершения. - При использовании автоматического секционирования создание новых секций проходит без проблем и задержек.