SQLite的隔离性

隔离在SQLite

官方地址

数据库的“隔离”属性决定何时由一个操作对数据库进行的更改对其他并发操作可见。

数据库连接之间的隔离

如果使用两个不同的数据库连接(通过对sqlite3_open()的单独调用返回的两个不同的sqlite3)对象,并且两个数据库连接没有共享缓存,那么读取器只能看到写入器提交的完整事务。写入者未作出的部分修改,读者是看不见的。无论这两个数据库连接是在同一个线程中,在同一进程的不同线程中,还是在不同的进程中,都是如此。这是SQL数据库系统的常见和预期行为。

之前的段落也是如此(单独的数据库连接彼此隔离)在共享缓存模式只要read_uncommitted编译指示仍然关闭。read_uncommitted编译指示在默认情况下是关闭的,所以如果应用程序没有打开它,它将保持关闭。因此,除非read_uncommitted编译指示用于改变默认的行为,在共享同一缓存的不同数据库连接上的读者看不到一个数据库连接所做的更改,直到写入器提交事务为止。

如果两个数据库连接共享同一个缓存,并且阅读器已经启用了read_uncommitted pragma,那么阅读器将能够看到写入器在提交写入器事务之前所做的更改。共享缓存模式和read_uncommitted pragma的结合使用是一个数据库连接在另一个数据库连接上看到未提交更改的唯一方式。在所有其他情况下,独立的数据库连接彼此完全隔离。

除了使用PRAGMA read_uncommitted共享缓存数据库连接的情况外,SQLite中的所有事务都显示“serializable”隔离。SQLite通过实际串行化写操作来实现串行化事务。SQLite数据库一次只能有一个写入器。可以同时打开多个数据库连接,所有这些数据库连接都可以写入数据库文件,但它们必须轮流打开。SQLite使用锁来自动串行化写操作;这不是使用SQLite的应用程序需要担心的问题。

隔离性和并发性

SQLite通过使用瞬态日志文件(和数据库文件在相同目录下)来实现隔离性和并发性控制(以及原子性)。有两个主要的“日志模式”。旧的“回滚模式”对应于使用journal_mode pragma的“删除”、“持久化”或“截断”选项。在回滚模式中,更改是直接写入到数据库文件的。与此同时,还构造了一个单独的回滚日志文件,如果事务回滚,该文件可以将数据库恢复到原来的状态。回滚模式(尤其是DELETE模式,意味着回滚日志在每次事务完成后就从磁盘上删除)是当前的模式行为。

从3.7.0(2010-07-21)版本开始,SQLite还支持“WAL模式”。在WAL模式中,更改不会写入到原始数据库文件。相反地,更改是写入到单独的“write ahead log”或者“WAL”文件。之后,当事务提交后,这些更改将用一种称为“checkpoint”的操作从WAL文件移回到原始数据库。WAL模式通过运行”PRAGMA journal_mode=WAL“生效。

在回滚模式中,当有写事务进行事,SQLite通过锁住数据库文件,阻止任何其他数据库连接的读取,来实现隔离性。读写器可以在写操作开始时处于活动状态,在任何内容被刷新到磁盘之前,并且所有更改仍然保存在写入器的私有内存空间中。但是在任何更改写入到磁盘上的数据库文件之前,为了给写入者排他地访问数据库,所有的读取者必须(临时地)被禁止。因此,当事务被写入磁盘时,由于被锁定在数据库之外,禁止读取者查看不完整的事务。只有在事务被完整写入并且同步到磁盘,而且提交后,读取者才能被允许返回数据库。因此,读取者不会看到部分写入的更改。

WAL模式准许同时有读取者和写入者。这么做是因为更改不会复写原始数据库文件,而是写入到单独的write-ahead日志文件。这意味着读取者可以继续从原始数据库文件读取旧的,原始的,未修改的内容,同时写入者也在写write-ahead日志。在WAL模式,SQLite展现了“快照隔离性”。当一个读取事务开启时,读取者看到的依然是未改变的数据库文件“快照”,就好像读取事务开启时就存在。在读取事务处于活动状态时提交的任何写事务对读取事务仍然是不可见的,因为读者看到的是前面时刻的数据库文件快照。

举一个例子:假设有两个数据库连接X,Y。X使用BEGIN,随后一连串的SELECT语句,来启动一个读取事务。然后Y随后来,运行UPDATE语句修改数据库。X可以随后执行SELECT来查询Y修改的记录,但是X只能看见以前未修改的条目,因为当X持有一个读取时区时,Y的更改对于X来说完全不可见。如果X想看见Y执行的更改,那么X就必须结束读取事务,然后开启一个新的读取事务(通过在另外一个BEGIN后运行COMMIT)。

