数据库postgresql元数据管理

更新时间:2024-03-31 20:12:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

别人都说,写博客可以帮助自己管理和整理以前知识,自己也来写自己的第一篇博客。

前一段时间都在看postgresql的元数据管理,趁着还没有忘,敢快总结一下,算是自己的第一篇博客。

一、 元数据的概念

先讲一下概念,或者说统一一下用语,什么是元数据,metadata。 这里讲的元数据其实就是平时在数据库也叫“数据字典”,也有叫“系统表”。因为作者以前并没有直接用过数据库,是直接看postgresql的代码学习数据库的,所以对于这些名称到底该叫什么不是非常清楚,这里就统一叫元数据(metadata)。

元数据是什么呢?在我的理解来看,元数据就是管理数据的数据,它记录了数据库里面的数据是如何定义、如何组织等等。

二、 元数据的格式

在postgresql里面,元数据是以系统表的形式存放在磁盘。系统表其实与用户的普通表并没有太大区别,一个表由几个column组成,每一个column都有自己的数据类型。对于系统表的访问同样遵循ACID的特性。这里我们拿pg_namespace来作为例子。

Pg_namespace记录了一个database实例中所有namespace(schema)的信息。(元数据从是否在不同database共享方面分为两种,一类是共享元数据,也就是在所有的database中只有一个元数据表,比如pg_database, 另外一种是在每一个database都有一份单独的实例,比如现在讲的pg_namespace,它在每一个database实例中都有一个)

Pg_namespace的每一行,也就是每一个tuple都记录一个namespace的信息,nspname

记录了它的名称,而它的数据类型是name, 而nspowner记录这个namespace的owner,类型是oid,它其实参照了另外一张元数据表pg_authid的一列。 Pg_authid是记录了数据库中每一个user的信息。Napacl记录关于这个namespace的一些权限。

比如用户输入:select * from tom.t1 ; postgresql在解析这条语句的时候,就会去查pg_namespace这张表里面,是否有一个名称为tom的namespace。

三、 元数据的cache

我们在这里把元数据管理当作postgresql的一个模块。它的代码在src\\backend\\catalo下面。其它模块读取元数据的时候,并不是直接去通过扫描系统表来访问元数据,而是通过元数据的cache来访问的。

为什么cache?因为对于元数据的访问是非常频繁,而元数据是在以表的形式存放在磁盘里面,如果每一次读取元数据都去访问磁盘,那么对于元数据的访问就会成为系统性能的瓶颈,所以这里需要把经常使用到的元数据的放到内存。

那么cache的格式是什么样子?cache与系统表的关系呢?cache的单位是什么?下面会围绕这三个问题慢慢道来。

我们首先弄清楚元数据是如何访问的?这里还拿pg_namespace来作例子。一般对于namespace元数据的访问就像前面讲的,在解析SQL语句的时候,会通过一个字符串来查database中是否有这个namespace? 如果有,那它的OID是什么? 或者在返回给用户一些错误信息的时候,通过namespace的oid来查这个namespace的字符串表示是什么?或者通过namespace的oid来获得它的ACL列表,来进行权限检查。 其实最简单的就是,要么通过namespace的名称来获得这个这个namespace的pg_namespace中的一个,也就是这个namespace对应的tuple。或者通过oid来拿到这个namespace对应的tuple。

这里还是拿pg_namespace来举例子,元数据的cache其实就是在内存中的一个hash表。Hash的key就是一个或几个列的值,而value就是对应一个元数据表的tuple。那这里面又引出一业个问题,用户在使用pg_namespace的时候,可能根据一个namespace的字符串来查,也有可能根据一个namespace的oid来查,一个hash表能实现吗? 这里就引出来我们第二个问题:cache与系统表的关系? 一个系统表的cache有可能是多个hash表, hash表的value都是对应该系统表的一个tuple,而key值不同。对于pg_namespace来讲,它的cache在内存中就有两个hash表。在syscache.h的SysCacheIdentifier中,它列出来所有作为元数据cache的hash表的id, 其中NAMESPACENAME, NAMESPACEOID就是对应pg_namespace的两个hash表, NAMESPACENAME的key是以namespace的字符串,而NAMESPACEOID的key值是namespace的oid。

