• SQLite页缓冲区管理


    页面管理器是访问本地数据库文件和日志文件的唯一模块(通过操作系统API)。但是它不对数据库的内容做解析,也不对数据库内容做修改(但是页管理器会对文件头信息部分内容做修改)。它把随机访问系统或面向字节的文件系统抽象成数据库文件(基于页的随机访问系统)。它定义了一套与文件系统无关的接口来访问数据库文件的页。B+树使用页面管理模块的接口访问数据库,而非直接访问数据库文件或日志文件。B+树把数据库文件视为大小均匀的页的逻辑队列。除内存数据库外,一般的数据库都是把数据库文件存在于外存(如:磁盘)上,用的时候读入内存,操作之,有需要的话再写回外存。一般情况下,数据库文件会比较大。又因为内存有限,所以在一部分内存中只保持一小块的数据库文件,这块内存叫做database cachedata buffer,在SQLite中叫做page cache,页管理器负责管理page cache

    1 页面管理器工作职责:对于每一个数据库文件,在文件和缓冲区之间移动页面是页面管理器作为缓冲区管理器的基本功能。移动页面对B+树和其他高层模块是透明的。页面管理器是文件系统与这些高层模块的中间人,其主要目的是数据库在内存中的页是可寻址的,使得高层模块可直接访问页的内容,也负责把页写回数据库文件。页面管理器建立其了一个抽象,使得数据库文件在内存中呈现成为页的队列。除了缓冲区管理器,页面管理器也提供了其他功能,它提供了一个典型的事务处理系统所需的核心功能:事务管理器(实现事务ACID属性,并发的控制及恢复,原子的提交和回滚),数据管理器(负责协调数据库文件与缓冲区之间的页的读写,文件空间管理),日志管理器(向日志文件写入记录),锁管理器(确保事务在访问数据库页之前已经获得对数据库文件的相应的锁)。总之,页面管理器实现了存储的持久性和事务的原子性。

    页面管理器的上层的模块完全从低级别的锁和日志管理机制绝缘,他们不知道锁和日志的活动。B+树模块把所有东西看作以事务为单位,不关心ACID特性的具体实现。页管理器把一个事务拆分成锁操作,日志操作,对数据库文件的读写操作。B+树以页号向页管理器请求一个页,页管理器返回一个指向已加载到缓冲区的页的内容的指针。在对页改动之前,B+树模块通知页管理器使页管理器在日志文件里保存足够的信息以应对可能的恢复,并且要获得对相应数据库文件的锁。B+树在用完一个页时通知页管理器,如果页面被更改,则写回文件。

    2 页面管理器接口结构:页面管理器实现了名为Pager的数据结构,每个打开的数据库文件通过一个单独的Pager对象来管理,每个Pager对象都与唯一一个打开的数据库文件对应,数据库文件和Pager对象几乎等价。B+树想用一个数据库文件时,就新建一个Pager对象,使用对象作为句柄对文件进行页级别操作。页管理器用句柄跟踪关于文件的锁日志文件数据库的状态日志状态等信息。

    3 缓冲区管理:SQLitre为每个打开的数据库文件维护一个单独的page cahce(页缓冲区)。如果一个线程两次或多次打开相同的文件,页管理器只在第一次调用时创建并初始化一个单独的页缓冲区。如果两个或多个线程打开相同的文件,则对这同一个文件将会有很多独立的页缓冲区。内存数据库内存数据库不涉及任何外部设备,但是他们也被视为像普通本地文件一样,全部都存储在页缓冲区中。因此,B/B+树模块可以用相同的接口来访问任何数据库。

    页缓冲区位于应用程序的内存空间。缓冲区中的页也可能被操作系统所缓存。当应用程序要读任意文件的内容时,操作系统先提供自己缓存的页,再用应用程序自己的缓冲区的页。SQLite的页缓冲区的组织和管理是独立于操作系统的。页缓冲区的管理是系统性能的关键。下面讨论页管理器如何管理和维护页缓冲区,缓冲区的客户端如何读取或修改缓冲区元素。

    3.1 缓冲区组织:为了加快搜索缓存速度,内存中缓冲区中内容都会有比较好的组织。SQLite用哈希表组织已被缓存的页,用page-slots(页槽)存储表中的页。缓冲区是全相连的,即任何页槽可以存储任何页面。哈希表最初是空的,随着页需求的增加,页面管理器创建了新的页槽并把其插入哈希表中。一个缓冲区能存储的页槽有个最大值,而内存数据库中无限制(只要操作系统允许应用程序的空间增长)。

    哈希表中的每一页都由一个PgHder类型的对象表示,接着是page image,接着是一些私有数据(B+树用来存储页面特定的控制信息)。(内存数据库没有日志文件,所以他们的恢复信息被记录在内存对象中,指向这些内存对象的指针存储在在私有数据之后,这些指针只被页管理器使用。)当页管理器把附加页调入缓冲区时,缓冲区中的这个页被初始化为零。在缓冲区的所有的页面都可以通过哈希表访问,在Pager对象中哈希表名为aHash,数组大小在SQLite库被编译时就已经确定。每个数组元素都指向‘一桶’页面,每桶的页面以一个无序的双向链表组织起来。PgHdr对象只是对页面管理器是可见的,但对B+树和高层模块是不可见的。PgHdr对象存储了很多控制变量:pgno表示在数据库中其代表的的页号,injournal为真表示本页已经写到回滚日志中,needSync为真表示在数据页被写回到数据库文件之前,日志需要先被刷新到磁盘上(刷新是把一个文件被改动的部分,传输到磁盘上),dirty为真表示页文件被改变,但是并没被写回到数据库文件,inStmt为真表示页在当前的语句声明中,nRef表示当前页的引用数,如果值大于0,页就是活跃的,我们说页是被钉住的,否则页是未被钉住的并且是自由的。还有很多指针变量,在PgHdr对象中,pNextHash和pPrevHash指针是把同一个哈希桶内的页面连起来,pNextStmt和pPrevStmt指针用于把在日志声明中页连起来,pNextFree和·pPrevFree指针用来把所有自由页连起来,自由页从未被拿出哈希桶,在缓冲区中所有的页面(无论是否自由页)都被pNextAll指针所连接起来,pDirty指针把所有的脏页连接起来,注意一个自由业也可能是脏页。

    3.2 缓冲区读:缓冲区是以页号作为搜索键。为了读取一个页,B+树模块调用页面管理器的API sqlite3pager_get函数来读取页号。函数获取页P步骤如下:

    1 搜索缓冲区空间。

    (a )对P值应用哈希函数,并获得索引值(SQLite使用非常简单的哈希函数来得到索引值,页号模哈希数组大小)。

    (b)用索引到aHash数组中并得到哈希桶中

    (c)以遍历pNextHash指针方式,搜寻桶中。如果找到了P值,则发生缓冲命中,引用值+1,并把页地址返回给调用者。

    2  如果P在缓冲区找不到的话,被认为是缓冲未命中。函数找到一个空闲槽来存放期望页(如果缓冲区没有到达最大数目限制,就会增加一个新的槽)。

    3  如果没有空闲的槽可用或可以被创建,某槽就会被决定来释放当前的某一页来重用该槽。这叫做遇难者槽。

    4  如果受害人槽或空闲槽是脏的,这一页就会被写回到数据库文件中去。

    5  页P被从数据库文件读入到一个空闲槽,钉住该页(即引用值+1),并把该页地址返回给调用者。如果页P比当前系统中的最大页还大,是不能读取该页的,代替的,会把整个页初始化为0,还会把底部的私有数据初始化为0。SQLite严格遵循按需存取页面执行政策,使得获取页面逻辑变得简单。

    3.3 缓冲区写入

    当一个页的地址被返回给B+树模块时,页面管理器并不知道客户端什么时候会用这个页。SQLite遵循简单规则如下:客户端获取页,应用页,释放页。在获取到一页后,客户端能直接的修改页内容,但是一定要在作出任何修改之前先调用sqlite3pager_write这个页面管理器API。在从调用返回时,客户端能更新这个页。在写函数第一次对一个页调用时,页管理器会把原始页的内容写到回滚日志文件上去,并把injournal和needSync置位。然后,当日志记录被刷新到磁盘上后页面管理器清除needSync(SQLite支持Write Ahead Log即日志预写协议:直到needSync被清除,否则他们不会把一个已经修改过的页面写回到数据库文件)。每次写函数对一个页调用时,dirty被置位,仅当把这页写内容回到数据库文件时才会清除dirty。因为当客户端对某一页进行写操作时,页面管理器并不知道,对页面的更新并不会马上传到数据库文件。页面管理器遵从延迟写(写回)页面更新的策略。只有当页面管理器进行缓冲区刷新或者是选择性回收脏页的时候,更新才会被传播到数据库文件上去。

    3.4 缓冲区替换

    当缓冲区变满的时候,缓冲区替换会发生。旧的页被移出,为新页腾出空间。当有一个页请求,且这个页不在缓冲区中,并且也没有空闲的槽,页面管理器会找出一个槽当遇难者槽来替换。因为缓冲区是全相连的,所以对一个新的页来说,任何槽都是可以的。遇难者槽的确定由缓冲区替换算法决定。SQLite遵循一种类似于LRU(最近最少使用)的替换策略。SQLite组织自由页作为一个逻辑队列,当一个页被解除钉住时,页面管理器会把这个页放到队列尾部去。遇难者页将会被从队列的头部进行选择,但是不是总会选择LRU队列的首元素。SQLite试着找出在队列头的槽来回收,这样可以不涉及到往日志文件上做刷新操作。(遵守WAL协议,在把脏页写到数据库文件时,页管理器会刷新日志文件。)刷新是一个缓慢的操作,SQLite试图尽可能推迟操作时间。如果遇难者页被找到,队列最前面的页会被回收。否则,SQLite首先刷新日志文件,然后再回收队列上的首个槽。如果遇难者页是脏的,页管理器会在回收该页之前把该页写回数据库文件。被钉住的页是当前被使用的页,不能被回收。为了避免缓冲区的所有页都被钉住的情况,SQLite需要在缓冲区中有一个最小数量的槽来确保总是有一定数量的槽可以被回收,SQLite3.36默认是10个槽。

  • 相关阅读:
    从汇编看c++对静态成员的存取
    从汇编看c++内联函数评估求值
    从汇编看c++初始化列表初始化成员变量
    53. sql2005“备份集中的数据库备份与现有的xx数据库不同”解决方法
    52. 查看linux系统是32位还是64位
    51. linux卸载jdk
    50. linux下查看tomcat日志
    49. jdk-6u45-linux-i586.bin安装步骤
    48. Linux 删除文件夹命令
    47. linux下给已经存在的用户设置用户组
  • 原文地址:https://www.cnblogs.com/amdb/p/4035614.html
Copyright © 2020-2023  润新知