PgStat_Msg统计信息消息
PgStat_Msg联合体包含了所有的统计消息,PgStat_MsgHdr作为所有消息的消息头,也是PgStat_Msg联合体中的一种,其包含了StatMsgType类型的m_type和消息数据大小int类型的m_size。PgStat_MsgDummy作为空信息,仅仅包含了PgStat_MsgHdr作为自己唯一的成员,除此之外的消息都在StatMsgType类型成员之外增加了自定义的成员。
PgStat_Msg | PgStat_Msg对应成员名 | StatMsgType | 用途 | 执行函数 |
PgStat_MsgHdr | msg_hdr | |||
PgStat_MsgDummy | msg_dummy | PGSTAT_MTYPE_DUMMY | 空信息 | |
PgStat_MsgInquiry | msg_inquiry | PGSTAT_MTYPE_INQUIRY | 通知收集器写统计文件消息 | pgstat_recv_inquiry |
PgStat_MsgTabstat | msg_tabstat | PGSTAT_MTYPE_TABSTAT | 发送表和缓冲访问统计消息 | pgstat_recv_tabstat |
PgStat_MsgTabpurge | msg_tabpurge | PGSTAT_MTYPE_TABPURGE | 发送无效表的消息 | pgstat_recv_tabpurge |
PgStat_MsgDropdb | msg_dropdb | PGSTAT_MTYPE_DROPDB | 发送删除的数据库信息消息 | pgstat_recv_dropdb |
PgStat_MsgResetcounter | msg_resetcounter | PGSTAT_MTYPE_RESETCOUNTER | 通知收集器重置计数器消息 | pgstat_recv_resetcounter |
PgStat_MsgResetsharedcounter | msg_resetsharedcounter | PGSTAT_MTYPE_RESETSHAREDCOUNTER | pgstat_recv_resetsharedcounter | |
PgStat_MsgResetsinglecounter | msg_resetsinglecounter | PGSTAT_MTYPE_RESETSINGLECOUNTER | pgstat_recv_resetsinglecounter | |
PgStat_MsgResetslrucounter | msg_resetslrucounter | PGSTAT_MTYPE_RESETSLRUCOUNTER | pgstat_recv_resetslrucounter | |
PgStat_MsgResetreplslotcounter | msg_resetreplslotcounter | PGSTAT_MTYPE_RESETREPLSLOTCOUNTER | pgstat_recv_resetreplslotcounter | |
PgStat_MsgAutovacStart | msg_autovacuum_start | PGSTAT_MTYPE_AUTOVAC_START | 发送一个数据库即将被清理的消息 | pgstat_recv_autovac |
PgStat_MsgVacuum | msg_vacuum | PGSTAT_MTYPE_VACUUM | 发送VACUUM或VACUUM ANALYZE执行完消息 | pgstat_recv_vacuum |
PgStat_MsgAnalyze | msg_analyze | PGSTAT_MTYPE_ANALYZE | 发送ANALYZE执行消息 | pgstat_recv_analyze |
PgStat_MsgArchiver | msg_archiver | PGSTAT_MTYPE_ARCHIVER | pgstat_recv_archiver | |
PgStat_MsgBgWriter | msg_bgwriter | PGSTAT_MTYPE_BGWRITER | bgwriter通知更新统计信息消息 | pgstat_recv_bgwriter |
PgStat_MsgWal | msg_wal | PGSTAT_MTYPE_WAL | pgstat_recv_wal | |
PgStat_MsgSLRU | msg_slru | PGSTAT_MTYPE_SLRU | pgstat_recv_slru | |
PgStat_MsgFuncstat | msg_funcstat | PGSTAT_MTYPE_FUNCSTAT | 发送函数使用统计信息消息 | pgstat_recv_funcstat |
PgStat_MsgFuncpurge | msg_funcpurge | PGSTAT_MTYPE_FUNCPURGE | 发送无效函数的消息 | pgstat_recv_funcpurge |
PgStat_MsgRecoveryConflict | msg_recoveryconflict | PGSTAT_MTYPE_RECOVERYCONFLICT | pgstat_recv_recoveryconflict | |
PgStat_MsgDeadlock | msg_deadlock | PGSTAT_MTYPE_DEADLOCK | pgstat_recv_deadlock | |
PgStat_MsgTempFile | msg_tempfile | PGSTAT_MTYPE_TEMPFILE | pgstat_recv_tempfile | |
PgStat_MsgChecksumFailure | msg_checksumfailure | PGSTAT_MTYPE_CHECKSUMFAILURE | pgstat_recv_checksum_failure | |
PgStat_MsgReplSlot | msg_replslot | PGSTAT_MTYPE_REPLSLOT | pgstat_recv_replslot | |
PgStat_MsgConnect | msg_connect | PGSTAT_MTYPE_CONNECT | pgstat_recv_connect | |
PgStat_MsgDisconnect | msg_disconnect | PGSTAT_MTYPE_DISCONNECT | pgstat_recv_disconnect |
磁盘statfile文件与内存统计结构体
我们知道在数据库集簇的目录下有与统计信息收集器相关的文件:global子文件夹下的pgstat.stat文件用于保存当前全局的统计信息;pg_stat_tmp文件则是PgStat进程和各个后台进程进行交互的临时文件所在地。这里我们将讲述PgStat进程中与读写statfile相关函数流程,如下图中的绿色所示流程。
- pgstat_read_statsfiles其实就是将statfile文件中存储的统计信息恢复到db hash table、Per-database table/function hash table,将statfile文件中存储的统计信息恢复到global、archiver、SLRU stats结构体。
- pgstat_write_statsfiles其实就是将globalStats的stats_timestamp设置为当前时间戳。向文件中写入globalStats、archiverStats、walStats、slruStats结构体数据。 获取DB hash table中的PgStat_StatDBEntry entry,并写入PgStat_StatDBEntry,如果需要,将此数据库的表和函数统计数据写入相应的数据库统计信息文件。获取replSlotStatHashhash table中的PgStat_StatReplSlotEntry,并写入PgStat_StatReplSlotEntry中。
-
static HTAB * pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
函数从数据库集簇下存在的统计信息文件中读取信息并创建databases哈希表返回。如果onlydb参数不是InvalidOid,意味着我们仅仅需要oid为onlydb加上**共享catalog(“DB 0”)**的统计信息;我们仍然为所有数据库返回DB hash table,但是我们不会为其他数据库的表和函数创建哈希表。permanent参数指示从永久文件中读取而不是从临时文件中读取。当该参数为true时(PgStat进程启动时才会设置为true),读取后删除文件;内存中的状态现在是权威的,如果其他人读取这些文件,文件数据应该是过期。如果deep参数设置为true,也就是要求所谓的deep read,表和函数统计信息将会被读取,否则表和函数的哈希表将保持为空,也就是不读取。其流程如下所示:
- 根据permanent参数选定永久或临时的.stat文件。
- 创建pgStatLocalContext内存上下文以存放db hash table,创建key为数据库OID、value为PgStat_StatDBEntry的哈希表。
- 清理global, archiver, WAL and SLRU统计结构体。
- 将globalStats、archiverStats、walStats和slruStats的stat_reset_timestamp设置为当前时间戳,以读取磁盘上的统计信息文件。
- 尝试打开stats文件。如果它不存在,后端只返回零,收集器只从零开始,计数器为空。如果成功,校验stats文件。
- 从stats文件中读取global、archiver、SLRU stats结构体。
- 从stats文件中读取依据不同类型读取Entry,针对PgStat_StatDBEntry类型,将其加入DB hash table;如果对数据库中的表和函数感兴趣,则需要读取PgStat_StatTabEntry或PgStat_StatFuncEntry,并将其加入Per-database table/function hash table。如果指定deep,请从数据库特定文件中读取数据(具体查看函数pgstat_read_db_statsfile)。否则,我们只保留哈希表为空。针对PgStat_StatReplSlotEntry类型,将其加入replSlotStatHash hash table。
- 最后,如果permanent参数选定永久,删除永久统计信息文件。
static bool pgstat_write_statsfile_needed(void)
函数用于判定目前内存统计信息是否有更新,如果pending_write_requests不为NIL,则说明有统计信息需要写入到statsfile文件中。
static void pgstat_write_statsfiles(bool permanent, bool allDbs)
函数写入全局统计文件以及请求的DB文件。
permanent指定写入永久文件而不是临时文件。如果为true(仅在PgStat进程关闭时发生),还将删除临时文件,以便在新PgStat进程准备就绪之前,在新postmaster监管下启动的后端无法读取旧数据。当“allDbs”为false时,将只写入请求的数据库(在pending_write_requests中列出);否则,将写入所有数据库。其流程如下所示:
- 根据permanent参数选定永久或临时的.stat文件,根据permanent参数选定永久或临时的.stat文件。
- 将globalStats的stats_timestamp设置为当前时间戳。向文件中写入globalStats、archiverStats、walStats、slruStats结构体数据。
- 获取DB hash table中的PgStat_StatDBEntry entry,并写入PgStat_StatDBEntry,如果需要,将此数据库的表和函数统计数据写入相应的数据库统计信息文件。
- 获取replSlotStatHashhash table中的PgStat_StatReplSlotEntry,并写入PgStat_StatReplSlotEntry中。
- 如果指定permanent,则需要删除pgstat_stat_filename文件。
- 最后清理pending_write_requests链表
PgStat_MsgInquiry消息类型
在上图中的根据消息类型调用相应的处理函数流程中,有一种特殊的消息类型PgStat_MsgInquiry,该类型的消息由backend后端进程发送给PgStat辅助进程以请求将统计信息写入到stats files中。通常,PgStat_MsgInquiry消息会提示(prompt)全局统计文件、共享目录的统计文件和指定数据库的统计文件的写入动作。如果databaseid为InvalidId,则只写入前两个,也就是只写入全局统计文件、共享目录的统计文件。仅当现有文件的时间戳早于指定的cutoff_time时,才会写入新文件;这防止了在多个请求几乎同时到达时进行重复工作,假设后端进程发送的请求在过去有一点截止时间cutoff_times。clock_time应为请求者的当前本地时间;PgStat收集器使用它来检查系统时钟是否倒转,但除非发生这种情况,否则它不会起作用。然而,我们假设clock_time>=cutoff_time。前面的注释翻译有点看不懂,这里贴一下原始注释,然后分析代码理解一下。(Ordinarily, an inquiry message prompts writing of the global stats file, the stats file for shared catalogs, and the stats file for the specified database. If databaseid is InvalidOid, only the first two are written. New file(s) will be written only if the existing file has a timestamp older than the specified cutoff_time; this prevents duplicated effort when multiple requests arrive at nearly the same time, assuming that backends send requests with cutoff_times a little bit in the past. clock_time should be the requestor’s current local time; the collector uses this to check for the system clock going backward, but it has no effect unless that occurs. We assume clock_time >= cutoff_time, though.)
pgstat_recv_inquiry函数用于处理PgStat_MsgInquiry函数,通过分析该函数可以理解backend后端进程请求将统计信息写入到stats files的流程。该函数第一步需要先看看pending_write_requests列表上是否已经有后端进程请求此数据库的写入请求,如果有则会导致pgstat_recv_inquiry函数直接返回;对于pgStatLocalContext内存上下文中没有对应PgStat_StatDBEntry项目的,直接向pending_write_requests列表中添加该DB OID写入到stats files的请求;如果上次写入此数据库的时间>请求的截止时间且当前时间 < dbentry->stats_timestamp说明主机时间被往以前调整了,需要强制新的统计文件写入以恢复同步(向pending_write_requests列表中添加该DB OID写入到stats files的请求),否则是过时请求;如果msg->cutoff_time <= dbentry->stats_timestamp,说明是过时请求,pgstat_recv_inquiry函数直接返回
PgStat辅助进程收集的统计信息主要用于查询优化时的代价估算。在PostgreSQL的查询优化过程中,查询请求的不同执行方案是通过建立不同的路径(Path)来表达的。在生成了许多符合条件的路径之后,从中选择出代价最小的路径转化为一个计划,这个计划将被传递给执行器执行。因此优化器的核心工作就是建立许多路径,然后从中找到最优的路径。造成同一个查询请求有不同路径的主要原因:表不同的访问方式(如顺序访问Sequential Access、索引访问Index Access、使用TID直接访问元组);表间不同的连接方式(嵌套循环连接Nest-loop join、归并连接Merge Join、Hash连接Hash join);表间不同的连接顺序(左连接Lefft-join、右连接Right-join、布希连接Bushy-join)。评价路径优劣的依据是用系统表pg_statistic中的系统统计信息估计出的不同路径的代价。下篇博客将描述PgStat收集的统计消息如何存储到pg_statistic系统表中。