第三章 存储管理

1. 存储管理器的体系结构

    PostgreSQL的存储管理器主要包括两个功能:内存管理和外存管理。存储管理器除了管理内存和外存的交互外,还对内存进行统筹安排和规划。存储管理器是数据库管理系统与物理存取设备的接口,处在系统结构的底层,它包含了操作物理存取设备的接口。
    存储管理器的体系结构如图:
在这里插入图片描述
    内存管理包括共享内存的管理以及进程本地内存的管理。在共享内存中存储着所有进程的公共数据,例如锁变量、进程通信状态、缓冲区等。而本地内存为每个后台进程所专有,是他们工作区域,存储着属于该进程的Cache(高速缓存)、事务管理信息、进程信息等。为了防止多个进程并发访问共享内存中数据时产生冲突,pg提供了轻量级锁,用于支持对共享内存中同一个数据的互斥访问。pg使用共享内存实现了IPC以及无效消息共享,用以支持进程间通信,还提供内存上下文(MemoryContext)用于统一管理内存的分配和回收,从而更有效安全地对内存空间进行管理。
    外存管理包括表文件管理、空闲空间管理、虚拟文件描述符管理以及大数据存储管理等。在pg中,每个表都用一个文件存储,表文件以表的oid命名。对于超出操作系统文件大小限制的表文件,pg会自动将其切分成多个文件来存储,并在原表文件名的尾部加上切分文件的顺序来标识它们。8.4以后,每个表除了表文件外还拥有两个附属文件:可见性映射表文件(VM)和空闲空间映射表文件(FSM)。前者用于加快清理操作(VACUUM)的执行速度,后者则用于表文件空闲空间的管理,用于记录每个表文件块的空闲空间大小,通过一定的查找机制和数据组织实现了文件块的快速选择。为了避免超过操作系统对每个进程打开文件数的限制,存储管理器使用了虚拟文件描述符机制,使得后台进程可以打开“无限多个”文件。此外,存储管理器还提供了大对象机制以及TOAST机制,用以支持大数据存储。
    pg的存储管理器采用与操作系统类似的分页存储管理方式,即数据在内存中是以页面块的形式存在。每个表文件由多个BLCKSZ(一个可配置的常量)字节大小的文件块组成,每个文件块又可以包含多个元组。表文件以文件块为单位读入内存中,每一个文件块在内存中形成一个页面块。一个文件块中可以存放多个元组,但pg不支持元组的跨块存储,每个元组最大为MaxHeapTupleSize,这样保证了每个文件块中存储的是多个完整的元组。
    存储管理器的主要任务有:

  • 缓冲池管理:为了减少对磁盘的读写,在事务执行时,数据首先将会放入缓冲池中,设立了进程间共享的缓冲池(共享缓冲池)以及进程私有的缓冲池(本地缓冲池);
  • Cache机制:将进程最近使用的一些系统数据缓存在其私有内存中,其级别高于缓冲池;
  • 虚拟文件描述符管理:pg通过虚拟文件描述符(Virtual File Descriptor,VFD)来对物理文件进行管理,避免因为操作系统对进程打开文件数的限制出现错误;
  • 空闲空间管理:用于快速定位到表文件中的空闲空间以便插入新数据,从而提供空间利用率;
  • 进程间通信机制(IPC):IPC用来在多个后台进程之间进行通信和消息的传递,同时还提供了对共享内存的管理;
  • 大数据存储管理:提供大对象和TOAST机制。大对象机制是一种由用户控制的大数据存储方法,它允许用户调用函数,通过SQL语句直接向表中插入一个大尺寸文件;而TOAST机制则是在用户插入的变长数据超过一定限度时自动触发,用户无法对TOAST加以控制。
    在这里插入图片描述

2. 外存管理

    外存管理负责处理数据库与外存介质的交互过程。外存管理由SMGR提供对外存操作的统一接口。SMGR负责统管各种介质管理器,会根据上层的请求选择一个具体的介质管理器进行操作。
在这里插入图片描述

2.1 表和元组的组织方式

    同一个表中的元组按照创建顺序依次插入到表文件中。元组之间不进行关联,这样的表文件叫堆文件。pg系统中包含了四种堆文件:普通堆(ordinary cataloged heap)、临时堆(temporary heap)、序列(SEQUENCE relation,一种特殊的单行表)和TOAST表。临时堆仅在会话中临时创建,会话结束会自动删除;序列则是一种元组值自动增长的特殊堆;TOAST表其实也是一种普通堆,但它被专门用于存储变长数据。
    堆文件的物理结构