另外一个例子:X使用BEINGSELECT启动一个读取事务,然后Y使用UPDATE对数据库做了些更改。然后X也尝试使用UPDATE对数据库做更改。X试图把它的事务从一个读取事务升级到写事务,会以SQLITE_BUSY_SNAPSHOT错误而失败,因为被X看到的数据库快照不再是数据库的最新版本。如果X被允许写入,那么它会fork数据库的历史,但这个操作的有些事情不被SQLite支持。为了X能向数据库写入,X必须首先释放它的快照,随后用BEGIN启动一个新事务。

如果X开始一个事务,最初只会读但是X知道它最终会想写,不希望出现的问题与可能的SQLITE_BUSY_SNAPSHOT错误因为之前另一个连接跳线,那么X问题可以立即开始其事务,而不只是一个普通的BEGIN。BEGIN IMMEDIATE命令会提前并且开启一个写事务,因而会阻塞所有其他的写入者。如果BEGIN IMMEDIATE操作成功,在这个事务中的后续操作都不会因为SQLITE_BUSY错误而失败。

在相同数据库连接上操作之前无隔离性

SQLite在单独数据连接里的操作之间提供了隔离性。可是,相同数据库连接里的操作是没有隔离性的。

换句话说,如果X开始写事务使用BEGIN IMMEDIATE然后问题一个或多个UPDATEDELETE、和/或INSERT语句,那么这些变化可见后续评估的SELECT语句的数据库连接X Y SELECT语句在不同的数据库连接将显示没有变化,直到X事务提交。但是X中的SELECT语句将显示提交之前的更改。

在单个数据库连接X里,SELECT语句总是能看到SELECT语句之前完成的所有对于数据库的更改,无论提交还是未提交。当SELECT语句完成后,SELECT语句明显不会看到任何改变。但是当SELECT语句运行时发生更改了会怎么样了?如果一个SELECT语句被启动,sqlite3_step()接口执行到一半,然后由修改SELECT语句正在读取的表的应用程序运行一些UPDATE语句,然后对sqlite3_step()进行更多的调用以完成SELECT语句,会发生什么情况?SELECT语句的后面步骤会看到更新所做的更改吗?答案是行为未定义。特别是,无论SELECT语句看到并发更改的改变依赖于SQLite正在运行哪个版本,数据库文件模式 ,无论ANALYZE是否运行,以及查询的细节。在某些情况下,可能也依赖于数据库文件内容。没有一个好办法知道当SELECT语句已经开始后,SELECT语句看到的更改是否是由于相同数据库连接做的。因此,
开发人员应该努力避免编写假设在这种情况下会发生什么的应用程序。

如果一个应用程序在单表上执行像“SELECT rowid,* FROM table WHERE…”并且使用sqlite3 step()启动单步调试输出语句,并且检查每一行,那么对于应用程序删除当前行或者使用“DELETE FROM table WHERE rowid=?”删除之前的任何行,这种操作都是安全的。对于要删除在随后查询期望出现的一行而现在没有出现的应用程序也是安全的。但是,如果删除了未来的行,那么可能会出现在随后的sqlite3_step()之后,甚至在据说已经删除之后。可能也没有影响。这种行为是未定义的。当SELECT语句正在运行时,应用程序还可以INSERT新行到数据库表中,但是无论新行是否出现,在随后的查询sqlite3 step()是未定义的。并且应用程序可以UPDATE当前行或者之前的行,苏日安这么做可能会造成在随后的sqlite3_step()行重复出现。只要应用程序准备好处理这些含糊不清的问题,操作本身就是安全的,不会损害数据库文件。

对于之前两个段落的目录,两个有相同共享缓存,并且开启了PRAGMA read_uncommitted的数据库连接,被认为是相同的数据库连接。

总结

  1. SQLite的事务是串行化的。
  2. 在一个数据库连接里做的更改在提交之前,对于其他数据库连接是不可见的。
  3. 一个查询能看到的在查询开始之前在相同的数据库连接完成的所有更改,无论这些更改是否已经提交。
  4. 在查询开始运行后,如果在相同数据库连接发生更改,但是又在查询之前,那么无论查询能否看到这些变更,这种行为都是未定义的。
  5. 在查询开始运行后,但在查询完成前,如果在相同数据库连接发生更改,那么查询可能不止一次地返回已更改的行,也可能返回先前已删除的行。
  6. 就前四项而言,两个使用相同共享缓存和启动PRAGMA read_uncommitted的数据库连接被认为一个相同的数据库连接,而不是单独的数据库连接。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值