3. 单文件提交

我们首先概要说明SQLite在单个数据库文件上为了执行事务的原子提交而采取的步骤.在后面的部分将讨论如何设计文件格式以保护其在断电故障中损坏,以及原子提交在多个数据库上的执行.

3.1. Initial State

数据库连接首次打开的时候, 计算机的状态如下图所示.图中最右边标记为Disk的区域为大容量存储设备中的信息,每个矩形为一个扇区,蓝颜色代表扇区中的原始数据.中间区域是操作系统的磁盘cache.此时,cache是冷的, 没有缓存任何数据.左边区域代表SQLite进程空间中的内容,由于SQLite连接处于刚打开状态,因此用户空间还没有数据. 

3.2. Acquiring A Read Lock

在对SQLite写操作之前,即使是追加写操作, 也必须先读取数据库检查其状态.为了知道如何解析INSERT语句以及数据存储的位置,SQLite必须读取sqlite_master 表格中的schema信息.

读取数据库文件的第一步是获取该文件的一个共享锁(shared lock).共享锁允许该数据库文件的多个连接同时进行读取操作,但是共享锁将阻止该数据库文件连接上的写入操作.如果另一个连接正在写入该数据库文件, 读操作可能读取到的数据中,部分是写入操作之前的,部分数据是写入操作之后的, 这将造成写入操作是非原子性的, 因此有必要在共享锁期间禁止其他连接上的写入操作.

需要注意的是,共享锁存在于操作系统的磁盘cache中,而不是在磁盘上,文件锁通常仅仅是操作系统内核中的标识,其执行细节取决于操作系统提供的接口层.因此如果操作系统崩溃或者发生断电故障,锁将立即消失.如果创建锁的进程退出,锁也将立即消失. 

3.3. Reading Information Out Of The Database

获得共享锁以后,我们就可以从数据库中读取数据了.此时,我们假设cache是冷的,因此数据首先从大容量存储设备读取到操作系统cache中,然后在从操作系统cache中读取到用户空间.在后续的读取操作中,需要的数据可能已经存在于操作系统cache中,因此只需从操作系统cache中读取数据到用户空间.

通常,读取操作会从数据库文件中读取部分页(pages).在这个图示的例子中,数据库文件有8个页, 读取操作获得了其中3个页中的数据.在一个典型的应用程序中,数据库文件可能有几千个页,读取操作通常只访问其中一小部分页. 

3.4. Obtaining A Reserved Lock

在对SQLite数据库文件进行写操作之前,必须获得一个预订锁(reserved lock).预订锁和共享锁一样,都允许其他进程读取数据库文件.一个预订锁可以和其他进程的多个共享锁共存,但是,一个数据库文件只有一个预订锁.因此在给定时间内,只有一个进程允许对数据库文件进行写操作.

预订锁的设计思想是,一个进程将要对数据库文件进行修改,但此时还没有发起修改请求.因为修改请求还没有开始,其他进程可以继续进行读取操作,然而,该锁将禁止其他进程的写入操作. 

3.5. Creating A Rollback Journal File

在对数据库文件进行任何修改之前,SQLite首先创建一个分离的回滚日志文件,并且把被修改数据的原始内容页面写入到该日志文件中.回滚日志文件的设计思想是,其包含数据库文件恢复到原始状态的所有数据.

回滚日志文件包含一个小的文件头(图示中绿色区域),用来记录文件库文件的原始大小.回滚日志文件现在只在操作系统的磁盘cache中创建,在后续的某个时刻将被写入到大容量存储设备上去.因为没有发生实际的IO操作,所以创建过程很快.这一阶段的操作如下图所示. 

3.6. Changing Database Pages In User Space

在原始页面数据保存到回滚日志文件以后, SQLite进程就可以修改其页面中的数据了.每个数据库连接有其私有的用户空间,因此不同数据库连接间的数据更改是不可见的.其他的数据库连接仍然能看看到操作系统磁盘cache中还没有修改的数据.因此,即使一个进程正在修改其用户空间中的数据, 其他进程仍然能够读取操作系统磁盘cache中的原始数据. 

3.7. Flushing The Rollback Journal File To Mass Storage

接下来的步骤就是刷新(flush)回滚日志文件中的数据到非易失存储设备.正如我们后续将看到的,这一步骤至关重要,它将保证数据库在异常断电故障时,数据不被损坏.因为写磁盘通常是一个耗时操作,这个操作步骤当然也需要很长时间.