在这里插入图片描述
在这里插入图片描述
    当更新的元组同时满足如下条件时,称为HOT元组

  • 所有索引属性都没被修改(索引键是否修改是在执行时逐行判断的,因此若一条UPDATE语句修改了某属性,但前后值相同则认为没有修改);
  • 更新的元组新版本与旧版本在同一文件块内(限制在同一文件块的目的是为了通过版本链向后找时不产生额外的I/O操作从而影响到性能)。在这里插入图片描述

2.2 磁盘管理器

    磁盘管理器是SMGR的一种具体实现,它对外提供了管理磁盘介质的接口,其主要实现在文件md.c中。磁盘管理器并非之间对磁盘上的文件直接进行操作,而是通过VFD机制来进行文件操作。
在这里插入图片描述
    mdfd_chain为为空并不一定表示表仅有一段,可能其他段没有打开。对于一个大表的操作,往往是逐段打开的。

2.3 VFD机制

在这里插入图片描述
在这里插入图片描述
   当进程需要打开一个文件时,会为其分配一个VFD;而关闭文件时则会回收VFD。VFD的分配和回收流程如下:

  • 进程在打开第一个文件时,将初始化VfdCache数组,置其大小为32,为其中每一个Vfd结构分配内存空间,将Vfd结构中的fd字段置为VFD_CLOSED,将所有的数组元素放在FreeList上;
  • 分配一个VFD,即从FreeList头取一个VFD,并打开该文件,将该文件的相关信息记录在分配的VFD中;
  • 若FreeList上没有空闲VFD,则将VfdCache数组扩大一倍,新增加的VFD放入FreeList链表中;
  • 关闭文件时,将该文件所对应的VFD插入到FreeList的头部。
        LRU池的大小与操作系统对于进程打开文件数的限制是一致的。
    在这里插入图片描述

2.4 空闲空间映射表

    对于每个表文件,同时创建一个名为“关系表OID_fsm”的文件,用于记录该表的空闲空间大小,称之为空闲空间映射表文件(FSM)。
    为了能够更快地查找合适的空闲空间,FSM文件不应该太大。因此在FSM中存储的并不是实际的文件块空闲空间大小,而是仅仅用一个字节来记录。该字节的值用于描述对应文件块中空闲空间的范围。在这里插入图片描述
    对于每个表块,不论其中是否有空闲空间,在FSM文件中都能找到对应的字节。若存在,则FSM中用1字节记录;若表块没有空闲空间,在FSM中使用0值来进行记录。
在这里插入图片描述
    FSM文件并不是在创建表文件的时候就创建的,而是推迟到需要使用FSM文件的时候,即执行VACUUM操作时或未来插入元组第一次查找FSM文件时才创建的。

2.5 可见性映射表

    为了能够加快VACUUM查找包含无效元组的文件块的过程,定义了可见性映射表(VM)。VM中为表的每个文件块设置了一位,用来标记该文件块是否存在无效元组。对包含无效元组的文件块,VACUUM有两种处理方式,即快速清理(Lazy VACUUM)和完全清理(Full VACUUM)。注意,VM文件仅在Lazy VACUUM操作中被使用到,而Full VACUUM操作由于要跨块清理等复杂操作,需要对整个表文件进行扫描,这个时候VM文件作用不大。
    对于每个表文件,其对应的VM文件命名为:“关系表OID_vm”。对该文件的操作在visibilitymap.c
    与其他文件一样,VM文件也被划分为若干个文件块(简称VM块),VM块除了必要的标记信息外,其他的每一位都对应一个表块,当表块中所有的元组对当前的事务都是可见的时候,表块对应的位才设置为1。
   当对某个表块中的元组进行更新或者删除后,那么该表块在VM文件中对应位置的标志位将被置为0。在设置标志位的时候,需要对其对应的VM页面加锁。这是为了避免在VACUUM判断该页面是否对所有事务可见的同时,其他进程修改该页面,从而导致VACUUM清理过程中忽略了此页面。
    当标志位为1时,VACUUM操作会忽略扫描对应的表块,房所以能大大提高VACUUM的效率。由于VM文件不跟踪索引,所以对索引的清理操作还是需要进行完全扫描。
    当前,VM文件仅仅是作为一个提示(hint)来加快VACUUIM的速度,所以即使VM文件损坏也仅仅会导致VACUUM忽略那些需要清理的页面,而不会对数据产生任何负面影响。

