Удаление секции без эксклюзивной блокировки на родительскую таблицу
Актуально для СУБД 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 ...
будет зависеть от ранее открытых транзакций и ожидать их завершения. - При использовании автоматического секционирования создание новых секций проходит без проблем и задержек.