这个步骤通常比简单的刷新回滚日志文件到磁盘更复杂一些.在大多数操作系统上, 需要进行2个分离的刷新(或者fsync)操作.第一次刷新将写入回滚日志文件的基本数据,然后更新回滚日志文件头中的页面号,这些页面中的数据就是回滚日志的基本内容.第二次刷新将把更改的回滚日志头写入到磁盘.关于为什么需要更新回滚日志头数据并在第二次刷新中写入到磁盘的细节,后续部分再讨论. 

3.8. Obtaining An Exclusive Lock

在修改磁盘上的数据库文件之前,我们必须获得该数据库的一个排他锁( exclusive lock).获得排他锁其实是一个两阶段的处理过程,首先, SQLite获得一个”pending”锁,然后升级pending锁到排他锁.

pending锁允许其他获得共享锁的进程继续读取数据库文件,但是它将阻止创建新的共享锁.pending锁的设计思想是,防止由于大量的读操作导致的写操作饿死.由于可能存在几十或者几百个进程读取数据库文件,每个进程在读取之前获得共享锁, 然后读取数据库文件,最后释放共享锁.如果大量的进程读取相同的数据库文件,在之前的进程没有读取完毕时, 新进程又获得共享锁,因此数据库文件上将始终存在共享锁,写操作的进程没有机会获得排他锁.pending锁将允许已经存在的共享锁继续操作,但是将阻止创建新的共享锁.最终,所有的共享锁都将退出,pending锁有机会升级到排他锁. 

3.9. Writing Changes To The Database File

一旦获得排他锁, 就意味着没有其他进程正在进行读取操作,这时候对数据库文件的写操作是安全的.通常,写请求中的数据只是更新操作系统磁盘cache,并没有真正写到大容量存储设备上. 

3.10. 0 Flushing Changes To Mass Storage

为了将写操作中的更新数据真正写入到数据库文件, 这里需要刷新操作.刷新操作至关重要,因为它将保证数据库文件在断电故障时不被损坏.和3.7节中刷新回滚日志文件一样,这是个耗时操作,SQLite事务提交中的大部分时间都将消耗在磁盘IO的刷新操作中. 

3.11. 1 Deleting The Rollback Journal

写请求中的数据安全的存储到大容量存储设备以后,回滚日志文件就可以删除了.在事务提交过程中, 回滚日志文件删除是瞬态完成的.如果在回滚日志文件被删除之前,发生了操作系统崩溃或者断电故障,恢复程序(后面将介绍)将认为数据库文件没有任何改变.如果在回滚日志文件被删除之后,发生了操作系统崩溃或者断电故障,恢复程序将认为所有提交都已经写入了磁盘文件.因此根据回滚日志文件是否存在, SQLite可以判断事务提交中的数据要么全部写入了磁盘文件,要么没有写入到磁盘文件.

从用户进程的角度看, 删除一个文件像是原子性的,但实际上并不是.用户进程总是可以通过操作系统判断一个文件是否存在.如果在事务提交过程中发生了断电故障, SQLite重启后将通过操作系统检查回滚日志文件是否存在,如果回滚日志文件存在,那么事务是没有完成的,将进行回滚操作.如果回滚日志文件不存在,则说明事务提交已经完成.

事务的存在取决于回滚日志文件是否存在,并且从用户进程角度看,删除一个文件是原子性的,因此,事务看起来也是原子性的.

在许多操作系统上,删除一个文件是比较昂贵的操作. SQLite会做优化处理,会把回滚日志文件大小截断为0字节,或者将回滚日志文件的头信息清零.通过其中的任意一种优化方法处理后,回滚日志文件都不能再用于回滚操作, 因此可以认为事务提交已经完成.从用户进程角度看,截断文件大小为0字节和删除一个文件一样是原子性的.尽管对回滚日志文件头进行清零操作不是原子性的,但是如果回滚日志文件头信息中有非法格式的数据,其都不能用于回滚操作,因此,我们可以认为,只要回滚日志文件头中有字段被改成无效值,事务提交都是已经完成状态.典型的,我们只要把回滚日志文件头中的第一个字节设置为0,该文件就变成无效了. 

3.12. 2 Releasing The Lock

提交阶段的最后一个步骤是释放排他锁, 以便其他进程可以访问该数据库文件.