2.6 大数据存储

  • TOAST机制:使用数据压缩和线外存储来实现;
  • 大对象机制:使用一个专门的系统表来存储大对象数据
    在这里插入图片描述
    在这里插入图片描述
        TOAST机制的主要优点在于可以有效地节省查询时所占的内存空间。通常的查询是用相对比较短键值的主属性来完成,TOAST数据只是在向用户显示结果时才被取出来,在查询过程中并不需要检查TOAST数据的具体取值。因此,查询时所用到的数据要小很多,并且它的大部分元组都存在共享缓冲区里,可以不需要任何线外存储。另一方面,在排序时数据量也比较小,因此排序可更多地在内存里完成。
    在这里插入图片描述
        访问一个线外TOAST数据的基本流程是:首先从表的TOAST属性中获取TOAST指针,然后通过TOAST指针找到TOAST表,再通过TOAST数据的OID在TOAST表中找到所有的片段并按序号拼装起来得到TOAST数据。
    在这里插入图片描述
        TOAST技术保障了PostgreSQL中的元组大小足以存放在一个文件块中,正因为如此,TOAST技术必须被设置为一种系统自动处理的机制,用户不能加以控制。而且TOAST机制主要集中于变长的数据类型。如果用户需要存储文件类型的大数据或者要对自己使用的大数据进行主动控制,就需要用到大数据存储技术
        大对象存储机制可以支持三种数据类型的存储:
  • 二进制大对象(BLOB):主要用来保存非传统数据,如图片、视频、混合媒体等;
  • 字符大对象(CLOB):存储大的单字节字符集数据,如文档等;
  • 双字节字符大对象(DBCLOB):用于存储大的双字节字符集数据,如变长双字节字符图形字符串。
        所有的大对象都存储在一个叫pg_largeobject的系统表中,其在数据库中的OID固定为2613.在这里插入图片描述
        为了加快大对象的查找效率,PostgreSQL为pg_largeobject创建了一个叫pg_largeobject_loid_pn_index的索引表,可以利用该索引根据大对象的OID及它的pageno对大对象的某一页进行快速查找。
    在这里插入图片描述
        TOAST与大对象的比较
    在这里插入图片描述

3. 内存管理

    存储管理问题的本质都是:减少I/O次数。在磁盘上读或写一个块大约要花10~30毫秒,在这段时间内一台普通的机器或许能执行数万条指令。在通常情况下,读写磁盘所用的时间决定了数据库操作所花费的总时间。因此,要尽可能地让最近使用的文件块停留在内存中,这样就能有效地减小磁盘I/O的代价。
在这里插入图片描述

3.1 内存上下文概述

    pg的每一个子进程都拥有多个私有的内存上下文,每个子进程的内存上下文组成一个树形结构,其根节点为TopMemoryContext。在根节点之下有多个子节点,每个子节点都用于不同的功能模块,例如CacheMemoryContext用于管理Cache;ErrorMemoryContext用于错误处理,每个子节点又可以有自己的子节点。在这里插入图片描述
    目前MemoryContext中只有AllocSetContext一种实现,因此pg中只有针对AllocSetContext的一种操作函数集合,由全局变量AllocSetMethods表示。每当创建新的MemoryContext时,会将其methods字段置为AllocSetMethods。
    全局变量AllocSetMethods中指定了AllocSetContext实现的操作函数,它们一一对应MemoryContextMethods中的操作函数:AllocSetAlloc、AllocSetFree、AllocSetRealloc、AllocSetInit、AllocSetReset、AllocSetDelete、AllocSetGetChunkSpace、AllocSetIsEmpty、AllocSetStats和AllocSetCheck。在这里插入图片描述
在这里插入图片描述
    内存上下文的初始化工作由MemroyContextInit来完成。在初始化时首先创建所有内存上下文的根节点TopMemoryContext,然后在该节点下创建子节点ErrorContext用于错误恢复处理:

  • TopMemoryContext:该节点在分配后一直存在,直到系统退出时才释放。在该节点下面分配其他内存上下文节点本身所占用的空间;
  • ErrorContext:该节点是TopMemory的第一个子节点,是错误恢复处理的永久性内存上下文,恢复完毕就会进行重置。

