目录
一、缓冲区概述
1、缓冲区结构
2、buffer_tag结构
3、Backend进程读取操作
4、写脏块
二、缓冲区管理器结构
1、第一层:Buffer Table layer(缓冲区表层)
2、第二层:Buffer Descriptor Layer(缓冲区描述层)
3、第三层:buffer pool layer
4、数据的访问过程
三、缓冲区管理器锁(Buffer Manager Locks)
1、表层的锁
2、描述层的锁
3、spin lock--自旋锁/旋转锁
四、缓冲区管理器的工作原理
1、情况1:访问已存放在缓冲区中的数据块
2、情况2:加载数据块到空的缓冲池插槽
3、情况3:加载数据块到一个需要释放的缓冲池插槽
4、缓冲区块替换机制
五、环形缓冲区(Ring Buffer)
1、Bulk-reading
2、Bulk-writing
3、Vacuum-processing
六、共享池缓冲区参数设置
2、wal_buffers 参数设置
3、effective_cache_size参数设置
一、缓冲区概述
1、缓冲区结构
缓冲区:存放各种类型的数据块
(1)数据文件页、表和索引块
(2)可用空间地图块(fsm)
(3)可见性地图块(_vm)
(4)缓冲区数组索引--buffer_ids
2、buffer_tag结构
(1)RelFileNode:分别为表对象oid、数据块oid、表空间oid
(2)页面的fork number(分别为0、1、2)(0=表和索引块,1=fsm,2=vm)
(3)页面number(页面属于哪个块)
示例:缓冲区标记:{(16821、16384、37721)、0、7}
(1)7=标识第7个块中的页
(2)0=fork号,0表明这是存放表数据的数据块
(3)37721=对象号(关系号,OID),表明是属于哪个对象的
(4)16384=数据库OID,说明该块的内容属于OID为16384的数据库
(5)16821=表空间OID,说明该块的内容属于OID 16821的表空间。
3、Backend进程读取操作
后台进程读取数据块到缓冲区的流程(图解)
4、写脏块
下面进程工作时会导致脏块写(将脏页刷新到存储区):
(1)checkpointer(检查点进程):
--将从上一个检查点以来到当前检查点为止,产生的脏块,全都写入磁盘。
--检査点进程将检査点记录写入WAL段文件,并在检查点启动时刷新脏页。
--ckpt的写是一个I/O密集型的写。
(2)background writer(后台写进程)
--后台写进程bgwr持续一点一点地刷新脏页,对数据库活动的影响最小。
--后台写进程的作用是可以减少检查点密集写的影响。
--后台写进程每200毫秒唤醒一次(由bgwriter_delay定义),最多刷新100页(由bgwriter_Iru_maxpages定义)(默认情况下)。
tip:类比oracle的bgwr和ckpt进程,不同的是:
在oracle中,ckpt会给bgwr一个写的信号,由bgwr完成全部的脏块写动作。
在PG中,检査点与后台写进程分离。pg的ckpt自己也会执行脏块写的动作,与bgwr一起写。
相比之下,PG后台写进程独立的方式可以减少ckpt在写时的I/O密集程度。
二、缓冲区管理器结构
Buffer manager’s three-layer structure--缓冲区管理器的三层结构(图解)1、第一层:Buffer Table layer(缓冲区表层)
在这一层内置的hash函数将buffer_tags映射到插槽,插槽中记录了buffer_tags和描述层的buffer_id的映射关系。
2、第二层:Buffer Descriptor Layer(缓冲区描述层)
本层描述内容包含:buffer_tag与缓冲池插槽id的映射关系,访问次数统计,锁等信息
(1)tag:数据块的tag
(2)buffer_id:缓冲池ID
(3)refcount:记录块被访问的次数。
(4)usage_count:使用的次数,与refcount相关。两者配合使用。
(5)countext_lock and io_in_progress_lock:锁信息。访问内存缓冲区时,需要进行加锁保护。
(6)flags:内存块的标记
--dirty bit:脏块
--valid bit:有效块(脏块的内容被写到磁盘后,原本的脏块就变为有效块)
--io_in_progress bit:正在写/处理的块
(7)freeNext
3、第三层:buffer pool layer
缓冲池是存储数据文件页(如表、索引)的简单数组。缓冲池数组的索引称为buffer_ids;
缓冲池被分割成大小为8k的插槽,等于页面大小。因此,每个槽可以存储整个页面。
4、数据的访问过程
第一次访问一个数据块(在缓冲区全为空时),缓冲区管理器是怎么做的:
(1)先请求一个缓冲区空间,再buffer table 层把描述层的buffer_id把buffer_tag进行映射;
(2)然后在描述层把buffer_tag与缓冲区id进行映射;
(3)最后把数据块读到相应的缓冲区槽。访问数据块时,要怎么做:
(1)先通过buffer table层,获取A块的插槽id=0
(2)通过ID=0定位到Buffer Descriptor层,找到对应的插槽,在插槽中获得对应的buffer_id
(3)将获取的buffer_id告诉后端进程,然后后端进程根据这个buffer_id来直接访问缓冲区
三、缓冲区管理器锁(Buffer Manager Locks)
缓冲区管理器为许多不同的目的使用许多锁;
锁是缓冲区管理器同步机制的一部分,它们与任何SQL语句和SQL选项都不相关。
1、表层的锁
BufMappingLock保护整个缓冲表的数据完整性。它是一个轻量锁,可以在共亨和独占模式下使用。
在缓冲区表中搜索条目时,后端进程保存共亨的BufMappingLock。当插入或删除条目时,后端进程持有独占锁。
2、描述层的锁
每个缓冲区描述符使用两个轻量级锁
(1)content_lock:内容锁。典型的强制访问限制的锁。它可以用于共享和独占模式。
当执行下列操作之一时,将获取独占内容锁:
--dml操作
--物理删除元组或压缩存储页上的可用空间(Vacuum操作和HOT处理)
--冻结存储页中的元组
(2)io_in_progress_lock:用于等待缓冲区上的I/O完成(对数据块进行操作时)。
当pg进程从存储区加载/写入页面数据时,该进程在访问存储器时持有相应描述符的独占io_in_progress锁。
3、spin lock--自旋锁/旋转锁
如何固定缓冲区描述符:
(1)获取缓冲区描述符的自旋锁
(2)将其refcount和usage_count的值增加1
(3)松开旋转锁
如何将脏位设置为“1”:
(1)获取缓冲区描述符的自旋锁
(2)使用按位操作将脏位设置为“1”
(3)松开旋转锁
四、缓冲区管理器的工作原理
1、情况1:访问已存放在缓冲区中的数据块
(1)后端进程访问表层相关插槽;
(2)获取共享模式的BufMapping锁;
(3)通过表层获得对应描述层的插槽ID(id=2);
(4)在描述层通过获得的插槽id,获取缓冲区的buffer id;
(5)释放锁;
(6)后端进程依据获得的buffer id,直接访问缓冲池的内容,获得数据。
2、情况2:加载数据块到空的缓冲池插槽
(1)后台进程没有在缓冲区中发现这个数据块;
(2)从描述层中分配一个可用的插槽,返回插槽id
(3)获取独家模式的BufMapping锁;
(4)在表层绑定tag与id信息;
(5)将磁盘中的数据块内容读取到缓冲池;
(6)释放锁;
(7)后台进程利用从描述层获取的buffer id,直接访问缓冲区内容。
3、情况3:加载数据块到一个需要释放的缓冲池插槽
核心:先释放、再加载
(1)后台进程发现目标数据不在缓冲池中。
(2)在描述层寻找可用的插槽,发现目前插槽已满。
然后根据替换机制规则,判断出F是一个可以老化出去的块。然后将这个块的空间释放,用于存储现在需要查询的数据块。
(3)目前这个内存块存储的还是原本Tag_F映射的buffer id对应的磁盘内容;
(4)获取旧的独家模式的BufMapping锁;
(5)获取新的独家模式的BufMapping锁,并在表层插入一条新的条目。此时在表层就会有两个记录的tag同时指向了同一个描述层id(接下来的步骤需要给旧的删除);
(6)删除旧条目,释放旧的独家模式的BufMapping锁(删除后,映射关系失效);
(7)将当前需要查询的数据,从磁盘中读取到缓冲区中;
(8)释放之前获取的新的独家模式的BufMapping锁;
(9)后台进程根据获取的buffer id直接访问缓冲区读取数据。
4、缓冲区块替换机制
替换页面算法
8.1以前:LRU算法
最近最少使用原则:热度高的表放到LRU的列表头部,热度低了就被放到列表末尾,老化放在末尾的表。
8.1及以后:时钟扫描算法
缓冲区描述符显示为蓝色或青色框,框中的数字显示每个描述符的使用计数,每扫描一次则-1,缓冲区每被访问过一次则+1
当时钟扫描到refcount=0时,此块将会被老化。否则重复循环扫描,直到扫到0为止
五、环形缓冲区(Ring Buffer)
当业务需要一个大块的缓冲池时,假如扫描了超过四分之一缓冲池空间(共享缓冲区/4)时,还没有找到足够的可用缓冲池,则会新分配一个环形缓冲区。这个新分配的环形缓冲池就被称为ring-buffer。
这种情况一般会出现在大数据块操作时,例如:bulk-reading(大量加载)、bulk-writing(大量写)、vacuum-processing(空间整理).
优点:不会因为某个特殊的大业务动作,来影响已经使用的数据缓冲区,起到一个保护作用,提高缓冲区重复利用率,降低磁盘I/O。
1、Bulk-reading
当大量加载业务需要大块的缓冲池切扫描了超过四分之一缓冲池还没找到,则会分配一个256KB的ring-buffer。
2、Bulk-writing
当大量写业务出现这种情况,会分配一个16MB的ring-buffer。
执行下面列出的sql命令时,会触发大量写业务:
(1)COPY FROM command.
(2)CREATE TABLE AS command.
(3)CREATE MATERIALIZED VIEW or REFERSH MATERIALIZED VIEW command.
(4)ALTER TABLE command.
3、Vacuum-processing
当自动真空机进行真空处理时出现这种情况,会分配一个256KB的ring-buffer。
六、共享池缓冲区参数设置
1、shared_buffers参数设置
show shared_buffers;
Alter system set shared_buffers=256M;
2、wal_buffers 参数设置
show wal_buffers;
Alter system set wal_buffers =4M
3、effective_cache_size参数设置
show effective_cache_size;
这个参数提供可用于磁盘高速缓存的内存量的估计值。它只是一个建议值,而不是确切分配的内存或缓存大小。它不会实际分配内存,而是会告知优化器内核中可用的缓存量。
这个值会影响执行SQL时是选择全表扫描还是优化器的决策。在一个索引的代价估计中,更高的数值会使得索引扫描更可能被使用,更低的数值会使得顺序扫描(全表扫描)更可能被使用。
在设置这个参数时,还应该考虑 Postgresql的共享缓冲区以及将被用于 Postgresql数据文件的内核磁盘缓冲区。
默认值是4GB。不建议修改,除非对于这个参数的理解以及数据库业务场景非常了解、笃定修改后会提高性能(大佬可以玩,菜鸡不要动)。