在下面的图示中,当锁被释放以后,用户空间中的数据也被清除了.SQLite之前的老版本是这样执行的,但是在最近的版本中,将保留用户空间中的数据,这样在下一次事务中,这些数据可能会被重用.重用用户空间中的数据比从操作系统磁盘cache中读取数据,或者通过磁盘驱动从磁盘读取数据都要高效的多.在重用用户空间的数据之前,我们首先必须获得该文件的共享锁,并且确保没有其他进程修改过数据库文件.在数据库文件的第一个页面中,存在一个计数字段,每当数据库文件修改以后,这字段都将累加更系.我们可以检查这个字段来判断是否其他进程修改过数据库文件.如果数据库文件被其他进程修改了, 用户空间中的数据必须清除,并重新从操作系统读取.如果数据库没有被其他进程修改,重用用户进程空间中的数据将获得显著的性能提升. 

4. Rollback

原子提交被认为是瞬态完成的,但是之前描述的提交过程肯定是需要一定时间才能完成的.假定计算机的断电故障中断了提交过程,为了维持瞬态提交这种假象,我们必须不完整的更改,把数据库文件恢复到事务开始之前的状态.

4.1. When Something Goes Wrong…

假定在上面的步骤3.10中发生了断电故障,此时正在进行刷新写操作数据到磁盘.当重新上电以后,数据状态可能如下图所示.我们正试图向磁盘刷新三个页面的数据,但是仅有一个页面被成功写入,另一个页面部分写入,而第三个页面根本没有写入.

恢复上电以后,磁盘上的回滚日志文件是完整有效的.这一点至关重要,因为步骤3.7中的刷新操作绝对保证了在对数据库文件进行任何改动之前, 回滚日志文件已经安全的写入到了非易失存储设备中. 

4.2. Hot Rollback Journals

任何SQLite进程在访问数据库文件时,都需要获得共享锁(见3.2描述), 然后注意到存在一个会滚日志文件,SQLite进程检查该会滚日志文件是否是一个热日志(“hot journal”).如果该回滚日志文件是热日志文件,需要对其进行回放操作,以便恢复数据库文件到其原始状态.当之前的事务提交过程中发生了操作系统崩溃或者断电故障,才有可能存在一个热日志文件.

一个热日志文件需要满足下面的所有条件:

  • 回滚日志文件存在.
  • 回滚日志文件不是空的.
  • 主数据库文件上没有预订锁.
  • 回滚日志文件头格式正确,没有被清零.
  • 回滚日志文件不包含住日志文件名(见5.5中的描述),或者包含主日志文件名,且该主日志文件存在.

4.3. Obtaining An Exclusive Lock On The Database

处理热日志文件的第一步是获得数据库文件的排他锁,这将阻止其他进程对该热日志文件进行回滚操作. 

4.4. Rolling Back Incomplete Changes

一旦进程获得了排他锁,就允许对数据库文件进行写操作.它从回滚日志文件中读取原始的数据,然后把这些原始数据写到数据库文件相应的页面中.回滚日志文件头信息中记录着数据库文件在事务开始之前的原始大小,SQLite使用这个信息将数据库文件截断为原始文件大小以避免之前不完整的事务提交导致的数据库文件变大的情况.回滚操作完成后, 数据库文件应该和事务开始之前大小一致并且文件内容也一致. 

4.5. Deleting The Hot Journal

当回滚日志文件中的数据全部回放到数据库文件中后,并且已经刷新到磁盘,以避免我们遭遇另一次掉电故障,热日志文件就可以被删除了. 
正如3.11部分所讨论的,当操作系统执行文件删除操作比较昂贵时,作为优化措施, 回滚日志文件可能被截断为0字节长度,或者其头信息中可能被清零.无论哪种处理方式,回滚日志文件将不再是热日志文件. 

4.6. Continue As If The Uncompleted Writes Had Never Happened

恢复过程的最后一步是将排他锁降级到共享锁.至此,数据库文件就回到了事务提交之前的状态.因为所有的恢复处理都是原子性的和透明的, SQLite就好像从来没有发生过被终止的事务一样. 

https://blog.csdn.net/azurelaker/article/details/82594113

