目录
SQLite
的原子提交原理
...
1
1.0
简介
...
2
2.0
硬件设定
...
2
3.0
单个文件提交
...
4
3.1
实始状态
...
4
3.2
申请一个共享锁
...
5
3.3
从数据库里面读取信息
...
5
3.4
申请一个
Reserved Lock
.
6
3.5
生成一个回滚日志文件
...
6
3.6
修改用户进程中的数据页
...
7
3.7
刷新回滚日志文件到存储设备中
...
7
3.8
获得一个独享锁
...
7
3.9
将变更写入到数据库文件中
...
8
3.10
刷新变更到存储
...
8
3.11
删除回滚日志文件
...
9
3.12
释放锁
...
9
4.0
回滚
...
10
4.1
出事了,出事了
!!!
10
4.2 Hot
Rollback Journals
11
4.3
取得数据库的一个独享锁
...
11
4.4
回滚没有完成的变更
...
12
4.5
删除
hot
日志文件
...
13
4.6
如果一切正常,没有什么未完成的写操作
...
13
5.0
多文件提交
...
14
5.1
每个数据库文件单独拥有日志文件
...
14
5.2
主日志文件
...
15
5.3
更新回滚日志文件头
...
16
5.4
修改数据库文件
...
16
5.5
删除主日志文件
...
17
5.6
清除回滚日志
...
17
6.0
原子操作的一些实现细节
...
18
6.1
总是记录整个扇区
...
18
6.2
写日志文件时垃圾的处理
...
19
6.3
提交前缓存溢出
...
19
7.0
优化
...
20
7.1
在事务间保存缓存
...
20
7.2
独享访问模式
...
21
7.3
不必将空闲页写进日志
...
21
7.4
单页更新及扇区原子写
...
22
7.5
Filesystems With Safe Append Semantics
22
8.0
原子提交行为测试
...
23
9.0
会导致完蛋的事情
...
23
9.1
缺乏文件锁实现
...
23
9.2
不完整的磁盘刷新
...
24
9.3
文件部分地删除
...
24
9.4
写入到文件中的垃圾
...
24
9.5
删除掉或更名了
“hot
”日志文件
...
24
10.0
总结及未来的路
...
25
1.0
简介
“原子提交”是
SQLite
这种支持事务的数据库的一个重要特性。原子提交意味着某个事务中数据库的变化会完整完成或者根本不完成。原子提交意味着不同的写入分别写入到数据库的不同部分就似同时发生在同一个时间点一样。
实际上硬件会连续的写到海量存储器中,只是写一个扇区所用的时间非常少。所以,同时或瞬间写入到数据文件的不同部分成为可能。
SQLite
的原子提交逻辑会使得一个事务中的变化就象同时发生的一样。
事务的原子是
SQLite
的重要特性,即使事务由于操作系统出错或掉电发生中断也能保持其原子性。
本文描述了
SQLite
实现原子操作的技术。
2.0
硬件设定
在这
往篇文章中,我们把海量存储特指定为“硬盘”,即使它可能是flash memory.
我们假定硬盘是以扇区为单位进行整块写入的。我们不能单独修改硬盘的小于扇区的部分。如果需要修改硬盘小于扇区的部分,你也必须整个读入此部分所在扇区,对此扇区进行修改,然后将整个扇区写回硬盘。
在传统的
Spinning disk
中
,
扇区是最小的传输单元
---
无论是读还是写。然而,对于
flash memory
,每次读的最小数目通常都远小于最小写操作数目。
SQLite
只关心写操作的最小数目,因此在本文中,当我们说“扇区”的时候,就是指单次写入的最少字节总数。
SQLite 3.3.14
以前的版本,我们假定任何情况下,一个扇区是
512
字节。这是一个编译时设定的值,而且从没针对更大数进行测试过。当磁盘驱动器内部使用的是以
512
字节为单位的扇区时,
512
字节的假定显得非常合理。然而,现在的磁盘都已经发展到
4k
每扇区了。同样,
flash memory
的扇区大小通常都大于
512
字节。因此,从
3.3.14
版本开始,
SQLite
有一个函数去获取文件系统的扇区真实大小。在当前的实现中
(3.5.0)
,这个函数仍然简单的返回
512—
因为在
win32
及
unix
环境下,没有标准方法去取得扇区的真实大小。但这个方法在人们需要针对他们应用进行调整的时候是非常有意义的。
SQLite
并不
假
定扇区写操作是原子的。然而,我们假定扇区写操作是线性的。所谓“线性”是指,当开始扇区写操作时,硬件从前一个扇区的结束点开始,然后一字节一字节的写
入,直到此扇区的结束点。这个写操作可能是从尾向头写,也可能是从头向尾写。如果在一个扇区写入操作时发生掉电故障,这个扇区可能会一部分已经修改完成,
还有一部分还没来得及进行修改。
SQLite
的关键设定是这样的:如果一个扇区的任何部分发生修改,那么不是它开始的部分发了变化,就是它结束部分发生了变化。所以硬件从来都不会从一个扇区的中间部分开始写入。我们不知道这个假定是否总是真实的,但无论如何,看起来还是蛮合理的。
上段中,
SQLite
并没有假定扇区写操作是原子的。在
SQLite3.5.0
版本中,新增了一个
VFS
(虚拟文件系统)接口。
SQLite
通过
VFS
与实际的文件系统进行交互。
SQLite
已经为
windows
及
unix
编写了一个缺省的
VFS
实现。并且可以让用户在运行时实现一个自定义的
VFS
实现。
VFS
接口有一个方法叫:
xDeviceCharacteristics.
此方法读取实际的文件系统各种特性。
xDeviceCharacteristics
方法可以指明扇区写操作是原子的,如果确实指定扇区写是原子的,
SQLite
是不会放过这等好处的。但在
windows
及
unix
中,缺省
xDeviceCharacteristics
的实现并没有指明扇区写是原子的,所以这些优化通常会忽略掉了。
SQLite
假定操作系统会对写进行缓冲,因此写入请求返回时,有可能数据还没有真实的写入到存储中。
SQLite
同时还假定这种写操作会被操作系统记录。因此,
SQLite
需要在关键点做
"flush"
或
"fsync"
函数调用。
SQLite
假定
flush
或
fsync
在数据没有真实的写入到硬盘之前是不会返回的。不幸的是,我们知道在一些
windows
及
unix
版本中,缺少
flush
或
fsync
的真正实现。这使得
SQLite
在写入一个提交发生掉电故障后数据文件得到损坏。然而,这不要紧,
SQLite
能够做一些测试或补救。
SQLite
假定操作系统会是广告中那样漂亮运行。如果这些都不是问题,那么剩下的只期望你家的电源不要间歇性的休息。
SQLite
假定文件增长方式是指新分配的文件空间,刚分配的时候是随机内容,后来才被填入实际的数据。换而言之,文件先变大,然后再填充其内容。这是一悲观假定,因而
SQLite
不得不做一些额外的操作来防止因断电发生的破坏数据文件
—
发生在文件大小已经增大,而文件内容还没完全填入之间的掉电。
VFS
的
xDeviceCharacteristics
可以指明文件系统是否总是先写入数据然后才更变文件大小的。
(
这就是那个:
SQLITE_IOCAP_SAFE_APPEND
属性,如果你想查看代码的话
)
当
xDeviceCharacteristics
方法指示了文件内容先写入然后才改变文件大小的话,
SQLite
会减少一些相当的数据保护及错误处理过程,这将大大减少一个提交磁盘
IO
操作。然而在当前的版本,
windows
及
unix
的
VFS
实现并没有这样假定。
SQLite
假定文件删除从用户进程角度来讲是原子的。也就说当
SQLite
要
求删除一个文件,也在这删除的过程中间,断电了,一旦电源恢复,只有下列二种情况之一分发生:文件仍然存在,所有内容都没有发生变化;或者文件已经被删除
掉了。如果电源恢复之后,文件只发生了部分删除,或者部分内容发生了变化或清除,或者文件只是清空,那么数据库还有用才怪呢。
SQLite
假定发现或修改由于宇宙射线,热噪声,量子波动,设备驱动
bug
等等其他可能所引发的错误,都由操作系统或硬件来完成。
SQLite
并不为此类问题增加任何数据冗余处理。
SQLite
假定在写入之后去读取所获得的数据,是与写入的数据完全一致的!
3.0
单个文件提交
我们着手观察
SQLite
在针对一个数据库文件时,为保证一个原子提交所采取的步骤。关于在多个数据库文件之间为防止电源故障损坏数据库及保证提交的原子性所采用的技术及具体的文件格式在下一节进行讨论。
3.1
实始状态
当一个数据库第一次打开时计算机的状态示意图如右图所示。图中最右边(
”Disk”
标注)表示保存在存储设备中的内容。
每个方框代表一个扇区。蓝色的块表示这个扇区保存了原始资料。图中中间区域是操作系统的磁盘缓冲区。在我们的案例开始的时候,这些缓存是还没有被使用
—
因此这些方框是空白的。图中左边区域显示
SQLite
用户进程的内存。因为这个数据库联接刚刚打开,所以还没有任何数据记录被读入,所以这些内存也是空的。
3.2
申请一个共享锁
SQLite
在可以写数据库之前,它必须先读这个数据库,看它是否已经存在了。即使只是只是增加添加新的数据,
SQLit
仍然必须从
sqlite_master
表中读取数据库格式,这样才知道如何分析
INSERT
语句,知道在哪儿保存新的信息。
为了从数据库文件读取,第一步是获得一个数据库文件的共享锁。一个“共享”锁允许多个数据库联接在同一时刻从这个数据库文件中读取信息。
“共享”锁将不允许其他联接针对此数据库进行写操作。这是必然的,如果一个联接在向数据库写入数据的同时,我们去读到信息,也可能读到的一部分数据是修改之前的,而另一部分数据是修改之后的。这将使得另外联接的修改操作看起来是非原子的。
请注意共享锁只是针对操作系统的磁盘缓存,并非磁盘本身。通常文件锁只是操作系统内核的一些标识(详情要根据具体的操作系统)。因此,锁会立即消失一旦操作系统崩溃或者停电。当然创建该锁的进程消失,该锁也会随之而去。
3.3
从数据库里面读取信息
当
共享锁取得之后,我们就可以开始从数据库文件中读取信息了。在当前环节,我们已经假定了系统缓存是空的,所以信息必须首先从读硬盘读取到系统缓存中去,然
后从系统缓存中传递到用户空间。针对之后的读取,部分或者全部数据都可能可以从操作系统缓存中取得,所以只需要传递到用户空间即可。
一般的,数据库文件只有部分被读取。这个例子中,
8
页中只有
3
页被读取。一个典型应用中,一个数据库文件拥有成千上万页,一个查询通常读取到的页码数量只占总数一个很小的百分比。
3.4
申请一个
Reserved Lock
在修改一个数据库之前,
SQLite
首先得拥有一个针对数据库文件的“
Reserved
”
锁。
Reserved
锁类似于共享锁,它们都允许其他数据库联接读取信息。单个
Reserved
锁能够与其他进程的多个共享锁一起协作。然后一个数据库文件同时只能存在一个
Reserved
。因此只能有一个进程在某一时刻尝试去写一个数据库文件。
Reserved
锁的存在是宣告一个进程将打算去更新数据库文件,但还没有开始。因为还没有开始修改,因此其他进程可以读取数据,其他进程不应该去尝试修改该数据库。
3.5
生成一个回滚日志文件
在修改数据库文件之前,
SQLite
会生成一个单独的回滚日志文件,并在其中写进将会被修改的页的原始数据。回滚日志文件意味它将包含了所有可以将数据库文件恢复到原始状态的数据。
回滚日志文件有一个小的头部(图中绿色标记部分)记录了数据库文件的原始大小。因此,如果一旦即使数据库文件变大,我们还是会知道它原始大小。数据库文件中被修改的页码及他们的内容都被写进了回滚日志文件中。
当一个新文件刚被创建,大部分的桌面操作系统(
windows,linux,macOSX
)实际并不会马上写入数据到硬盘。此文件还只是存在于操作系统磁盘缓存中。这个文件还不会立即写到存储设备中,一般都会有一些延迟,或者到操作系统相当空闲的时候。用户的对于文件生成感觉是要远远快(先)于其真实的发生磁盘
I/O
操作。右图中我们用图例说明了这一点,当新的回滚日志文件创建之后,它还只是出现在操作系统磁盘缓存之中,还没真实在写入到硬盘之上。
3.6
修改用户进程中的数据页
当原始的数据已经被保存到回滚日志文件中之后,用户内存的数据就可以被修改了。任何一个数据库联接都有其他私有用户内存空间,所以用户内存空间发生的变化只有当前数据库联接才可见。
其他数据库联接仍然可以读取那些存在于操作系统磁盘缓存中还没有被修改的数据。所以即使一个联接忙于某些修改,其他进程还可以读取原始数据到它们各自的空间中去
3.7
刷新回滚日志文件到存储设备中
接下来的步骤是将回滚日志文件刷新到硬盘中去。接下来我们会看到,这是一个紧要步骤用来保证我们可以从突然掉电中救回数据。这个步骤将要花费大量的时间
—
因为通常写入到硬盘是一个耗时操作。
.
这个步骤通常要比简单的直接刷新这个回滚文件到硬盘要复杂一些。在大部分的操作系统中,二个单独的
flush
是必须的。第一个
flush
处理日志文件的内容部分。接下来,将日志文件的页码总数写入到日志文件头部,然后将日志头部
flsuh
到硬盘中。至少为什么我们要做一个头部修改及做一个额外的
flush
操作的原因我们会在后面的章节解释。
3.8
获得一个独享锁
在修改数据库文件本身之前,我们必须取得一个针对此数据库文件的独享锁。取得此锁的过程是分二步走的。首先
SQLite
取得一个“临界”锁,然后将此锁提升成一个独享锁。
一
个临界锁允许其他所有已经取得一个共享锁的进程从数据库文件中继续读取数据。但是它会阻止新的共享锁的生成。也就说,临界锁将会防止因大量连续的读操作而
无法获得写入的机会。这些读取者可能有一打,也可能上百,甚至于上千。任何一个读取者在开始读取之前都要申请一个共享锁,然后开始读取它需要的数据,然后
释放共享锁。然而存在这样一种可能:如果有太多的进程来读取同一个数据文件,在老的进程释放它的共享锁之前总是会有新的进程申请共享锁,因此不会存在某一
时刻这个数据库文件上没有共享锁的存在,也因此写入者不会拥有取得一个独享锁的机会。临界锁的概念可以使现有的读
分享到:
相关推荐
SQLite的原子提交原理SQLite的原子提交原理SQLite的原子提交原理SQLite的原子提交原理SQLite的原子提交原理SQLite的原子提交原理SQLite的原子提交原理SQLite的原子提交原理SQLite的原子提交原理SQLite的原子提交原理
嵌入式数据库SQLite的原理与应用
SQLite,轻量级数据库原理分析及其开发应用,数据库
在Java下连接SQLite数据库 一、下载SQLite数据库的JDBC:http://www.zentus.com/sqlitejdbc/ 二、将下载到的包解压后得到jar包放到%JAVA_HOME%\lib下,并且将其添加到ClassPath系统环境变量中。一定要保证在类路径...
SQLite.Interop.dll for sqlite expert professional 4.2 用与SQLite Expert打开本地已加密的数据库的插件。适用32位和64位。 1.直接将下载的文件复制到SQLite Expert安装路径(如:c:\Program Files (x86)\SQLite ...
一些小型的应用程序需要使用到数据库,sqlite可以说是最好的选择。这里个人整理了一般简单通用的操作类SQLiteHelper
SQLite 安装包 sqlite3 及sqlite数据库jdbc jar包 2分不能再少了
嵌入式数据库SQLite的原理与应用
网上很难下载到,分享给需要的:sqlite3odbc.dll 属性 文件版本 1.34455.0,0 产品名称 ODBC Driver for SQLite3 3.43.2 产品版本 0.99991 版权 Copyriglt @2004-2023 ,de> 大小 942 KB 修改日期 2023-10-23 21:50 ...
一款绿色的SQLite文件查看编辑器,支持SQL语句,直接解压后运行文件夹里面的SQLiteDatabaseBrowserPortable.exe即可运行,打开文件时文件类型选择所有类型,内附简单的使用方法,配合i4等苹果手机助手可以方便得将...
svn无法清理 上传 下载 的情况清理本地svn库
gcc使用的sqlite3数据库,sqlite3.a ,可以用于Dev-C++,也可以用于Windows下使用gcc编译的程序,sqlite版本是:3270200,还包含了sqlite3.lib库文件,可以用于visual studio。
SQLite.Interop.dll for sqlite expert professional 4.2 用与SQLite Expert打开本地已加密的数据库的插件。1.将SQLite.Interop.dll x86拷贝到SQLite Expert安装路径(如:x:\Program Files (x86)\SQLite Expert\...
使用sqlite3所需要用到的库文件等
文档包含sqlite源码,源码分析,以及使用教程
sqlite常量的定义: const SQLITE_OK = 0; 返回成功 SQLITE_ERROR = 1; SQL错误或错误的数据库 SQLITE_INTERNAL = 2; An internal logic error in SQLite SQLITE_PERM = 3; 拒绝访问 SQLITE_ABORT = 4; 回调...
qlite数据库远程管理器,只要在SQLITE数据文件所在目录下运行SQLITE3SERVER服务器程序就可以在于之联网的机器管理SQLITE数据库。可同时管理多台SQLITE数据库服务器。 实现的功能如下: 1、可列出远程SQLITE数据库...
Qt中SQlite数据库加密SqliteCipher 操作实例,打开多个数据库,附着数据库跨库查询, 对应说明:https://blog.csdn.net/woguanni/article/details/120524602
Android 数据库SQLite 详解,简单学好SQLite
基于sqlite3的加密版本的数据库动态链接库,附lib和.h头文件,新测试可用。可设置密码和修改密码,加密后的数据库文件是无法被打开的。官方提供的免费版本sqlite3都是没有加密的,官方收费的很贵哦,有需要的朋友...