1.前言
线上一位同事联系我,说给一张表添加字段卡住,alter table xxx add column xxx卡住了,在单机PostgreSQL中常见的情况就是被select等DML阻塞了,因为alter table是8级锁,access exclusive,后面发现不是那么简单。
2.分析

首先通过pg_stat_activity查看是被什么阻塞以及等待的事件:select pg_blocking_pids(pid) as blocked,pid,query,wait_event,wait_event_type from pg_stat_activity where query like '%alter%' and pid <> pg_backend_pid();

可以看到,被0号pid阻塞了,可能第一眼看到0号的pid有点懵,wait_event_type是Lock,Waiting to acquire a lock on a relation.
然后通过pg_locks查看具体等待的锁:
select locktype,relation::regclass as relname,page||','||tuple as ctid,virtualxid,transactionid as xid,virtualtransaction,pid,mode,granted from pg_locks where relation = 't1'::regclass;
select locktype,relation::regclass as relname,page||','||tuple as ctid,virtualxid,transactionid as xid,virtualtransaction,pid,mode,granted from pg_locks where pid = 3490;


一个进程在一个时间点只能等待至多一个锁,等待锁用granted=f表示,会休眠至其他锁被释放,或者系统检测到死锁。
可以看到,3490进程获取不到AccessExclusiveLock,于是又回到了前面所说,被“0”pid阻塞了,0号pid是个什么玩意?
0号pid在PostgreSQL内部对应的是2pc,prepare transaction预备事务。

prepared transaction是独立于会话,抗崩溃和状态维护的事务。事务的状态会持久化在磁盘上,这一点不难理解,因为两阶段提交中一般都涉及多台数据库之间的协同,各台数据库收到prepare transaction的命令后,如果要返回成功,那么该节点必须要确保自己后续能在被要求提交事务的时候去提交,或后续能在被要求回滚的时候回滚,所以PostgreSQL需要把相关信息持久化到存储上,随时响应。
既然持久化了,那么数据库服务器即使从崩溃中重新启动后也可以恢复事务,直到在对prepared transaction执行回滚或提交操作之前,将一直维护该事务。
在PostgreSQL中,使用prepared transaction创建一个2pc,注意需要将max_prepared_transaction设置为非0值,另外注意在standby环境下上,最好将其设置的比max_connections大一点,以免standby不能接收查询。

这样就创建好了一个预备事务,如上所说,会持久化至存储上,对应的目录在~/data/pg_twophase,可以用hexdump简单看一下












3.总结
