standard_ProcessUtility()函数中CheckPoint语句执行过程
概述
在 PostgreSQL 中,T_CheckPointStmt
标签与执行检查点(checkpoint)操作相关。 CHECKPOINT
命令会强制数据库进行一次检查点操作,这个操作会更新所有数据文件,以确保事务日志中的信息被刷新到磁盘上,保证数据的持久性。
T_CheckPointStmt标签执行过程:
- T_CheckPointStmt标签的代码实现:
case T_CheckPointStmt:
if (!has_privs_of_role(GetUserId(), ROLE_PG_CHECKPOINT))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser or have privileges of pg_checkpoint to do CHECKPOINT")));
RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT |
(RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
break;
- CheckPoint语句的执行过程:
- 开始:执行standard_ProcessUtility()函数。
- 接收到T_CheckPointStmt:表示代码接收到了一个
T_CheckPointStmt
语句,即用户尝试执行一个检查点操作。 - 检查用户是否有权限:代码会检查当前用户是否有执行检查点操作的权限。这通常是通过调用
has_privs_of_role
函数来完成的,该函数检查用户是否拥有ROLE_PG_CHECKPOINT
角色的权限。 - 有权限:如果用户有权限,代码将执行到下一步。
- 执行强制检查点:如果用户有权限,
RequestCheckpoint
函数被调用,执行一个强制检查点操作。这里使用了CHECKPOINT_IMMEDIATE
表示立即执行检查点,CHECKPOINT_WAIT
表示等待检查点完成,以及根据数据库是否正在恢复来决定是否使用CHECKPOINT_FORCE
。 - 返回错误信息:如果用户没有权限,代码将返回一个错误信息,指出用户必须是超级用户或拥有
pg_checkpoint
的权限才能执行检查点。 - 结束:结束standard_ProcessUtility()函数。
has_privs_of_role()函数的执行过程:
- 直接检查两个传入参数是否相等,如果相等就表示当前user拥有执行checkpoint权限。
/* Fast path for simple case */
if (member == role)
return true;
- 调用superuser_arg()函数检查当前用户的权限。
/* Superusers have every privilege, so are part of every role */
if (superuser_arg(member))
return true;
superuser_arg()函数的执行过程:
这个函数通过查询 PostgreSQL 的系统表 pg_authid 来检查一个角色是否是超级用户,并且使用了缓存来提高性能。它还处理了一些特殊情况,如数据库初始化过程中的超级用户检查。
- 局部变量声明:
bool result;
HeapTuple rtup;
声明了一个布尔变量 result
来存储结果,以及一个 HeapTuple
类型的变量 rtup
来存储系统缓存中的元组。
- 缓存命中快速返回:
if (OidIsValid(last_roleid) && last_roleid == roleid)
return last_roleid_is_super;
如果上次查询的角色 ID (last_roleid
) 有效,并且与当前的角色 ID 相同,函数将直接返回上次查询的结果 (last_roleid_is_super
)。
- 特殊情况处理:
if (!IsUnderPostmaster && roleid == BOOTSTRAP_SUPERUSERID)
return true;
如果当前不在 Postmaster 进程下运行(例如,在初始化数据库或恢复过程中),并且角色 ID 是引导超级用户 ID (BOOTSTRAP_SUPERUSERID
),则直接返回 true
。
- 查询系统缓存:
rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
使用系统缓存查询函数 SearchSysCache1
来查找 pg_authid
系统表中对应 roleid
的元组。
- 检查元组有效性并获取结果:
if (HeapTupleIsValid(rtup))
{
result = ((Form_pg_authid) GETSTRUCT(rtup))->rolsuper;
ReleaseSysCache(rtup);
}
else
{
result = false;
}
如果找到的元组有效,从元组结构体中获取 rolsuper
字段的值,并将其存储在 result
中。然后释放系统缓存中的元组。如果元组无效,设置 result
为 false
。
- 注册缓存回调:
if (!roleid_callback_registered)
{
CacheRegisterSyscacheCallback(AUTHOID,
RoleidCallback,
(Datum) 0);
roleid_callback_registered = true;
}
如果尚未注册缓存回调,使用 CacheRegisterSyscacheCallback
函数注册一个回调,以便在系统缓存发生变化时更新本地缓存。这确保了本地缓存与系统缓存的一致性。
-
缓存结果:
last_roleid = roleid; last_roleid_is_super = result;
将当前查询的角色 ID 和结果缓存起来,以便下次查询时可以直接返回结果。
-
返回结果:
return result;
返回 result
,表示给定的角色 ID 是否是超级用户。
这个函数通过查询 PostgreSQL 的系统表 pg_authid
来检查一个角色是否是超级用户,并且使用了缓存来提高性能。它还处理了一些特殊情况,如数据库初始化过程中的超级用户检查。
RecoveryInProgress()函数的执行过程:
这个函数通过检查共享内存中的状态来确定系统是否仍在恢复模式中。如果系统不在恢复模式中,函数会立即返回false
;如果系统在恢复模式中,函数会更新本地状态并返回true
。
- 这里检查
LocalRecoveryInProgress
变量,如果它为false
,则直接返回false
,表示系统不在恢复模式中;如果LocalRecoveryInProgress
为true
,则执恢复过程。
if (!LocalRecoveryInProgress)
return false;
- 将
XLogCtl
全局变量的地址赋给volatile
类型的指针xlogctl
。这里使用volatile
指针是为了确保对共享变量进行新的读取,防止优化器对其进行优化处理。
/*
* use volatile pointer to make sure we make a fresh read of the
* shared variable.
*/
volatile XLogCtlData *xlogctl = XLogCtl;
- 这里更新
LocalRecoveryInProgress
变量的状态。它检查xlogctl
指向的共享内存中的SharedRecoveryState
字段是否不等于RECOVERY_STATE_DONE
(恢复完成状态)。如果不等于,说明恢复尚未完成,LocalRecoveryInProgress
被设置为true
;否则设置为false
。
LocalRecoveryInProgress = (xlogctl->SharedRecoveryState != RECOVERY_STATE_DONE);
- 最后,函数返回
LocalRecoveryInProgress
的值,这个值表示系统是否仍在恢复模式中。在恢复模式中不需要内存屏障(memory barrier)。因为即使函数返回true
,调用者也不能依赖这个返回值来确定系统仍在恢复模式中。
/*
* Note: We don't need a memory barrier when we're still in recovery.
* We might exit recovery immediately after return, so the caller
* can't rely on 'true' meaning that we're still in recovery anyway.
*/
return LocalRecoveryInProgress;
RequestCheckpoint()函数的执行过程:
该函数用于在后端进程中请求一个检查点(checkpoint)。检查点是数据库系统中的一个操作,用于将事务日志(WAL)中的更改刷新到磁盘上,以确保数据的持久性。下面是逐行解释该函数的源码执行过程:
- 声明了三个整型变量
ntries
、old_failed
和old_started
,用于记录尝试发送信号的次数,以及检查点失败和开始的次数。
int ntries;
int old_failed, old_started;
- 检查是否处于独立后端环境(不是由postgres的postmaster进程管理)。
/*
* If in a standalone backend, just do it ourselves.
*/
if (!IsPostmasterEnvironment)
{
/*
* There's no point in doing slow checkpoints in a standalone backend,
* because there's no other backends the checkpoint could disrupt.
*/
CreateCheckPoint(flags | CHECKPOINT_IMMEDIATE);
/*
* After any checkpoint, close all smgr files. This is so we won't
* hang onto smgr references to deleted files indefinitely.
*/
smgrcloseall();
return;
}
- 如果是,那么直接执行检查点。如果不是,在独立后端环境中,创建一个检查点,并且设置
CHECKPOINT_IMMEDIATE
标志,以确保检查点会立即完成; - 在任何检查点之后,关闭所有存储管理器(smgr)文件,以避免无限期地保持对已删除文件的引用。
- 如果处于独立后端环境,函数执行到这里就结束了。
- 设置请求标志,并获取计数器的快照。
/*
* Atomically set the request flags, and take a snapshot of the counters.
* When we see ckpt_started > old_started, we know the flags we set here
* have been seen by checkpointer.
*
* Note that we OR the flags with any existing flags, to avoid overriding
* a "stronger" request by another backend. The flag senses must be
* chosen to make this work!
*/
SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
old_failed = CheckpointerShmem->ckpt_failed;
old_started = CheckpointerShmem->ckpt_started;
CheckpointerShmem->ckpt_flags |= (flags | CHECKPOINT_REQUESTED);
SpinLockRelease(&CheckpointerShmem->ckpt_lck);
- 获取一个自旋锁,以原子地设置请求标志,并获取计数器的快照。
- 保存当前的失败和开始计数,并将传入的
flags
与CHECKPOINT_REQUESTED
标志一起设置到请求标志中。 - 释放自旋锁。
- 定义一个循环,用于发送信号请求检查点。如果检查点进程尚未启动或正在重启,可能会需要重试。
#define MAX_SIGNAL_TRIES 600 /* max wait 60.0 sec */
for (ntries = 0;; ntries++)
- 检查检查点进程的PID是否为0,如果是,表示检查点进程尚未启动。并检查如果尝试次数达到最大值或者
flags
中没有设置CHECKPOINT_WAIT
,则记录错误并退出循环。
if (CheckpointerShmem->checkpointer_pid == 0)
{
if (ntries >= MAX_SIGNAL_TRIES || !(flags & CHECKPOINT_WAIT))
{
elog((flags & CHECKPOINT_WAIT) ? ERROR : LOG,
"could not signal for checkpoint: checkpointer is not running");
break;
}
}
- 如果向检查点进程发送SIGINT信号失败,则检查是否达到了最大尝试次数或者
flags
中是否设置了CHECKPOINT_WAIT
。并检查如果尝试次数达到最大值或者flags
中没有设置CHECKPOINT_WAIT
,则记录错误并退出循环。
else if (kill(CheckpointerShmem->checkpointer_pid, SIGINT) != 0)
{
if (ntries >= MAX_SIGNAL_TRIES || !(flags & CHECKPOINT_WAIT))
{
elog((flags & CHECKPOINT_WAIT) ? ERROR : LOG,
"could not signal for checkpoint: %m");
break;
}
}
- 如果信号发送成功,则退出循环。
else
break; /* signal sent successfully */
- 在每次循环中检查中断,并等待0.1秒后重试。
CHECK_FOR_INTERRUPTS();
pg_usleep(100000L); /* wait 0.1 sec, then retry */
- 如果
flags
中设置了CHECKPOINT_WAIT
,则等待检查点完成。
/*
* If requested, wait for completion. We detect completion according to
* the algorithm given above.
*/
if (flags & CHECKPOINT_WAIT)
{
int new_started, new_failed;
/* Wait for a new checkpoint to start. */
ConditionVariablePrepareToSleep(&CheckpointerShmem->start_cv);
...
}
- 声明两个变量用于存储新的开始和失败计数;
- 准备等待新的检查点开始的条件变量。
- 进入一个循环,等待检查点开始。
for (;;)
{
SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
new_started = CheckpointerShmem->ckpt_started;
SpinLockRelease(&CheckpointerShmem->ckpt_lck);
if (new_started != old_started)
break;
ConditionVariableSleep(&CheckpointerShmem->start_cv,
WAIT_EVENT_CHECKPOINT_START);
}
ConditionVariableCancelSleep();
/*
* We are waiting for ckpt_done >= new_started, in a modulo sense.
*/
ConditionVariablePrepareToSleep(&CheckpointerShmem->done_cv);
- 获取自旋锁,读取新的开始计数,然后释放自旋锁;
- 如果新的开始计数与旧的不同,表示检查点已经开始,退出循环;
- 如果检查点尚未开始,等待条件变量,然后取消等待;
- 准备等待检查点完成的条件变量。
- 准备等待检查点完成的条件变量。
for (;;)
{
int new_done;
SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
new_done = CheckpointerShmem->ckpt_done;
new_failed = CheckpointerShmem->ckpt_failed;
SpinLockRelease(&CheckpointerShmem->ckpt_lck);
if (new_done - new_started >= 0)
break;
ConditionVariableSleep(&CheckpointerShmem->done_cv,
WAIT_EVENT_CHECKPOINT_DONE);
}
ConditionVariableCancelSleep();
- 声明一个变量用于存储新的完成计数;
- 获取自旋锁,读取新的完成和失败计数,然后释放自旋锁;
- 如果新的完成计数大于或等于新的开始计数,表示检查点已经完成,退出循环;
- 如果检查点尚未完成,等待条件变量,然后取消等待。
- 如果新的失败计数与旧的不同,表示检查点请求失败,记录错误并退出函数。
if (new_failed != old_failed)
ereport(ERROR,
(errmsg("checkpoint request failed"),
errhint("Consult recent messages in the server log for details.")));
释放自旋锁;
- 如果新的完成计数大于或等于新的开始计数,表示检查点已经完成,退出循环;
- 如果检查点尚未完成,等待条件变量,然后取消等待。
- 如果新的失败计数与旧的不同,表示检查点请求失败,记录错误并退出函数。
if (new_failed != old_failed)
ereport(ERROR,
(errmsg("checkpoint request failed"),
errhint("Consult recent messages in the server log for details.")));
至此,完成执行检查点(checkpoint)的所有操作。