当前位置: 首页 > news >正文

网站空间500m是什么网站制作方案书

网站空间500m是什么,网站制作方案书,WordPress页脚随机文章,织梦网站后台视频教程共享数据缓冲区 概述共享数据缓冲区管理共享缓冲区管理的核心功能包括#xff1a; 共享数据缓冲区的组织结构初始化共享缓冲池BufferDesc 结构体InitBufferPool 函数 如何确定请求的数据页面是否在缓冲区中#xff1f;BufferTag 结构体RelFileNode 结构体ForkNumber 结构体Re… 共享数据缓冲区 概述共享数据缓冲区管理共享缓冲区管理的核心功能包括 共享数据缓冲区的组织结构初始化共享缓冲池BufferDesc 结构体InitBufferPool 函数 如何确定请求的数据页面是否在缓冲区中BufferTag 结构体RelFileNode 结构体ForkNumber 结构体ReadBuffer_common 函数 怎么查看缓冲区中每个缓冲块的状态BufferDesc 结构体pg_atomic_uint32 怎么快速找到缓冲区的空页StrategyGetBuffer 函数BufferStrategyControl 结构体 如果没有空闲页了怎么办 声明本文的部分内容参考了他人的文章。在编写过程中我们尊重他人的知识产权和学术成果力求遵循合理使用原则并在适用的情况下注明引用来源。 本文主要参考了 postgresql-10.1 的开源代码和《OpenGauss数据库源码解析》和《PostgresSQL数据库内核分析》一书以及一些相关资料。 概述 PostgreSQL 的共享数据缓冲区Shared Buffer Cache是数据库系统中用于存储从磁盘读取的数据页的内存区域以便快速重用这些数据从而减少对磁盘的访问次数和提高查询性能。当数据库需要访问某个数据页时它首先检查该页是否已在共享数据缓冲区中如果是直接从内存中读取数据否则从磁盘加载数据页到缓冲区再进行访问。   共享数据缓冲区的大小是可配置的通过调整 shared_buffers 参数来实现这个参数定义了缓冲区分配的内存量。理想的缓冲区大小取决于系统的总内存、数据库的工作负载以及其他内存需求。正确配置共享数据缓冲区对于优化数据库性能至关重要因为它直接影响到数据检索的速度。PostgreSQL 还实现了复杂的缓冲区管理策略比如使用 LRU最近最少使用算法来决定哪些数据页应当保留在缓冲区中哪些可以被替换出去以确保最常访问的数据页尽可能地保留在内存中。 共享数据缓冲区管理 共享缓冲区管理是数据库管理系统DBMS中的一个关键组件特别是在 PostgreSQL 这样的关系数据库系统中。它涉及到数据库系统如何在内存中有效管理、缓存和访问数据页的机制。共享缓冲区Shared Buffers是内存中的一个区域用于存储最近访问的数据页包括表和索引的数据以便快速响应后续的数据查询和操作从而减少直接磁盘 I/O 的需求提升数据库的整体性能。 共享缓冲区管理的核心功能包括 数据页缓存 当数据库操作需要访问磁盘上的数据页时这些页首先被加载到共享缓冲区中。如果后续操作再次请求相同的数据页系统可以直接从内存中获取避免了磁盘访问的延迟。写入策略 为了保证数据的持久性对共享缓冲区中的数据页所做的更改最终需要写回磁盘。PostgreSQL 使用写前日志WAL来确保即使在系统崩溃的情况下所有的事务更改也能被恢复。数据页的实际写回可以延迟执行直到系统认为最合适的时机比如在缓冲区空间不足或定期检查点Checkpoint发生时。缓冲区替换策略 共享缓冲区的大小是有限的当缓冲区满时数据库需要决定哪些数据页保留在内存中哪些被替换出去。PostgreSQL 使用近似最近最少使用LRU算法进行页替换以优先保留最频繁访问的数据页。配置和调优 数据库管理员可以通过调整 shared_buffers 参数来配置共享缓冲区的大小这通常根据系统的总内存和数据库的工作负载进行优化。合理配置共享缓冲区对于提高数据库的性能至关重要。   上图描绘的是数据库中共享数据缓冲区管理的概念模型其中 矩形块: 代表数据库的共享缓冲区中的多个缓冲槽buffer slots每个槽可以存储一个数据块的内容。这是内存中分配给数据库缓存数据块的区域。其中一个缓存块的大小由参数 BLOCK_SIZE 定义在系统初始化时定义。椭圆形 “缓冲区管理器”Buffer Manager的象征它负责管理共享缓冲区的操作包括决定何时从磁盘读取数据块到缓冲区何时将更改后的数据块写回磁盘以及如何在内存中替换数据块。圆柱形 “DataBase” 代表数据库的持久存储如磁盘或其他形式的持久存储介质其中实际存储所有数据块。读/写操作: 箭头表示数据的读写操作。数据库操作如查询和更新首先查找共享缓冲区以确定所需的数据块是否已加载到内存中。如果找到直接进行读写操作提高性能如果没有找到从磁盘读取数据块到共享缓冲区再进行操作。备份/恢复: 从共享缓冲区到数据库的双向箭头可能表示数据的备份和恢复流程。在备份时数据从共享缓冲区写入到备份存储在恢复时备份数据可以重新加载到共享缓冲区然后被恢复到数据库中。 这张数据库共享缓冲区与管理机制图展示了数据库内存中的共享缓冲区是如何被缓冲区管理器管理的以及数据块是如何在内存和磁盘之间流动的。这种机制对于数据库的性能和数据的高效处理至关重要因为它大大减少了对磁盘 I/O 操作的需求而磁盘 I/O 通常是数据库操作中最耗时的部分。 共享数据缓冲区的组织结构 PostgreSQL 的共享数据缓冲区是由一组内存页组成的结构这些页在物理上可以分散存储但在缓冲区管理器的控制下逻辑上是连续的。每个内存页对应数据库磁盘上的一个数据块当数据块被读取时它们被载入这些内存页中。共享数据缓冲区的大小由 shared_buffers 配置参数确定它定义了内存中用于缓存的页的总数。缓冲区管理器负责维护一个页表记录哪些数据块当前在缓冲区中以及它们的使用状态。通过精心设计的替换策略如 LRU最近最少使用算法管理器优先保留最活跃的数据块以优化数据的访问速度和减少磁盘 I/O从而提升数据库操作的效率。这个组织结构是实现 PostgreSQL 高性能数据处理的基础。 初始化共享缓冲池 共享缓冲池处于共享内存区域在系统启动的时候需要对其进行初始化操作负责这一工作的函数为 InitBufferPool。在共享缓冲池管理中使用了一个全局数组 BufferDescriptor 来管理池中的缓冲区其数组元素类型为 BufferDesc。数组元素个数为缓冲区的总数用于管理所有缓冲区的描述符可以认为 BufferDescriptors 就是共享缓冲池。另外还使用了一个全局指针变量 BufferBlocks 来存储缓冲池的起始地址。每个缓冲区对应一个描述符结构 BufferDesc用于描述相应缓冲区的状态信息。 BufferDesc 结构体 在 PostgreSQL 的源码中与共享数据缓冲区管理相关的主要结构体是 BufferDesc它位于 src/include/storage/buf_internals.h 文件中。BufferDesc 结构体表示一个共享缓冲区内的单个缓冲页其中包含了页的状态信息、锁信息、引用计数等。每个 BufferDesc 项与一个在共享内存中的数据块相对应对应的结构体源码如下 typedef struct BufferDesc {BufferTag tag; /* 该缓冲页包含的页面的标识符包含表空间、数据库、关系和块号信息 */int buf_id; /* 缓冲页的索引编号从0开始计数 *//* 标签的状态包含标志位、引用计数和使用计数 */pg_atomic_uint32 state; int wait_backend_pid; /* 如果有后台进程在等待该缓冲页的引用计数变为零则这里记录该后台进程的PID */int freeNext; /* 在空闲链表中的下一个元素的链接 */LWLock content_lock; /* 用于锁定对缓冲页内容的访问的轻量级锁 */ } BufferDesc;BufferDesc 结构体描述了 PostgreSQL 中的一个缓冲页数据块的元数据这些元数据包括 缓冲页的内容信息 通过 BufferTag 结构体标识缓冲页包含的特定页面。缓冲页的位置和状态信息 buf_id 用于标识缓冲页在共享缓冲区中的位置state 用于存储缓冲页的状态包括是否被修改脏页、引用计数等。并发控制信息 wait_backend_pid 和 content_lock 用于在并发环境下控制对缓冲页的访问确保数据的一致性和完整性。缓冲页的链表信息 freeNext 用于将多个 BufferDesc 串联成一个链表这通常用于维护空闲的缓冲页。 此外管理共享数据缓冲区的核心函数和控制逻辑位于 src/backend/storage/buffer 目录下。特别是bufmgr.c 文件包含了对缓冲区进行操作的函数如读取、写入、分配和替换缓冲页的函数。   PostgreSQL 还使用了缓冲池的概念通常是一个 BufferPool 结构它是一个 BufferDesc 数组表示整个共享缓冲区。缓冲池的管理还涉及锁定机制和引用计数以确保对缓冲页的并发访问是安全的。 InitBufferPool 函数 其中InitBufferPool 函数用于在数据库系统初始化期间设置共享缓冲区。这个函数首先为缓冲区描述符、缓冲区块、I/O 锁以及检查点用的缓冲区 ID 数组在共享内存中分配空间并对它们进行初始化。这包括设置每个缓冲区描述符的状态、标识符、链接到空闲列表的下一个缓冲区、以及初始化相关的轻量级锁LWLock。如果这些结构在共享内存中已经存在由 foundDescs 等变量标识则验证它们全部存在这是在后台进程模型中期望的情况。若这些结构尚未存在则初始化一个新的共享缓冲区包括设置所有缓冲区描述符并将它们链接成空闲列表。此外该函数还会初始化用于缓冲区管理策略的其他相关数据结构以及为后台写回上下文设置文件刷新策略。这个函数是 PostgreSQL 启动时建立核心数据结构和内存管理基础的关键部分对于数据库能够高效处理缓存和 I/O 操作至关重要。函数源码如下所示路径src\backend\storage\buffer\buf_init.c /* 初始化共享缓冲池 */ void InitBufferPool(void) {/* 几个布尔变量用于标记是否已经找到了共享内存中的相应结构 */bool foundBufs,foundDescs,foundIOLocks,foundBufCkpt;/* 将缓冲描述符对齐到缓存线界限 */BufferDescriptors (BufferDescPadded *)ShmemInitStruct(Buffer Descriptors,NBuffers * sizeof(BufferDescPadded),foundDescs);BufferBlocks (char *)ShmemInitStruct(Buffer Blocks,NBuffers * (Size) BLCKSZ, foundBufs);/* 将轻量级锁对齐到缓存线界限 */BufferIOLWLockArray (LWLockMinimallyPadded *)ShmemInitStruct(Buffer IO Locks,NBuffers * (Size) sizeof(LWLockMinimallyPadded),foundIOLocks);LWLockRegisterTranche(LWTRANCHE_BUFFER_IO_IN_PROGRESS, buffer_io);LWLockRegisterTranche(LWTRANCHE_BUFFER_CONTENT, buffer_content);/** 用于排序待检查点缓冲区ID的数组位于共享内存中* 以避免在运行时分配大量内存。因为这将在检查点过程中进行* 或者当检查点重启时内存分配失败将是痛苦的。*/CkptBufferIds (CkptSortItem *)ShmemInitStruct(Checkpoint BufferIds,NBuffers * sizeof(CkptSortItem), foundBufCkpt);if (foundDescs || foundBufs || foundIOLocks || foundBufCkpt){/* 应该同时找到所有这些或者一个都不找到 */Assert(foundDescs foundBufs foundIOLocks foundBufCkpt);/* 注意这条路径只在 EXEC_BACKEND 情况下执行 */}else{int i;/** 初始化所有缓冲头。*/for (i 0; i NBuffers; i){BufferDesc *buf GetBufferDescriptor(i);CLEAR_BUFFERTAG(buf-tag);pg_atomic_init_u32(buf-state, 0);buf-wait_backend_pid 0;buf-buf_id i;/** 最初将所有缓冲区作为未使用的链接在一起。* 后续的这个列表的管理由 freelist.c 执行。*/buf-freeNext i 1;LWLockInitialize(BufferDescriptorGetContentLock(buf),LWTRANCHE_BUFFER_CONTENT);LWLockInitialize(BufferDescriptorGetIOLock(buf),LWTRANCHE_BUFFER_IO_IN_PROGRESS);}/* 纠正链接列表的最后一个条目 */GetBufferDescriptor(NBuffers - 1)-freeNext FREENEXT_END_OF_LIST;}/* 初始化其他共享缓冲管理相关的东西 */StrategyInitialize(!foundDescs);/* 初始化每个后端文件刷新上下文 */WritebackContextInit(BackendWritebackContext,backend_flush_after); }其中BufferBlocks[] 使用 buffer_id 标识每个缓冲块。 如何确定请求的数据页面是否在缓冲区中 在 PostgreSQL 中确定请求的数据页面是否在缓冲区中主要涉及共享缓冲区Shared Buffer Cache的查找过程。这个过程大致如下 缓冲区标识 每个数据页面在缓冲区中的存在通过一个缓冲区标识符BufferTag来标识。这个标识符包括表空间 IDTablespace OID、数据库 IDDatabase OID、关系 IDRelation OID、分叉号Fork Number以及块号Block Number。这些信息共同定义了数据页面的唯一性。缓冲区哈希表 PostgreSQL 使用一个缓冲区哈希表Buffer Mapping Hash Table来快速查找缓冲区中是否存在指定的数据页面。这个哈希表将缓冲区标识符映射到缓冲区描述符Buffer Descriptor。查找过程 • 当数据库需要访问一个数据页面时它首先构造该页面的缓冲区标识符。 • 然后数据库通过缓冲区哈希表查找该标识符对应的缓冲区描述符。 • 如果找到了匹配的缓冲区描述符这意味着请求的数据页面已经在共享缓冲区中。缓冲区描述符 缓冲区描述符包含了关于缓冲区状态的信息如是否被锁定、是否被修改脏页面以及关联的数据页面等。如果数据页面在缓冲区中找到数据库可以直接从内存中访问这个页面避免了磁盘 I/O 操作。未命中缓冲区 如果请求的数据页面不在缓冲区中即缓冲区哈希表中没有找到对应的缓冲区描述符PostgreSQL 需要从磁盘读取该页面。读取后数据库可能需要在共享缓冲区中为这个页面分配一个新的缓冲槽并更新缓冲区哈希表以便未来的访问可以直接从内存中获取。 通过这种机制PostgreSQL 可以有效地管理内存中的数据页面优化数据访问性能。这个过程是数据库缓冲区管理策略的一部分对于减少数据库的响应时间和提高事务处理的吞吐量至关重要。 BufferTag 结构体 BufferTag 结构体它在 PostgreSQL 中用于唯一标识共享缓冲区中的一个数据块。BufferTag 是缓冲区管理系统的核心部分它允许数据库精确地追踪和访问内存中的数据页面。下面是对该结构体中每个字段的详细解释路径src\include\storage\buf_internals.h typedef struct buftag {RelFileNode rnode; /* 物理关系标识符用于唯一标识一个数据库中的表或索引 */ForkNumber forkNum; /* 分叉号指明数据块属于关系的哪个物理文件如主数据文件、TOAST文件等*/BlockNumber blockNum; /* 块号相对于关系的开始位置用于在其所属分叉中唯一标识一个数据块 */ } BufferTag;BufferTag 结构体通过组合关系的物理标识符、分叉号和块号BufferTag 能够精确地指向数据库中的一个具体数据块这对于缓冲区管理、数据访问和数据一致性维护至关重要。BufferTag 可以用来查询对应于该文件块的缓冲区查询流程如下 RelFileNode 结构体 RelFileNode 结构体在 PostgreSQL 中用于表示一个数据库关系如表或索引的物理位置。结构体中的每个字段都是使用 Oid对象标识符类型表示的用于唯一标识数据库中的对象。下面是对该结构体中每个字段的详细解释路径D:\pg相关src\include\storage\relfilenode.h typedef struct RelFileNode {Oid spcNode; /* 表空间的 OID用于标识数据块所在的表空间 */Oid dbNode; /* 数据库的 OID用于标识数据块所属的数据库 */Oid relNode; /* 关系的 OID用于标识具体的表或索引 */ } RelFileNode;RelFileNode 结构体通过组合表空间、数据库和关系的 OID 来完整地描述了一个数据库对象的物理存储位置。这使得 PostgreSQL 能够准确地定位到存储在磁盘上的数据块是实现数据管理和访问的基础。在数据库系统中表空间用于管理磁盘上的数据文件数据库 OID 将数据块与特定的数据库实例关联起来而关系 OID 则指定了数据块属于哪个具体的表或索引。这种层次化的标识方法为 PostgreSQL 的数据存储和访问提供了高度的灵活性和效率。 ForkNumber 结构体 ForkNumber 是枚举类型它在 PostgreSQL 中用于标识关系如表或索引的不同物理存储分叉。每个分叉代表了关系的不同物理部分主要用于存储数据、自由空间信息、可见性信息等。以下是对各个枚举成员的详细中文注释路径src\include\common\relpath.h typedef enum ForkNumber {InvalidForkNumber -1, /* 无效的分叉编号用于错误检查或初始化 */MAIN_FORKNUM 0, /* 主分叉用于存储表的实际数据 */FSM_FORKNUM, /* 自由空间地图Free Space Map分叉用于跟踪表中未使用的空间 */VISIBILITYMAP_FORKNUM, /* 可见性地图分叉用于跟踪哪些页面对哪些事务可见 */INIT_FORKNUM /* 初始分叉用于存储关系的初始化分叉仅在关系创建时使用 *//** 注意如果添加了新的分叉需要修改 MAX_FORKNUM 和可能的 FORKNAMECHARS* 并在 src/common/relpath.c 中更新 forkNames 数组*/ } ForkNumber;ReadBuffer_common 函数 ReadBuffer_common 函数是 PostgreSQL 缓冲区读取操作的核心逻辑。该函数负责从共享缓冲区中读取指定的数据块如果数据块不在缓冲区中则从磁盘加载。这个过程涵盖了检查缓冲区是否已存在所需数据块、处理缓冲区分配、读取数据块等多个步骤。以下是对代码的详细解释路径src\backend\storage\buffer\bufmgr.c /* ReadBuffer_common -- 所有ReadBuffer变种的通用逻辑** 如果请求从共享缓冲区缓存中得到满足则*hit被设置为true。*/ static Buffer ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,BlockNumber blockNum, ReadBufferMode mode,BufferAccessStrategy strategy, bool *hit) {BufferDesc *bufHdr;Block bufBlock;bool found;bool isExtend;bool isLocalBuf SmgrIsTemp(smgr);*hit false;/* 确保我们有足够的空间来记住缓冲区锁 */ResourceOwnerEnlargeBuffers(CurrentResourceOwner);isExtend (blockNum P_NEW); // 是否扩展操作TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,smgr-smgr_rnode.node.spcNode,smgr-smgr_rnode.node.dbNode,smgr-smgr_rnode.node.relNode,smgr-smgr_rnode.backend,isExtend);/* 如果调用者请求了P_NEW替换为适当的块号 */if (isExtend)blockNum smgrnblocks(smgr, forkNum);if (isLocalBuf) // 如果是本地缓冲区{bufHdr LocalBufferAlloc(smgr, forkNum, blockNum, found);if (found)pgBufferUsage.local_blks_hit;elsepgBufferUsage.local_blks_read;}else // 如果是共享缓冲区{/** 查找缓冲区。如果请求的块当前不在内存中则设置IO_IN_PROGRESS。*/bufHdr BufferAlloc(smgr, relpersistence, forkNum, blockNum,strategy, found);if (found)pgBufferUsage.shared_blks_hit;elsepgBufferUsage.shared_blks_read;}/* 在这一点上我们不持有任何锁。 *//* 如果缓冲池中已经存在所请求的数据块则完成处理 */if (found){if (!isExtend){/* 如果不是扩展操作只需更新统计信息即可 */*hit true; // 标记命中缓冲池VacuumPageHit; // 更新真空清理命中的页面数if (VacuumCostActive) // 如果真空清理成本模型激活VacuumCostBalance VacuumCostPageHit; // 更新真空清理成本TRACE_POSTGRESQL_BUFFER_READ_DONE(forkNum, blockNum,smgr-smgr_rnode.node.spcNode,smgr-smgr_rnode.node.dbNode,smgr-smgr_rnode.node.relNode,smgr-smgr_rnode.backend,isExtend,found);/* 如果是 RBM_ZERO_AND_LOCK 模式调用者期望在返回时页面被锁定 */if (!isLocalBuf) // 如果不是本地缓冲区{if (mode RBM_ZERO_AND_LOCK) // 如果模式为零并锁定LWLockAcquire(BufferDescriptorGetContentLock(bufHdr),LW_EXCLUSIVE); // 获取内容锁else if (mode RBM_ZERO_AND_CLEANUP_LOCK) // 如果模式为零并进行清理锁定LockBufferForCleanup(BufferDescriptorGetBuffer(bufHdr)); // 获取清理锁}return BufferDescriptorGetBuffer(bufHdr); // 返回缓冲区描述符对应的缓冲区}/* 处理试图扩展关系但发现已存在标记为 BM_VALID 的缓冲区的特殊情况 */bufBlock isLocalBuf ? LocalBufHdrGetBlock(bufHdr) : BufHdrGetBlock(bufHdr); // 获取缓冲区块if (!PageIsNew((Page) bufBlock)) // 如果页面不是新的ereport(ERROR,(errmsg(unexpected data beyond EOF in block %u of relation %s,blockNum, relpath(smgr-smgr_rnode, forkNum)),errhint(This has been seen to occur with buggy kernels; consider updating your system.)));/* 在成功之前我们必须执行 smgrextend否则内核不会保留该页 */if (isLocalBuf){/* 只需要调整标志 */uint32 buf_state pg_atomic_read_u32(bufHdr-state);Assert(buf_state BM_VALID); // 断言缓冲区状态为有效buf_state ~BM_VALID; // 清除有效位pg_atomic_unlocked_write_u32(bufHdr-state, buf_state); // 更新缓冲区状态}else{/* 处理可能有人在我们清除 BM_VALID 和 StartBufferIO 检查它之间重新设置 BM_VALID 的小概率事件 */do{uint32 buf_state LockBufHdr(bufHdr); // 锁定缓冲区头Assert(buf_state BM_VALID); // 断言缓冲区状态为有效buf_state ~BM_VALID; // 清除有效位UnlockBufHdr(bufHdr, buf_state); // 解锁缓冲区头} while (!StartBufferIO(bufHdr, true)); // 开始缓冲区 IO}}/* 在这一点上我们已为页面分配了缓冲区但其内容尚未有效。如果是共享缓冲区IO_IN_PROGRESS 已设置。*/Assert(!(pg_atomic_read_u32(bufHdr-state) BM_VALID)); // 断言缓冲区状态不为有效不需要自旋锁bufBlock isLocalBuf ? LocalBufHdrGetBlock(bufHdr) : BufHdrGetBlock(bufHdr); // 获取缓冲区块if (isExtend){/* 新缓冲区被零填充 */MemSet((char *) bufBlock, 0, BLCKSZ); // 零填充新缓冲区smgrextend(smgr, forkNum, blockNum, (char *) bufBlock, false); // 扩展存储管理器中的文件}else{/* 读取页面除非调用者打算覆盖它只是希望我们分配一个缓冲区。*/// 省略了读取数据块并进行错误处理的代码}/* 如果模式为 RBM_ZERO_AND_LOCK 或 RBM_ZERO_AND_CLEANUP_LOCK在标记页面有效之前获取缓冲区内容锁 */if ((mode RBM_ZERO_AND_LOCK || mode RBM_ZERO_AND_CLEANUP_LOCK) !isLocalBuf){LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_EXCLUSIVE); // 获取独占内容锁}if (isLocalBuf){/* 只需要调整标志 */uint32 buf_state pg_atomic_read_u32(bufHdr-state);buf_state | BM_VALID; // 设置缓冲区状态为有效pg_atomic_unlocked_write_u32(bufHdr-state, buf_state); // 更新缓冲区状态}else{/* 设置 BM_VALID终止 IO并唤醒任何等待者 */TerminateBufferIO(bufHdr, false, BM_VALID); // 终止缓冲区 IO}VacuumPageMiss; // 更新真空清理未命中页面数if (VacuumCostActive) // 如果真空清理成本模型激活VacuumCostBalance VacuumCostPageMiss; // 更新真空清理成本TRACE_POSTGRESQL_BUFFER_READ_DONE(forkNum, blockNum,smgr-smgr_rnode.node.spcNode,smgr-smgr_rnode.node.dbNode,smgr-smgr_rnode.node.relNode,smgr-smgr_rnode.backend,isExtend,found); // 跟踪日志return BufferDescriptorGetBuffer(bufHdr); // 返回缓冲区描述符对应的缓冲区 }函数首先检查是否为扩展操作请求新的块 P_NEW然后根据是本地缓冲区还是共享缓冲区采取不同的处理策略。对于共享缓冲区BufferAlloc 函数用于尝试找到缓冲区如果找到则更新命中或读取的统计信息如果未找到则进行进一步的处理包括可能的磁盘读取。   如果请求的数据块已经存在于缓冲区中found true则根据是否是扩展操作和读取模式如 RBM_ZERO_AND_LOCK进行适当的处理包括锁定页面以供调用者使用。   如果数据块不在缓冲区中ReadBuffer_common 会进行磁盘读取或为新的数据块分配缓冲区。这涉及到处理缓冲区的有效性标记、执行实际的磁盘读取操作以及在必要时进行错误处理。 怎么查看缓冲区中每个缓冲块的状态 在 PostgreSQL 中要查看缓冲区中每个缓冲块的状态你需要访问共享缓冲区中的缓冲区描述符Buffer Descriptor这是因为缓冲区描述符包含了关于缓冲块状态的详细信息。此外某些扩展和贡献模块如 pg_buffercache提供了查看缓冲区状态的功能。   其中BufferDescriptors[] 是一个数组提供缓冲区页面的描述信息。 BufferDescPadded *BufferDescriptors;typedef union BufferDescPadded {BufferDesc bufferdesc;char pad[BUFFERDESC_PAD_TO_SIZE]; } BufferDescPadded;BufferDesc 结构体 BufferDesc 结构体它是 PostgreSQL 中用于描述共享缓冲区中一个缓冲块buffer block的状态和属性的数据结构。共享缓冲区是 PostgreSQL 用于缓存磁盘上数据页的内存区域以减少对磁盘的访问次数和提高数据库性能。以下是对 BufferDesc 结构体中每个字段的详细解释路径src\include\storage\buf_internals.h typedef struct BufferDesc {BufferTag tag; /* 缓冲块中包含的页面的ID用于唯一标识一个数据库页面 */int buf_id; /* 缓冲块的索引号从0开始计数 *//* 标签的状态包含标志位、引用计数和使用计数 */pg_atomic_uint32 state; int wait_backend_pid; /* 等待该缓冲块引用计数变为0的后台进程PID */int freeNext; /* 在空闲链表中的下一个缓冲块的链接 */LWLock content_lock; /* 用于锁定对缓冲块内容的访问的轻量级锁 */ } BufferDesc;其中pg_atomic_uint32 state 是一个原子变量存储了缓冲块的状态包括但不限于是否被修改脏页、是否被固定引用计数以及访问频率使用计数。pg_atomic_uint32 结构体是 PostgreSQL 中用于实现跨平台原子操作的数据结构。原子操作是指在多线程环境下能够保证被执行的操作在开始到结束期间不会被其他线程中断的操作。在数据库系统中这种操作对于维护数据的一致性和完整性至关重要。 typedef struct pg_atomic_uint32 {/* 根据 pg_atomic_flag 的定义这里的注释说明了为何需要这样的定义 */ #if defined(__hppa) || defined(__hppa__) /* 针对 HP PA-RISC 架构适用于 GCC 和 HP 编译器 */int sema[4]; /* 使用数组提供足够的空间来实现原子操作的信号量 */ #elseint sema; /* 在其他平台上一个 int 类型足以作为原子操作的信号量 */ #endifvolatile uint32 value; /* 实际存储的无符号32位整数值声明为 volatile 以防止编译器优化导致的问题 */ } pg_atomic_uint32;pg_atomic_uint32 BufferDesc 结构体中的 state 字段是一个非常关键的部分用于存储缓冲块Buffer Block的当前状态信息。这个字段是 pg_atomic_uint32 类型表示它是一个原子的无符号 32 位整数。在 PostgreSQL 的多线程环境中原子操作确保了在读写这个字段时的线程安全。   state 字段中包含的信息非常丰富它通过位掩码的方式存储了多个状态标志flags如下表所示 状态描述BM_LOCKED表明缓冲区当前被锁定防止其他进程访问以确保数据的一致性。BM_DIRTY表示缓冲区中的数据已被修改脏数据需要在某个时刻写回同步到磁盘上。BM_VALID指示缓冲区中的数据是最新的并且可以被数据库进程安全地读取。BM_TAG_VALID表示缓冲区的标签BufferTag包含表空间、数据库、关系和块号等信息是有效的说明缓冲区已经被分配给特定的数据页。BM_IO_IN_PROGRESS标记当前缓冲区正在进行 I/O 操作如读取或写入磁盘。BM_IO_ERROR表示最近一次对该缓冲区的 I/O 操作发生了错误。BM_JUST_DIRTIED表示缓冲区在最近的写操作开始后被修改过。BM_PIN_COUNT_WAITER存在进程正在等待缓冲区的固定计数pin count变为 0以便它们可以对缓冲区进行操作。BM_CHECKPOINT_NEEDED缓冲区需要在下一个检查点Checkpoint被写回磁盘以确保数据的持久性。BM_PERMANENT表示缓冲区包含的是持久化存储的数据不是临时或未记录日志的数据。 结论通过 BufferDesc 结构体中的 state 字段来查看缓冲区中每个缓冲块的状态。 怎么快速找到缓冲区的空页 在 PostgreSQL 中快速找到缓冲区的空页即未被使用的缓冲槽依赖于缓冲区管理策略特别是空闲缓冲槽的管理机制。PostgreSQL 使用几种策略来跟踪和回收空闲的缓冲槽以便它们可以被重新利用来存储新的数据页。以下是一些关键的机制 缓冲区替换策略   PostgreSQL 使用的一种常见缓冲区替换策略是时钟算法一种近似的 LRU 算法。在这种策略中每个缓冲槽都关联一个使用标记当缓冲槽被访问时该标记被设置。时钟算法周期性地扫描缓冲槽并根据使用标记来选择被替换的缓冲槽。未被使用即空页的缓冲槽会优先被选中用于存储新的数据页。空闲列表   PostgreSQL 维护一个空闲列表用于快速查找可用的缓冲槽。当缓冲槽中的数据被写回磁盘并且不再被任何事务引用时这个缓冲槽就会被添加到空闲列表中。需要新的缓冲槽来存储数据页时系统会首先检查空闲列表。缓冲区描述符   每个缓冲槽都有一个对应的 BufferDesc 结构体其中包含了缓冲槽的状态信息。通过检查这些描述符的状态字段系统可以确定哪些缓冲槽当前未被使用。后台写回进程Checkpointer 和 Background Writer   PostgreSQL 的后台写回进程定期将脏页修改过的缓冲槽写回磁盘。一旦数据被写回相关的缓冲槽就可以被标记为可用从而增加了空闲缓冲槽的数量。 StrategyGetBuffer 函数 StrategyGetBuffer 函数在 PostgreSQL 的缓冲管理策略中选择一个用于 BufferAlloc() 的候选缓冲区。函数首先尝试使用给定的缓冲访问策略如果有来选择缓冲区。如果没有策略或策略没有选中任何缓冲区它会尝试从空闲列表中获取一个缓冲区。如果空闲列表中没有可用的缓冲区函数将使用时钟扫描算法来找到一个未被锁定的缓冲区。该函数确保返回的缓冲区在返回时保持锁定状态以防止在缓冲区被锁定之前其他进程锁定它。这个过程是 PostgreSQL 管理其共享缓冲区特别是在高并发环境下确保数据的一致性和访问效率的关键部分。函数源码如下所示路径src\backend\storage\buffer\freelist.c /** StrategyGetBuffer** 由缓冲管理器调用以获取用于BufferAlloc()的下一个候选缓冲区。* BufferAlloc()的唯一硬性要求是所选的缓冲区当前不能被任何人锁定。** strategy 是一个 BufferAccessStrategy 对象或者为 NULL 表示默认策略。** 为了确保在我们锁定缓冲区之前没有其他人能够锁定它我们必须* 在返回缓冲区时保持缓冲区头部的自旋锁spinlock。*/ BufferDesc * StrategyGetBuffer(BufferAccessStrategy strategy, uint32 *buf_state) {BufferDesc *buf;int bgwprocno;int trycounter;uint32 local_buf_state; /* 避免重复引用和解引用 *//** 如果提供了策略对象看它是否能选择一个缓冲区。我们* 假设策略对象不需要 buffer_strategy_lock。*/if (strategy ! NULL){buf GetBufferFromRing(strategy, buf_state);if (buf ! NULL)return buf;}/** 如果需要我们需要唤醒后台写入器bgwriter。因为我们不想依赖* 自旋锁来完成这个工作所以我们强制先从共享内存中读取一次然后* 基于该值设置闩锁latch。我们需要这样做是因为否则 bgprocno 在我们* 检查时可能会被重置因为编译器可能会重新从内存中读取。*/bgwprocno INT_ACCESS_ONCE(StrategyControl-bgwprocno);if (bgwprocno ! -1){/* 首先重置 bgwprocno然后再设置闩锁 */StrategyControl-bgwprocno -1;/** 这里没有获取 ProcArrayLock这稍微有点问题。实际上这是安全的* 因为 procLatch 永远不会被释放所以我们只是可能设置了错误进程的* 闩锁或没有设置任何进程的闩锁。*/SetLatch(ProcGlobal-allProcs[bgwprocno].procLatch);}/** 我们计算缓冲区分配请求的次数以便后台写入器可以估计缓冲区消耗的速率。* 注意通过策略对象回收的缓冲区在这里故意不被计数。*/pg_atomic_fetch_add_u32(StrategyControl-numBufferAllocs, 1);/** 首先在不获取锁的情况下检查是否有缓冲区在空闲列表中。因为我们在大多数* StrategyGetBuffer()调用中不需要自旋锁所以如果在这里无用地获取它会很遗憾 -* 在大多数情况下是不必要的。这显然留下了一个竞争条件即一个缓冲区被放入空闲列表* 但我们还没看到存储 - 但这是相当无害的它将在下一次缓冲区获取时被使用。** 如果空闲列表中有缓冲区获取自旋锁以从空闲列表中弹出一个缓冲区。* 然后检查该缓冲区是否可用如果不可用则重试。** 注意 freeNext 字段被认为是由 buffer_strategy_lock 而不是* 个别缓冲区自旋锁保护的所以在不持有自旋锁的情况下操作它们是可以的。*/if (StrategyControl-firstFreeBuffer 0){while (true){/* 获取自旋锁以从空闲列表中移除元素 */SpinLockAcquire(StrategyControl-buffer_strategy_lock);if (StrategyControl-firstFreeBuffer 0){SpinLockRelease(StrategyControl-buffer_strategy_lock);break;}buf GetBufferDescriptor(StrategyControl-firstFreeBuffer);Assert(buf-freeNext ! FREENEXT_NOT_IN_LIST);/* 无条件地从空闲列表中移除缓冲区 */StrategyControl-firstFreeBuffer buf-freeNext;buf-freeNext FREENEXT_NOT_IN_LIST;/** 释放锁以便其他人可以在我们检查这个缓冲区时访问空闲列表。*/SpinLockRelease(StrategyControl-buffer_strategy_lock);/** 如果缓冲区被锁定或有非零的使用计数我们不能使用它* 丢弃它并重试。如果VACUUM将一个有效的缓冲区放入空闲列表* 然后其他人在我们到达之前使用它这种情况可能发生。自 8.3 版本以来* 这种情况可能完全不会发生但我们还是应该检查。*/local_buf_state LockBufHdr(buf);if (BUF_STATE_GET_REFCOUNT(local_buf_state) 0 BUF_STATE_GET_USAGECOUNT(local_buf_state) 0){if (strategy ! NULL)AddBufferToRing(strategy, buf);*buf_state local_buf_state;return buf;}UnlockBufHdr(buf, local_buf_state);}}/* 如果空闲列表为空运行“时钟扫描”算法 */trycounter NBuffers;for (;;){/* 使用时钟扫描的当前位置获取缓冲区描述符 */buf GetBufferDescriptor(ClockSweepTick());/** 如果缓冲区被固定pinned或有非零的使用计数我们不能使用它* 减少使用计数除非被固定并继续扫描。*/local_buf_state LockBufHdr(buf);if (BUF_STATE_GET_REFCOUNT(local_buf_state) 0){if (BUF_STATE_GET_USAGECOUNT(local_buf_state) ! 0){/* 减少缓冲区的使用计数并重置尝试计数器 */local_buf_state - BUF_USAGECOUNT_ONE;trycounter NBuffers;}else{/* 找到了一个可用的缓冲区 */if (strategy ! NULL)AddBufferToRing(strategy, buf);*buf_state local_buf_state;return buf;}}else if (--trycounter 0){/** 我们已经扫描了所有的缓冲区而没有做任何状态改变* 所以所有的缓冲区都被固定了或在我们查看它们时是这样。* 我们可以希望有人最终会释放一个缓冲区但失败可能比陷入* 无限循环更好。*/UnlockBufHdr(buf, local_buf_state);elog(ERROR, no unpinned buffers available);}UnlockBufHdr(buf, local_buf_state);} }其中GetBufferDescriptor(StrategyControl-firstFreeBuffer) 根据缓冲区 IDStrategyControl-firstFreeBuffer获取对应的缓冲区描述符 (BufferDesc)。这个函数根据缓冲区的索引号在这个上下文中是 StrategyControl-firstFreeBuffer表示空闲列表中第一个空闲缓冲区的索引来检索对应的 BufferDesc 结构体。BufferDesc 结构体包含了缓冲区的元数据如缓冲区的状态、锁信息、所持数据页的标识等信息。 BufferStrategyControl 结构体 BufferStrategyControl 结构体是 PostgreSQL 中用于控制共享缓冲区空闲列表的关键数据结构。这个结构体包含了管理缓冲区分配策略所需的控制信息和统计数据。它在 PostgreSQL 的缓冲区管理系统中扮演着至关重要的角色。此结构体封装了控制和监视共享缓冲区空闲列表的各种元素和统计信息使得 PostgreSQL 能够有效地管理其缓冲区资源优化数据的读写操作并提高整体数据库性能。函数源码如下所示路径src\backend\storage\buffer\freelist.c /** 共享的空闲列表控制信息。*/ typedef struct {/* 自旋锁保护下面列出的值 */slock_t buffer_strategy_lock;/** 时钟扫描的指针下一个考虑用于替换的缓冲区的索引。注意这不是一个具体的缓冲区 -* 我们只是不断增加这个值。因此要得到一个实际的缓冲区需要对其使用 NBuffers 取模。*/pg_atomic_uint32 nextVictimBuffer;/* 空闲列表的头部索引指向未使用的缓冲区列表的开始 */int firstFreeBuffer;/* 空闲列表的尾部索引指向未使用的缓冲区列表的结束 *//** 注意当 firstFreeBuffer 为 -1 时即列表为空lastFreeBuffer 是未定义的*//** 统计数据。这些计数器的宽度应该足够宽以确保它们不会在一个后台写入器周期内溢出。*/uint32 completePasses; /* 时钟扫描的完整循环次数 */pg_atomic_uint32 numBufferAllocs; /* 自上次重置以来分配的缓冲区数量 *//** 在发生活动时需要通知的后台工作进程号或者如果没有则为 -1。参见 StrategyNotifyBgWriter。*/int bgwprocno; } BufferStrategyControl;/* 指向共享状态的指针 */ static BufferStrategyControl *StrategyControl NULL;如果没有空闲页了怎么办 使用时钟扫描算法寻找替换候选   当没有空闲页时PostgreSQL 会运行时钟扫描Clock Sweep算法来寻找一个替换候选。这个算法会遍历缓冲区寻找引用计数为零即没有被任何查询或事务锁定的缓冲页。时钟算法还考虑了缓冲页的使用计数一个衡量页面活跃程度的指标尽可能地选择最不活跃的页进行替换。后面有机会再做详细的学习写回脏页   如果选中的替换候选是脏页即自上次写入以来已被修改PostgreSQL 需要先将这些更改写回磁盘。这确保了数据的持久性并释放了缓冲页以供重新使用。强制检查点   在极端情况下如果缓冲区压力过大PostgreSQL 可能会提前触发一个检查点Checkpoint。检查点的过程会将所有脏页写回磁盘并更新事务日志从而减少了在故障恢复时需要重放的日志量。通过这种方式检查点可以间接释放更多的缓冲页。调整缓冲区大小和配置   如果频繁遇到没有空闲页的情况可能表明共享缓冲区大小配置shared_buffers 参数不足以满足当前的工作负载需求。在这种情况下数据库管理员可能需要考虑增加共享缓冲区的大小或者优化查询以减少对缓冲区的需求。后台写入器和检查点进程   PostgreSQL 的后台写入器Background Writer进程和检查点Checkpoint进程也会定期将脏页写回磁盘。这有助于确保有足够的空闲页可用于即将到来的数据库操作。错误处理   如果经过上述所有步骤后仍然无法找到可用的缓冲页PostgreSQL 将无法分配新的缓冲页。在极端情况下这可能导致数据库操作失败PostgreSQL 会抛出错误。这种情况下通常需要数据库管理员介入进行进一步的诊断和配置调整。
http://www.w-s-a.com/news/378879/

