Isolation In SQLite
Isolation In SQLite
数据库的“隔离”属性确定一个操作对数据库所做的更改何时对其他并发操作可见。
数据库连接之间的隔离
如果使用两个不同的数据库连接(通过对sqlite3_open()的单独调用返回两个不同的sqlite3对象)并且两个数据库连接没有共享缓存来读取和写入相同的数据库,则读者只能看到完整的提交来自作者的交易。作者的部分变更未被提交,读者是看不见的。无论两个数据库连接是在同一个线程中,在同一个进程的不同线程中,还是在不同的进程中,情况都是如此。这是SQL数据库系统的常见和预期行为。
只要read_uncommitted编译指示保持关闭状态,前面的段落也是如此(共享缓存模式下单独的数据库连接是相互隔离的)。read_uncommitted编译指示在默认情况下是关闭的,所以如果应用程序无法开启它,它将保持关闭状态。因此,除非使用read_uncommitted编译指示来更改缺省行为,否则一个数据库连接所做的更改在共享相同高速缓存的不同数据库连接上的读者不可见,直到编写者提交其事务。
如果两个数据库连接共享相同的缓存并且读取器已启用read_uncommitted编译指示,则读取器将能够在编写器事务提交之前看到编写器所做的更改。共享缓存模式和read_uncommitted pragma的组合使用是一个数据库连接可以在不同数据库连接上看到未提交更改的唯一方式。在所有其他情况下,单独的数据库连接完全相互隔离。
除了启用PRAGMA read_uncommitted的共享缓存数据库连接外,SQLite中的所有事务都显示“可序列化”隔离。SQLite通过实际序列化写操作来实现可序列化的事务。一次只能有一个SQLite数据库的作者。可以同时打开多个数据库连接,并且所有这些数据库连接都可以写入数据库文件,但它们必须轮流。SQLite使用锁来自动序列化写操作; 这不是使用SQLite的应用程序需要担心的问题。
隔离和并发
SQLite使用临时日志文件实现隔离和并发控制(和原子性),这些日志文件与数据库文件在同一目录中。有两种主要的“日记模式”。较早的“回滚模式”对应于对journal_mode编译指示使用“DELETE”,“PERSIST”或“TRUNCATE”选项。在回滚模式下,更改直接写入数据库文件,同时构建单独的回滚日志文件,在事务回滚时能够将数据库恢复到其原始状态。回滚模式(特别是DELETE模式,表示在每次事务结束时从磁盘删除回滚日志)是当前的默认行为。
从版本3.7.0(2010-07-21)开始,SQLite也支持“WAL模式”。在WAL模式下,更改不会写入原始数据库文件。相反,更改会进入单独的“预写日志”或“WAL”文件。之后,在事务提交之后,这些更改将从称为“检查点”的操作中从WAL文件移回到原始数据库中。通过运行“PRAGMA journal_mode = WAL”启用WAL模式。
在回滚模式下,SQLite通过锁定数据库文件实现隔离,并在每个写入事务正在进行时防止其他数据库连接的任何读取。在写入开始时,读者可以在任何内容刷新到磁盘之前以及所有更改仍保留在写入器的私有内存空间中之前处于活动状态。但是,在对磁盘上的数据库文件进行任何更改之前,必须(暂时)驱逐所有读取器,以使写入者可独占访问数据库文件。因此,由于在交易写入磁盘时被锁定在数据库之外,因此禁止读者看到不完整的交易。只有在事务完全写入并同步到磁盘并且提交之后,才允许读取器回到数据库中。
WAL模式允许同时读者和作者。它可以这样做,因为更改不会覆盖原始数据库文件,而是进入单独的预写日志文件。这意味着读者可以在作者追加预写日志的同时,继续从原始数据库文件中读取旧的,原始的,未改变的内容。在WAL模式下,SQLite展示“快照隔离”。当读取事务开始时,该读取器继续看到数据库文件的不变“快照”,因为它在读取事务开始时刻存在。在读取事务处于活动状态时提交的任何写入事务对于读取事务而言仍然不可见,因为读者在前一时刻看到数据库文件的快照。
一个例子:假设有两个数据库连接X和Y.X使用BEGIN和一个或多个SELECT语句启动一个读事务。然后Y出现并运行UPDATE语句来修改数据库。X随后可以对Y修改的记录执行SELECT操作,但X会看到较旧的未修改条目,因为X的更改在X持有读操作时对X不可见。如果X想要查看Y所做的更改,则X必须结束其读取事务并启动一个新事务(通过运行COMMIT,然后再运行另一个BEGIN)。
另一个例子:X使用BEGIN和SELECT启动读事务,然后Y使用UPDATE更改数据库。然后X尝试使用UPDATE更改数据库。X将事务从读事务升级到写事务的尝试失败,并出现SQLITE_BUSY_SNAPSHOT错误,因为X查看的数据库快照不再是数据库的最新版本。如果允许X写入,则会分叉数据库文件的历史记录,这是SQLite不支持的。为了让X写入数据库,它必须首先释放它的快照(例如使用ROLLBACK),然后用随后的BEGIN启动一个新的事务。
如果X启动的事务最初只能读取,但X知道它最终会写入,并且不希望因可能的SQLITE_BUSY_SNAPSHOT错误而产生错误,这些错误是由于另一个连接在其前面跳转而产生的,则X可以发出BEGIN IMMEDIATE以启动它的交易,而不仅仅是一个普通的BEGIN。BEGIN IMMEDIATE命令继续并开始写入事务,从而阻止所有其他写入者。如果BEGIN IMMEDIATE操作成功,则该事务中的后续操作将不会因SQLITE_BUSY错误而失败。
在同一数据库连接上没有隔离操作
SQLite提供了独立数据库连接操作之间的隔离。但是,在同一数据库连接内发生的操作之间没有隔离。
换句话说,如果X使用BEGIN IMMEDIATE开始写事务,然后发出一个或多个UPDATE,DELETE和/或INSERT语句,那么这些更改对于在数据库连接X中计算的后续SELECT语句是可见的。SELECT语句数据库连接Y在X事务提交之前不会显示任何更改。但X中的SELECT语句将显示提交之前的更改。
在单个数据库连接X中,SELECT语句总是可以看到在SELECT语句开始之前完成的对数据库的所有更改,无论是已提交还是未提交。SELECT语句显然没有看到SELECT语句完成后发生的任何更改。但是在SELECT语句运行时发生的变化呢?如果启动一个SELECT语句并且sqlite3_step()接口执行了大约一半的输出,那么一些UPDATE语句由修改SELECT语句正在读取的表的应用程序运行,然后再调用sqlite3_step()完成SELECT语句?SELECT语句的后续步骤是否会查看UPDATE所做的更改?答案是这种行为是不确定的。尤其是,不管SELECT语句是否看到并发更改,都取决于SQLite的哪个版本正在运行,数据库文件的架构,是否运行了ANALYZE,以及查询的详细信息。在某些情况下,它也可能取决于数据库文件的内容。没有好的方法来知道SELECT语句是否会在SELECT语句启动后看到由同一个数据库连接对数据库所做的更改。因此,开发人员应该努力避免编写对这种情况下会发生什么的假设的应用程序。没有好的方法来知道SELECT语句是否会在SELECT语句启动后看到由同一个数据库连接对数据库所做的更改。因此,开发人员应该努力避免编写对这种情况下会发生什么的假设的应用程序。没有好的方法来知道SELECT语句是否会在SELECT语句启动后看到由同一个数据库连接对数据库所做的更改。因此,开发人员应该努力避免编写对这种情况下会发生什么的假设的应用程序。
如果应用程序在单个表上发出SELECT语句,如“ SELECT rowid,* FROM table WHERE ...
应用程序可以更新当前行或任何先前的行,尽管这样做可能会导致该行在后续的sqlite3_step()中重新出现。只要应用程序准备处理这些歧义,操作本身就是安全的,不会损害数据库文件。
为了前两段的目的,具有相同共享高速缓存并启用了PRAGMA read_uncommitted的两个数据库连接被认为是相同的数据库连接。
概要
- SQLite中的事务是SERIALIZABLE。
- 在提交之前,在一个数据库连接中进行的更改对所有其他数据库连接是不可见的。
- 查询查看在查询开始之前在同一个数据库连接上完成的所有更改,而不管这些更改是否已提交。
- 如果在查询开始运行之后但在查询完成之前在同一数据库连接上发生更改,则不确定查询是否会看到这些更改。
- 如果在查询开始运行之后但在查询完成之前在同一数据库连接上发生更改,则该查询可能会多次返回一个更改的行,或者它可能会返回以前删除的行。
- 就前四项而言,使用相同共享高速缓存并启用PRAGMA read_uncommitted的两个数据库连接被认为是相同的数据库连接,而不是单独的数据库连接。
SQLite在公共领域。