从源代码得知只有在drop database的过程中才会设置datconnlimit为-2
把drop database的主要过程整理为流程图方便大家理解,当然这不是完整的流程,只是把我认为关键的部分呈现出来。
我们都知道drop database是一个不可逆的操作[1] ,理论上要么不删除,要么删除成功。
在exec_simple_query中start_xact_command隐式开启事务,看到这你可能会质疑,不是说不可逆吗,怎么还会放到事务中?别着急,继续往下看。
由于drop database是一个Utility statement,因此不会生成执行计划,是直接调用到对应的逻辑里执行的。流程图中绿色部分从dropdb函数开始就是drop database语句的核心逻辑。
step1:
在dropdb函数里,首先做了一些ACLcheck及login校验后对pg_database加RowExclusiveLock
step2:
使用inplace_update去修改当前 drop database的datconnlimit这个过程稍微详细的描述下。
systable_inplace_update_begin里对我们要修改的这条记录使用heap_inplace_lock即调用Lock_buffer给当前buffer_content加LWlock LW_EXCLUSIVE模式锁,这样其他进程都无法访问这条内容。
然后执行修改datform->datconnlimit = !!#ff0000 DATCONNLIMIT_INVALID_DB!!;
改完之后systable_inplace_update_finish释放锁LWlock释放,这样修改就“生效”了。什么?生效了?不是在事务里?事务还没提交,怎么生效的?
大家应该注意到了这里是inplace update,而并不是PG默认的多版本标记更新,默认的标记更新只有等当前事务提交,才对外可见。为什么这么设计呢?
我们先串完整个过程,后续会有答案。
然后进行XLogFlush把WAL及时刷下去。
step3:
CatalogTupleDelete删除pg_database当前database这条记录,这里是标准的heap_delete,目前在事务里,得等到commit后才“生效”。
所以事务其实保护的就是这里,我们某个database可不可见主要取决于pg_database里对应的记录可不可见。那么这部分是可逆的,在这个步骤事务提交就这条记录清理,事务回滚这条记录依然可见。
step4:
ok,下来就到了不可逆的操作了,DropDatabaseBuffers丢弃这个database对应修改过的buffer内容,并且触发一次checkpoint。
remove_dbtablespaces调用unlink删除该database目录下物理文件。
处理完毕table_close释放pg_database加的锁。
step5:
finish_xact_command提交事务,操作完毕,这个时候要drop 的database在pg_database中记录就看不到了。
流程走读完毕,那么看起来就是drop database具体的流程中有部分可逆的步骤,有大部分不可逆的步骤。其实也好理解,当一个操作有一个不可逆的步骤,那么这个操做本身就是不可逆的,这些不可逆的其实也就是无法支持事务的。
再回到drop database datname这个语句本身,执行结果就只有两种,要么删除了,要么不删除,我们肯定不接受删除了一半,或者删除了部分数据这种中间状态,这种情况下database也是无法使用的。
所以在一开始执行drop database,就使用了inplace update将database修改为invalid禁止登录。即便你在drop database中间状态备份了数据,使用这个备份恢复数据,或者访问数据库都无法访问。似乎是保证了drop database的“原子性”。
所以回到这个case,不是什么BUG,是PITR选择的时间点不对,应该再往前,选用更早备份结合增量WAL重新恢复。当前恢复的时间点,这个database正在被删除。