在SQLite中,锁是一个重要而又基本的概念。
与锁密切相关的一个基本概念是事务。首先,为了更好的理解锁的概念,我们简单回顾一下什么是事务?
事务定义了一组SQL命令的边界,这组命令或者作为一个整体被全部执行,或者都不执行,这被称为数据库完整性的原子性原则,关于事务的典型例子就是银行转帐。
事务由3个命令控制:begin、commit和rollback。begin之后和commit之前的所有操作都可以被取消。commit命令提交所执行的所有操作。与之类似,rollback还原begin之后的所有操作。
那么在数据库中,事务和锁在查询处理中密切相关。查询总是在事务内执行,事务又涉及到锁,所以如果锁控制不当,会产生很多问题。接下来,让我们谈谈SQLite中的锁。
SQLite采用粗粒度的锁。当一个连接要写数据库时,所有其他的连接被锁住,直到写连接结束它的事务。SQLite有一个加锁表,用来帮助不同的写数据库都能够在最后一刻再加锁,以保证最大的并发性。
SQLite有5种不同的锁状态:未锁定(unlocked)、共享(shared)、保留(reserved)、待定(pending)和排它(exclusive)。每个数据库连接在同一时刻只能处于其中的一个状态(见下图),每种状态(未锁定(unlocked)状态除外)都有一种锁与之对应。
图中所示,所有的事务都是从未锁定(unlocked)、保留(reserved)或排他锁开始的。默认情况下,一切都从未锁定(unlocked)开始。白色的锁状态----未锁定(unlocked)、待定(pending)、共享(shared)和保留(reserved),全部都可以在同一时间同一数据库的不同连接种存在。不过,从灰色的待定锁开始,限制就多了。灰色的待定状态代表锁正在被某个连接拥有,即某个想要获取独占锁的写操作。与此对应,白色的待定状态表示连接获取和释放共享锁的途径。尽管有这些不同的锁状态,但是所有的SQLite事务都可以归结为两种类型之一:读事务和写事务。这也是图种描绘的最终内容:读操作、写操作以及它们如何在一起工作。
下面,我们就从读事务和写事务两个方面讨论。
-
读事务
从select语句的锁进程开始,它的路径比较简单。执行select语句的连接启动事务,从未锁定转到共享锁,提交之后回到未锁定状态,操作结束。
这里我们提出两个疑问:
-
执行两个语句会发生什么?
-
它们的锁路径是什么?
这两个疑问取决于是否运行在自动提交模式下。
考虑如下实例:
db = open('worlds.db') db.exec('begin') db.exec('select * from sometables') db.exec('select * from sometables') db.exec('commit') db.close()
这里有明确的begin命令开始,两个select命令在一个事务种执行,因此它们在同一个共享状态下执行。第一个exec()运行,让连接进入共享状态;然后第二个exec()运行;最后,手动提交命令,让连接从共享状态回到未锁定状态。代码的锁路径如下所示:
UNLOCKED->PENDING->SHARED->UNLOCKED
现在考虑没有begin和commit命令的情况。两个select命令运行在自动提交模式下。因此,它们各自经历完整的路径。现在,代码的锁路径如下所示:
UNLOCKED->PENDING->SHARED->UNLOCKED->PENDING->SHARED->UNLOCKED
由于该代码只是读取数据,因此,可能不会产生多大差异,但在自动提交模式下的确会两次锁定文件,而不是其他方式中那样只锁定一次。这样的做法,你可能会发现,可以在两个select exec()调用之间插入修改数据库的操作,因此,这样做无法确保两个命令返回相同的结果。相反,如果有begin...commit命令,可以保证它们的结果完全相同。
-
写事务
下面说说数据库写操作,例如update语句。首先,连接必须遵从与select相同的路径,先到共享状态。所有的操作----写操作或读操作----都必须经历;未知锁->待定锁->共享锁。
保留状态(reserved)
连接尝试向数据库写入内容时,必须从共享锁转换到保留锁。如果它获得保留锁,则准备好开始进行数据修改。即使连接真的不能在此时修改数据库,它也可以将修改内容存储在本地pager内的内存缓存中,也就是上一篇中提到的页面缓存。
当连接进入保留状态时,pager初始化回滚日志。回滚日志是一个文件,用于回滚和故障恢复。具体地说,它拥有将数据库还原到事务开始之前的原来状体啊的数据库页。当B-tree修改页时,pager将这些数据库页都存放到日志文件。比如说,对于update修改的每条记录,页面获取与原始记录相关的数据库页,并将它们复制到日志中。日志就拥有事务开始之前的一些数据库内容。因此,要撤销事务时,pager只是简单地将日志文件中的内容复制回数据库中。这样,数据库就还原到事务开始前的状态。
保留状态下,pager实际上管理三种页:已修改页、未修改页和日志页。已修改页是包含B-tree已改变记录的页,这些页存储在页缓存中。未修改页是B-tree读取但并未改变的页,它们是诸如select命令之类的结果。最后是日志页,它就是已修改页的原始版本。日志页不会存储在页面缓存中,但B-tree修改前会将其写入日志。
因为页面缓存,写操作连接的确可以在保留状态完成实际的工作,而不用干扰其他(读操作)连接。因此,SQLite可以有效地让多个读操作和一个写操作同一时间在同一数据库中工作。唯一需要注意的是,写操作连接要将所做的修改存储在页面缓存中,而不是数据库文件中。此外要注意,给定数据库同一时间只能有一个保留或独占连接----但是多个读操作可以和一个写操作并存。
待定状态(pending)
当连接完成update操作,并提交事务时,pager开始进入独占状态的过程。一旦获得待定锁,并继续持有该锁,阻止其他连接获取待定锁。这里的待定锁也被称为网关锁,因为写操作继续持有待定锁,其他连接无法从未锁定转换到共享状态,结果是没有可以进入数据库的新连接:没有新的读操作,没有新的写操作。挂起状态实际上是损耗阶段。写操作保证它可以排队等待数据库----只要每个人都守规矩且行为得当,最终都可以获得独占锁。只有其他已有共享锁的连接可以继续正常工作。待定状态下,写操作等待这些连接完成并释放其共享锁。
独占状态(exclusive)
独占状态中,主要工作是将修改的页从页面缓存刷新到数据库文件。这时要慎重,因为pager开始实际修改数据库了。
在pager开始写入修改的页前,首先要处理日志。它会检查日志的完整内容是否已写入磁盘。这种情况下,依然很可能,即使pager已将页写入日志文件,但是操作系统具有很多缓存,因此,可能还有一部分内容在操作系统的缓存中(也许不是全部)。
将日志提交到磁盘非常重要,因为如果程序或系统在pager写入数据库文件时崩溃,日志是日后还原数据库文件的唯一方式。如果在系统崩溃前,日志页没有完整地写入磁盘,那么数据库就无法还原到其原来的状态,因为内存中的日志页在系统崩溃时丢失了。这种情况下,如果运气好的话,还有一个处于不一致状态的数据库,如果不走运的话,也许数据库也损坏了。
一旦处理完日志,pager就可以将所有已修改的页复制到数据库文件。下一步做什么取决于事务模式。比如此处说的情况,事务自动提交,然后pager清理日志,清除页缓存,从独占锁回到未锁定状态。如果该事务未提交,pager继续持有独占锁,日志继续发挥作用,直到发出COMMIT或者ROLLBACK命令。
至此,以读事务和写事务为两大方面,对锁的5种状态之间的转换以及所起到的作用,分别做了举例和总结。对于SQLite中5种锁的状态的转换,仅仅是需要了解的基本概念,相关的知识还有很多,比如自动提交模式下对于效率有什么影响?页面缓存的大小对于保留锁状态过渡到独占锁状态的影响?等等还有很多其他方面需要了解,这里只是做了基本介绍。
相关推荐
SQLite是文件级别的数据库,其锁也是文件级别的:多个线程可以同时读,但是同时只能有一个线程写。Android提供了SqliteOpenHelper类,加入Java的锁机制以便调用。但在C#中未提供类似功能。 作者利用读写锁...
在SQLite中,锁和并发控制是数据库管理系统中关键的组成部分,它们确保了数据的一致性和完整性。SQLite使用了一种称为pager_module的模块来处理这些任务,该模块遵循ACID原则,即原子性、一致性、隔离性和持久性。在...
在Qt框架中,SQLite数据库是常用于存储应用程序数据的轻量级数据库引擎。然而,当在多线程环境中使用SQLite时,需要注意一些关键问题以确保数据的安全性和一致性。以下是四个重要的考虑因素: 1. **线程安全**: ...
一、SQLite锁机制 SQLite采用行级锁定策略,但在某些情况下会升级为表级锁定。在读写操作时,SQLite会自动进行加锁,确保数据的一致性和完整性。以下是一些基本的锁类型: 1. **Shared Lock(共享锁)**:多个读...
SQLite是一款轻量级的关系型数据库管理系统,特别适合用于嵌入式设备和移动应用中。本指南详细介绍了从编译准备到最终应用开发的全过程。 #### 二、准备工作 1. **下载SQLite源码** 首先,需要从官方网站...
在SQLITE数据库中,UPDATE操作可能会遇到性能问题,导致更新速度缓慢。这通常是由于多种因素引起的,包括但不限于索引缺失、大数据量、触发器、事务处理、锁竞争以及查询优化等。下面我们将深入探讨这些因素,并提供...
在VB6.0中操作SQLite数据库,是一种将轻量级、高性能的SQLite数据库与传统的Visual Basic编程环境相结合的方法。SQLite是一种自包含、无服务器、零配置、事务性的SQL数据库引擎,广泛应用于移动设备、嵌入式系统以及...
这个项目的源代码可能包含了如何在C#中使用多线程和同步锁与SQLite交互的示例。通常,它会展示如何在多个线程中并行执行读写操作,以及如何使用`lock`或其他同步原语来保护对数据库的访问。测试部分可能会比较不同...
我们可以得知SQLite是文件级别的锁:多个线程可以同时读,但是同时只能有一个线程写。Android提供了SqliteOpenHelper类,加入Java的锁机制以便调用。如果多线程同时读写(这里的指不同的线程用使用的是不同的Helper...
在Android开发中,SQLite是广泛使用的轻量级数据库,尤其适用于处理应用程序中的小规模数据存储需求。下面我们将深入探讨SQLiteManager的使用以及SQLite在Android开发中的应用。 SQLiteManager的主要特点和功能: ...
在实际操作中,使用 SQLite3 的开发者可能需要通过 SVN 将数据库文件(通常是 `.db` 文件)添加到版本控制系统中。这样,团队成员就可以同步数据库的结构和数据,确保每个人都在相同的环境中工作。然而,需要注意的...
综上所述,多进程写SQLite数据库的互斥解决方案主要是通过进程间锁(如`fcntl`或`multiprocessing.Lock()`)或线程间锁(如`threading.Lock()`)来实现。这种策略可以有效避免并发写入导致的数据冲突,保证数据库的...
3. 高并发支持不足:仅提供粗粒度的读写锁,不支持行级锁,导致在高并发情况下性能受限。 SQLite的特点可以总结为: 1. 一致性的文件格式:SQLite文件格式统一,便于数据移植,提供了高效的索引和事务处理,支持...
1. **数据库文件检查**:首先,找到本地svn仓库中的SQLite3数据库文件,通常是`.svn/wc.db`。确认文件是否完整,无损坏情况。 2. **数据库日志清理**:SQLite3在处理事务时会产生wal(Write-Ahead Logging)日志...
1. **SQLite数据库**: SQLite是一种开源、轻量级的关系型数据库管理系统,不需要独立服务器进程,可以直接嵌入到应用程序中,支持多种操作系统,如Windows、Linux、Mac OS X等。它的特点是小巧、高效且高度移植性。 ...
在标题中提到的“SQLite Expert Professional 3和4的license”,指的是这两个版本的许可证或授权信息。许可证是软件使用的重要部分,特别是商业软件,因为它们规定了用户可以如何使用和分发软件。对于SQLite Expert ...
3. 高并发:由于SQLite提供的数据锁粒度较粗,不支持行级锁,导致并发性能较低。 从个性化特征来看,SQLite的零配置特性使得它不需要初始化配置文件,没有安装和卸载过程,也不需要创建用户和权限划分。在系统出现...
1. **SQLite**: SQLite是一个开源的嵌入式关系数据库,不需要单独的服务器进程,可以直接在应用程序中使用。它支持多种操作系统,包括Windows、Linux和Mac OS X,并广泛应用于移动设备和桌面应用。 2. **数据库管理...
SQLite是一款开源、轻量级的嵌入式关系型数据库,被广泛应用于移动设备、嵌入式系统以及桌面应用中。SQLite 3.6.13是该数据库管理系统的一个较早版本,但其稳定性和功能仍然得到了许多开发者的认可。本文将深入探讨...