SQLite的原子提交--单文件场景的更多相关文章

  1. SQLite的原子提交原理

    本文描述了sqlite为保证数据库文件不被损坏而采取的种种手段.. 以下是原译者的摘要:http://www.kuqin.com/shuoit/20150618/346693.html 摘要: 本文源 ...

  2. sqlite原子提交原理

    英文地址 文章参考 简介 支持事务的数据库系统如sqlite的一个重要特性是原子提交(atomic commit).也就是在一个事务中进行的对数据库的写操作要么全部执行,要么全部不执行.看起来像是对数 ...

  3. ajax方式提交带文件上传的表单,上传后不跳转

    ajax方式提交带文件上传的表单 一般的表单都是通过ajax方式提交,所以碰到带文件上传的表单就比较麻烦.基本原理就是在页面增加一个隐藏iframe,然后通过ajax提交除文件之外的表单数据,在表单数 ...

  4. JavaWeb -- Struts2,对比, 简单表单提交,校验,防重复提交, 文件上传

    Struts2核心流程图 1. Struts2 和 Struts1 对比 struts1:基于Servlet(ActionServlet),actionForm众多(类的爆炸),action单例(数据 ...

  5. SpringMVC文件上传下载(单文件、多文件)

    前言 大家好,我是bigsai,今天我们学习Springmvc的文件上传下载. 文件上传和下载是互联网web应用非常重要的组成部分,它是信息交互传输的重要渠道之一.你可能经常在网页上传下载文件,你可能 ...

  6. ASP.NET MVC5+EF6+EasyUI 后台管理系统(56)-插件---单文件上传与easyui使用fancybox

    系列目录 https://yunpan.cn/cZVeSJ33XSHKZ  访问密码 0fc2 今天整合lightbox插件Fancybox1.3.4,发现1.3.4版本太老了.而目前easyui 1 ...

  7. 小型单文件NoSQL数据库SharpFileDB初步实现

    小型单文件NoSQL数据库SharpFileDB初步实现 我不是数据库方面的专家,不过还是想做一个小型的数据库,算是一种通过mission impossible进行学习锻炼的方式.我知道这是自不量力, ...

  8. (实用篇)php处理单文件、多文件上传代码分享

    php处理  单文件.多文件上传实例代码,供大家参考,具体内容如下 后台处理文件submit_form_process.php <?php /************************** ...

  9. php文件上传之单文件上传

    为了简单一些,php文件跟form表单写在了一个文件里. php单文件上传----> <!DOCTYPE html> <html> <head> <me ...

随机推荐

  1. JavaScript&Date对象

    JavaScript Date对象 <script type="text/javascript"> var date = new Date(); document.wr ...

  2. oracle的Date类型遇到MyBatis产生的坑

    坑描述: 公司的订单表数据量巨大(亿级),在进行查询的时候,发现一个慢查询. 背景: 数据库:oracle 表:T_order 索引字段:create_date  (字段类型 date) 慢查询sql ...

  3. JavaScript是如何工作的:与WebAssembly比较及其使用场景

    摘要: WebAssembly未来可期. 原文:JavaScript是如何工作的:与WebAssembly比较及其使用场景 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 这是专门探索 ...

  4. js 随机点名

    1.对象构造函数 设置节点与人名 constructor({ printElement, startElement, stopElement , person }) { this.list = per ...

  5. JavaScript for/in 语句 遍历数组内容

    for-in遍历 for-in是为遍历对象而设计的,不适用于遍历数组. 遍历数组的缺点:数组的下标index值是数字,for-in遍历的index值"0","1" ...

  6. 小tips:JS严格模式(use strict)下不能使用arguments.callee的替代方案

    在函数内部,有两个特殊的对象:arguments 和 this.其中, arguments 的主要用途是保存函数参数, 但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 a ...

  7. loj#6033. 「雅礼集训 2017 Day2」棋盘游戏(二分图博弈)

    题意 链接 Sol 第一次做在二分图上博弈的题..感觉思路真是清奇.. 首先将图黑白染色. 对于某个点,若它一定在最大匹配上,那么Bob必胜.因为Bob可以一直沿着匹配边都,Alice只能走非匹配边. ...

  8. 洛谷P3246 [HNOI2016]序列(离线 差分 树状数组)

    题意 题目链接 Sol 好像搞出了一个和题解不一样的做法(然而我考场上没写出来还是爆零0) 一个很显然的思路是考虑每个最小值的贡献. 预处理出每个数左边第一个比他小的数,右边第一个比他大的数. 那么\ ...

  9. 洛谷P4063 [JXOI2017]数列(dp)

    题意 题目链接 Sol 这题想还是不难想的,就是写起来很麻烦,然后去看了一下loj的最短代码表示只能Orz 首先不难发现一条性质:能够选择的区间一定是不断收缩的,而且新的可选区间一定是旧区间的某个位置 ...

  10. JMeter java.net.SocketException:Operationnotsupported:connect解决方案

    java.net.SocketException: Operation not supported: connect解决方案   by:授客 QQ:1033553122 测试环境 apache-jme ...