PostgreSQL15中DROP TABLESPACE引发ProcSignalBarrier死锁问题咨询
问题描述
执行了以下操作:
- 创建表空间:
create tablespace test_tablespace location '/path/to/tablespace/dir';
- 创建表并插入数据:
create table test_table (id numeric, file_name varchar) tablespace test_tablespace; insert into test_table (id, file_name) values (1,'test_field1'),(2,'test_field2'),(3,'test_field3');
- 创建使用dblink的函数来删除表和表空间并执行:
CREATE OR REPLACE FUNCTION test_func(par_tablespace_name varchar) RETURNS void LANGUAGE 'plpgsql' AS $BODY$ begin perform dblink_connect_u('port=5432 user=postgres dbname=test_db'); perform dblink_exec('drop table test_table'); perform dblink_exec('drop tablespace '|| par_tablespace_name ); perform dblink_disconnect(); end; $BODY$; select test_func('test_tablespace');
执行后触发死锁,相关信息如下:
pg_stat_activity 信息
| pid | wait_event_type | wait_event | query |
|---|---|---|---|
| 609024 | null | null | select test_func('test_tablespace'); |
| 610910 | IPC | ProcSignalBarrier | drop tablespace test_tablespace |
pg_blocking_pids(610910)结果为null。
PostgreSQL 日志
[610910] STATEMENT: drop tablespace test_tablespace [610910] LOG: still waiting for backend with PID 609024 to accept ProcSignalBarrier
该操作在PostgreSQL13中可正常执行,且当表空间为空(无数据文件)时也无问题。
现咨询:该死锁产生的原因是什么?如何通过其他会话的函数删除表空间?
死锁原因分析
- 执行
DROP TABLESPACE时,PostgreSQL需要通知所有曾经访问过该表空间的后端进程(包括创建过该表空间内表的会话),通过ProcSignalBarrier机制同步状态,确保这些会话不再持有该表空间的相关资源。 - 主会话(PID 609024)正是创建
test_table的会话,属于需要被通知的对象,但此时主会话正处于等待dblink会话(PID 610910)完成DROP TABLESPACE的状态,无法响应信号。 - dblink会话则一直在等待主会话响应信号,形成循环等待,最终触发死锁。
- 空表空间时,没有关联的使用记录,无需通知其他会话;PostgreSQL13对
DROP TABLESPACE的信号处理逻辑与当前版本不同,因此未触发该问题。
解决方法
要通过函数删除表空间,核心是打破循环等待的条件,让执行DROP TABLESPACE的会话无需等待当前调用函数的主会话,或提前释放主会话的关联资源:
1. 拆分dblink连接,断开主会话关联
将删除表和删除表空间的操作拆分为两次独立的dblink连接,让主会话在执行DROP TABLESPACE前释放与表空间的关联:
CREATE OR REPLACE FUNCTION test_func(par_tablespace_name varchar) RETURNS void LANGUAGE 'plpgsql' AS $BODY$ begin -- 第一次连接:删除表空间内的表 perform dblink_connect_u('port=5432 user=postgres dbname=test_db'); perform dblink_exec('drop table test_table'); perform dblink_disconnect(); -- 短暂等待,确保资源完全释放 perform pg_sleep(1); -- 第二次连接:删除表空间(此时主会话无表空间关联资源) perform dblink_connect_u('port=5432 user=postgres dbname=test_db'); perform dblink_exec('drop tablespace '|| par_tablespace_name ); perform dblink_disconnect(); end; $BODY$;
2. 提前结束主会话事务
在调用删除函数前,先执行COMMIT结束当前主会话的事务,确保主会话不再持有任何与目标表空间相关的资源,再执行select test_func('test_tablespace');。
3. 使用全新会话执行删除操作
直接在未访问过该表空间的全新会话中执行删除函数,或单独执行DROP TABLESPACE(前提是已删除表空间内所有对象)。
内容的提问来源于stack exchange,提问作者MegaNeiv
相关产品推荐
相关产品推荐