3.2 高速缓存

    Cache中包括一个系统表元组Cache(SysCache)和一个表模式信息Cache(RelCache)。SysCache中存放的是最近使用过的系统表的元组,而RelCache中包含所有最近访问过的表的模式信息(包括系统表的信心)。RelCache中存放的不是元组,而是RelationData数据结构,每一个RelationData结构表示一个表的模式信息,这些信息都由系统表元组中的信息构造而来。两种Cache在每一个进程中都是独立存在维护的。在这里插入图片描述
    对RelCache的管理比SysCache要简单得多,原因在于大多数时候RelCache中存储的RelationData的结构是不变的,因此PostgreSQL仅用一个Hash表来维持这样一个结构。对RelCache的查找、 插入、删除、修改等操作也非常简单。当需要打开一个表时,首先在RelCache 中寻找该表的RelationData结构,如果没有找到,则创建该结构并加人到RelCache中。 和SyeCache的初始化类似,RelCache的初始化同样也在InitPostgres函数中进行,同样分为两个阶段:RelationCachelnitialize和RelationCachelnitializePhase2.在这里插入图片描述
    由于同一个系统表在不同的进程中都有对应的Cache来缓存它的元组,当其中某个Cache中的一个元组被删除或更新时,需要通知其他进程对其Cache进行同步。在pg实现中,会记录下已被删除的无效元组,并通过SI Message方式(即共享消息队列方式)在进程之间传递这个消息。收到无效消息的进程将同步地把无效元组从自己的Cache中删除。
    当前系统支持三种无效消息传递方式:1. 使SysCache中元组无效;2. 使RelCache中ReltionData结构无效;3. 使SMGR无效(表物理位置方式变化时,需要通知SMGR关闭表文件)在这里插入图片描述
在这里插入图片描述

3.3 缓冲池管理

    如果需要访问的系统表元组在Cache中无法找到或需要访问普通表的元组,就需要对缓冲池进行访问。任何对于表、元组、索引表等的操作都在缓冲池进行,缓冲池的数据调度都以磁盘块为单位,需要访问的数据以磁盘块为单位调用函数smgrread写入缓冲池,而smgrwrite将缓冲池数据写回到磁盘。调入缓冲池中的磁盘块称为缓冲区、缓冲块或页面,多个缓冲区组成了缓冲池。
在这里插入图片描述
    pg有两种缓冲池:共享缓冲池和本地缓冲池。共享缓冲池主要用作普通可共享表的操作场所;本地缓冲池则用作仅本地可见的临时表的操作场所。
    对于缓冲池中缓冲区的管理通过两种机制完成:pin和lock。pin实际就是缓冲区的访问计数器。当进程要访问缓冲区前,对缓冲区加pin,pin的数目保存着缓冲区的refcount属性中。当refcount不为0时表明有进程正在访问缓冲区,此时该缓冲区不能被替换。而lock机制为缓冲区的并发访问提供了保障,当有进程对缓冲区进行写操作时加EXCLUSIVE锁,读操作时加SHARE锁,其意义和数据库的锁机制是类似的。
    PostereSQL对共享缓冲区的管理基本上采取了静态方式:它在系统配置时规定好了共享缓冲区的总数 (1000 个,由全局变量NBuffers 定义),以后在每次系统启动时,由Postmaster或某一独立的Postgres从共享内存中分配一片空间用作共享缓冲区,全部 (共享)缓冲区构成共享缓冲池。
在这里插入图片描述
    共享缓冲池的初始化实际上是对两个全局变量进行初始化:第一个是缓冲区描述符数组BufferDescriptors,该数组存储了所有共享缓冲区的描述符;第二个是指针BufferBlocks,全局变量BufferBlocks指向共享缓冲池头部。
    为了快速查找缓冲区,在初始化缓冲池阶段系统为其在共享内存中创建了Hash表。缓冲区描述符中的BufferTag结构体包含一个缓冲块的物理信息,因此具有唯一性。共享缓冲区查询以BufferTag作为索引键,查找Hash表并返回目标缓冲区在缓冲区描述符数组中的位置。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
    两种策略的使用是根据不同的操作来选择的,不同的操作会封装不同的BufferAccessStrategy来调用ReadBuffer_common来获取缓冲区。在需要进行缓冲区 替换时,ReadBuffer_comamon则会通过BulferAlloe 调用StratcgyGetBuffer, StrateeyGetButfer 会根据传 人 的 BufferAccessStrategy执行替换缓冲区的查找,如 果 BufferAccessStrategy的值设为空,将会采用一般的缓冲区替换策略;否则采用缓冲环的替换策略。
    本地缓冲池只在创建临时表或操作的数据对其他进程不可见的特殊情况下使用。且是在使用时创建缓冲池。在这里插入图片描述

3.4 IPC

    PostgreSQL中的IPC只考虑同一计算机上的进程间通信。在这里插入图片描述
    **IPC负责的清除工作有两个方面:一个是与共享内存相关的清除,另一个是与各个后台进程相关的清除工作。**与共享内存相关清除并不是将共享内存丢弃,而是重新设置共享内存。清除工作的流程可以描述如下:首先在申请资源的时候,系统会同时为该资源注册一个清除函数,当要求做清除操作时,系统将会调用对应的清除函数。

4. 表操作与元组操作