相关文章:

  • 广东网站设计招工.免费咨询贷款
  • 做试题网站在线做c 题的网站
  • 青岛发现51例阳性南京专业网站优化公司
  • 南昌建站如何上wordpress
  • 洛阳网站建设优惠公司建筑企业上市公司有哪些
  • 营销型网站建设营销型网站建设手机网站设计需要学什么
  • 在线视频网站 一级做爰片南通网站建设找哪家
  • 网站优化文章东莞专业网站建设价钱
  • 哈尔滨网页设计网站模板泰兴建设局网站
  • 响应式网站设计公司报纸做垂直门户网站
  • 陕西旭泽建设有限公司网站企业网站建设软件需求分析
  • 上海公司网站建设方案中企动力西安分公司
  • dedecms网站后台怎样才能上百度
  • 云互联的网站名字亚马逊雨林生物
  • 电商网站功能企查查企业信息查询网
  • 特色网站建设中国住房和城乡建设局官网
  • 长春市住房城乡建设厅网站做白酒网站
  • 自己的网站怎么做的成品免费ppt网站
  • 番禺区网站建设哪里有泰安公司
  • 网站制作详细过程网站开发最强工具
  • 孟村县做网站长春城投建设投资有限公司网站
  • 国家重大建设项目库网站wordpress安装 var
  • 供求信息网站建设报价网站制作 苏州
  • 动漫建模代做网站百度一下wordpress nginx 固定链接
  • 广州网站开发网络公司网站建设的书
  • php手机网站开发教程家政网站怎么做
  • 视频网站的建设预算通信科技网站设计
  • 糖果网站建设策划书淘宝客网站开源
  • 建站公司还有前途吗cf网站编程
  • 网站建设需求确认表建站工具 比较