从上面的描述其实基本上已经回答了三个问题,那么cache的格式是什么样子?cache与系统表的关系呢?cache的单位是什么?

这里再啰嗦一下。 Cache的格式是什么?

元数据cache以hash表的格式对系统表进行cache, key值对应系统表的一个或者多个列,value就是它对应系统表的一行或者说一个tuple

Cache与系统表的关系?

一个hash表只能对应一个系统表,但是一个系统表可能对应多个hash表。

Cache的单位是什么?

因为是以hash表作为cache的,这里cache的最小单位就是系统表的一行,这就意味着,cache并不需要把整张系统表加载到内存,只需要把系统表中使用过的行加载到内存即可。

这里介绍几个与cache相关的结构体。

第一个SysCacheIdentifier,它枚举了postgresql所有的cache的hash表。 第二个CatCache ,它是表示一个hash表。

第三个catctup ,它表示hash 表里面的一个value值,也就是一个tuple

详细的内容可以参见代码。

四、 Cache的同步

这里讲的同步有两层意义 ? ?

本身作为cache,它需要与真正存放在磁盘的系统表的内容保持一致 不同session之间cache的同步

第一层意思比较简单,作为cache,它的内容应该与它的源的内容保持一致,前面讲了,其它模块在使用cache的时候,都是读它的内容,且cache是读元数据的唯一一个入口。但是

用户修改或者删除元数据是直接通过操作系统表来完成的,那么cache中的数据如何与系统表的内容保持一致?

这里还是需要先介绍一点背景知识,在postgresql里面,每一条命令都是一个事务内执行的,一个事务可以执行多个命令。

这里还是举例说明。假如有以下命令

在begin与commit之间的命令都属于一个事务。

如果没有显式的begin 与commit命令,如下图:

那这里的每一条命令都在一个事务里执行,上面四条命令也就是会有四个事务。

Postgresql在一条命令中,比如create table t1,在它的执行过程中,如果修改了元数据(删除、增加、修改),并没有直接在hash表中修改已经被cached tuple,会向一个队列(transInvalInfo. CurrentCmdInvalidMsgs)中写入一条invalidation消息, 比如create table t1这条命令在执行过程中会向pg_class这张元数据表中增加一行,在元数据表增加一行后,它会向队列transInvalInfo. CurrentCmdInvalidMsgs中写入一条消息。 而在一条命令快结束的时候,postgresql会读出来transInvalInfo. CurrentCmdInvalidMsgs队列所有的消息,如果队列中消息

关联的tuple有被cached,那么它就会被从cache中删除。。 这样下一条命令再来从cache读取元数据的时候,会发现cache中没有这一条元数据信息,然后cache模块会从系统表中

重新加载这一行,这样就实现cache与系统表之间内容的同步。

换句话讲,系统表与cache之间的内容并不是实时同步,它们同步的单位是一条命令。 还是有一个例子说明,假设系统里面已经存在一张表,名字叫t1。

我们知道,pg_class这张元数据表记录了系统所有relation的信息,这个relation包括

table 、sequence、index等等。Cache中有两个hash对它进行cache,分别是 RELNAMENSP, RELOID,

RELNAMENSP使用namespace + table name作索引 Reloid使用 table oid作索引