4.1 表操作

    表的操作由heapam.c提供接口。在heapam.c中实现了关系表的打开、关闭、删除、扫描等操作。

  • 打开表:表的打开并不是打开具体的物理文件,仅仅是返回该表的RelationData结构体。在这里插入图片描述
  • 扫描表:1. 不依赖索引的顺序扫描;2. 以索引为基础的扫描。 顺序扫描是将文件块逐一加载到缓冲区中,然后扫描每个缓冲区中的每一个元组,以找到满足查询需求的元组。
    在这里插入图片描述
        pg对顺序扫描的实现也有两种策略:基本策略和同步扫描策略。在这里插入图片描述
    在这里插入图片描述
        同步扫描是为了解决快慢不一致的扫描同一个表文件,导致缓存频繁被替换,不能共享缓冲数据。而导致慢扫描需要频繁读取物理文件,从而增加I/O的时间开销问题。同步扫描是指让所有表扫描的位置起始点保持一致。在实现上,为了能够让后续扫描能够获取到第一个扫描的当前扫描位置(当前块号),postgresql在共享内存中放置了一个ss_scan_loactions_t结构来存放对于某个表的当前扫描位置,每个进程中都有一个全局指针scan_loactions指向这个结构。
    在这里插入图片描述
    在这里插入图片描述
  • 关闭表:是一个内存空间释放和将修改过的数据写回磁盘的过程。写回磁盘时,根据缓冲区的脏标记决定是否需要写回磁盘;
  • 表的删除:由smgrdounlink实现。首先它会释放物理内存,将缓冲池中所有该表的缓冲区全部移除(包括脏块)并关闭表。然后根据表的物理路径删除表问题。然后这个函数通常并不会被直接调用。当用户想删除一个表的时候,pg只是将它加入到PendingDeletes这个链表中,在所有的事务提交后,SMGR管理器回调用smgrDoPendingDeletes函数将所有需要删除的表删除。
    在这里插入图片描述

4.2 元组操作

    一个完整的元组信息将对应一个HeapTupleData结构和一个TupleDesc结构,在HeapTupleData中还包含一个HeapTupleHeaderData结构。

  • 插入元组
    在这里插入图片描述
  • 删除元组:使用标记删除的方式来删除元组。这对于MVCC是有好处的,其Undo和Redo速度是相当高速度,因为只需重新设置标记即可。被标记删除的磁盘空间会通过VACUUM收回。
  • 更新元组:实际上是删除和插入操作的结合,即先标记删除旧元组,再插入新元组。
        pg中进行删除和更新操作时,被删除或修改的元组并不会从物理文件中删除,而是在事务标记中被标记为无效。因此,当进行过大量的删除和更新操作后,数据库数据文件中由于有大量的无效元组,其尺寸会变得异常庞大,此时需要对数据库进行一定的清理操作。

5. VACUUM机制

5.1 VACUUM操作

    VACUUM分为两种:一种是Full VACUUM,它会对表进行完全清理;一种是Lazy VACUUM,仅标记无效数据空间为可用。
    PageRepairFragmentation函数,当将一个文件块内无效的元组标记为可用后,该函数将把块内所有的空闲碎片移动到块的空闲区域,从而实现了块内的碎片整理。
    heap_page_prune函数用于清理单个文件块的HOT链,并进行块内碎片的整理。碎片整理调用PageRepairFragmentation函数来完成。
在这里插入图片描述

  • Lazy Vacuum:不会立即回收这些无效数据使用的空间。表文件不会缩小,并且任何文件中没有使用的空间都不会返回给操作系统。可以和通常的数据库操作并发执行;
  • Full Vacuum:通过Vacuum full回收的空间都立即返回给操作系统。不利于并发数据库查询。
    在这里插入图片描述
    在这里插入图片描述

6. ResourceOwner资源跟踪

    ResourceOwner记录了大多数事务过程中使用到的资源,主要包括:

  • 父节点、子节点的ResurceOwner指针,用于构成资源跟踪器之间的树状结构;
  • 占用的共享缓冲区数组和持有的缓冲区pin锁个数;
  • Cache的引用次数以及占用的Cache中存储的数据链表,包括CatCache、RelCache以及缓存查询计划的PlanCache;
  • TupleDesc引用次数以及占用的Snapshot数组。
    在这里插入图片描述
        无论对于哪种资源,都为其定义了三种操作:在ResourceOwner中分配内存、向ResourceOwner中增加资源跟踪、从ResourceOwner中移除资源跟踪。
        对于资源跟踪本身,主要有两种操作:创建资源跟踪器和释放资源跟踪器所跟踪的资源。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值