当命令执行select * from t1第3行的时候,因为它是一个查询语句,并不会修改元数据表,但是因为它使用到表t1,它会导致描述t1的tuple被加载到RELNAMENSP和RELOID两个cache中。我们姑且把这个tuple叫做tuple_t1,那么现在tuple_t1共有三份,一份在系统表中,另外两份分别在RELNAMENSP与RELOID中。 接着我们执行第二条命令 drop table t1 , 在这条命令执行过程中,会删除系统表那一份tuple_t1, 且把能够标识tuple_t1的信息写到transInvalInfo. CurrentCmdInvalidMsgs 队列中去。在这个命令结束的时候,postgresql会从transInvalInfo. CurrentCmdInvalidMsgs读到tuple_t1的标识信息,并且根据标识信息,删除RELNAMENSP RELOID被cache的tuple_t1

接下来再执行select * from t1, 这个时候,postgresql会从RELNAMENSP去读取t1的信息,发现cache中没有t1的信息,cache会去读系统表pg_class,发现系统也没有tuple_t1,然就告诉其它解析模块,系统中现在并没有叫t1的表。

第二层意思比较难理解:不同session之间元数据cache的同步

这里还是先介绍一些背景知识,我们知道postgresql是多进程结构,每一个session都

对应一个backend进程,元数据表是在多个session进程之间是共享的,但是元数据的cache在进程之间并不是共享的,而是每一个进程都有自己的每一份私有的cache,我想这样做的好处就是:

? ?

用空间去换时间,每一份进程私有一份cache,那么在读取cache的时候就不用加锁,效率会更快一点。

Cache可以不用关心多版本的问题。 在并发的事务中,不同事务在同一个时间点看到的元数据并不一定是相同的。举个例子,在我在事务A是执行了drop table t1, 在这个事务没有提交前,事务B以执行select * from t1 , 在这个时候,事务A在执行以drop table t1,它以后的命令应该看到的描述t1的元数据已经不存在了, 但是对于事务B来讲,因为事务A还没有提交,它应该看到表t1还是存在的。 如果元数据是在多个进程之间共享的话,它就还需要解决这一个问题。

正是因为postgresql的元数据cache并不是进程之间共享的,所以就有进程之间元数据cache同步的问题。

这里先简单描述一下,后面用例子说明。

Postgresql在每一个命令结束的时候,不仅根据transInvalInfo. CurrentCmdInvalidMsgs队列的信息,把本地的cache删除,它还把transInvalInfo. CurrentCmdInvalidMsgs追加到transInvalInfo. PriorCmdInvalidMsgs这个队列之中。 当一个事务结束的时候,会把transInvalInfo. PriorCmdInvalidMsgs队列所有的消息写到共享内存的队列中。Postgresql在每一命令开始执行之前,都会到共享内存的队列中读取消息,通过读到的消息,删除以应自己cache中的tuple。

下面还是举例说明,假设已经存在table t1 时间点 1 2 3 4 5 6 7 8

上表是说明在两个在不同session同时执行的事务.

在时间点3, sesson2 查询t1,同时把描述t1的元数据加载到自己的cache

在时间点4 session1执行了drop table t1,它把t1的invalidation信息写到自己的transInvalInfo. PriorCmdInvalidMsgs队列中。

在时间点5 session2 执行select * from t1,在执行之前,共享内存还没有t1元数据的invalidation的消息,它仍然能够读到t1在cache中的元数据。

在时间点6 session1 commit导致t1的invalidation的消息写到共享内存中。

在时间点7 在命令开始前,从共享内存读到t1元数据失效的信息,process2会删除cache中关于t1的元数据,所以命令解析过程中,会报找不到t1这张表。

Session1/process1 Begin; Drop table t1; Commit; Sesson2/process2 Begin; Select * from t1 Select * from t1 Select * from t1 Commit;

五、 总结一下

? ? ? ?

在不同的进程之间元数据的同步是通过共享内存的消息队列完成的。 Postgresql也是以表的形式来保存元数据的,记录元数据的表叫系统表。

为了加速元数据的访问,元数据以hash 表的方式,对系统表进行cache,一个系统表因为hash key的不同,可以对应多个hash 表。

元数据的cache在不同的进程之间是私有的,相互独立互不影响。

本文来源:https://www.bwwdw.com/article/hzmr.html

Top