SQLite并发访问


182

SQLite3是否可以安全地处理从同一数据库读取/写入的多个进程的并发访问?有平台例外吗?


3
我忘了提赏金的Goall:大多数答案都可以:“ SQLite足够快”,“ SQLite可以很好地处理并发性”等。但是,恕我直言,请不要详细回答/如果两个写操作都不能清楚解释会发生什么会在完全相同的时间到达(理论上非常罕见的情况)。1)会触发错误并中断程序吗?或者2)第二个写操作会等到第一个写操作完成吗?或3)写入操作之一会被丢弃(数据丢失!)吗?4)还有别的吗?在许多情况下,了解并发写入的局限性可能很有用。
Basj

7
@Basj简而言之,2)将等待并重试几次(可配置),1)触发错误,SQLITE_BUSY。3)您可以注册处理SQLITE_BUSY错误的回调。
obgnaw

Answers:


116

如果大多数并发访问是读取的(例如SELECT),则SQLite可以很好地处理它们。但是,如果您开始同时进行写入,则锁争用可能会成为问题。然后,这很大程度上取决于文件系统的速度,因为SQLite引擎本身非常快,并且具有许多巧妙的优化方法可以最大程度地减少争用。特别是SQLite 3。

对于大多数台式机/笔记本电脑/平板电脑/电话应用程序,SQLite足够快,因为并发性不足。(Firefox将SQLite广泛用于书签,历史记录等。)

对于服务器应用程序,前段时间有人说,在典型情况下(例如,博客,论坛),SQLite数据库可以完美地处理每天少于10万的页面浏览量,而我还没有看到任何相反的证据。实际上,使用现代磁盘和处理器,95%的网站和Web服务都可以在SQLite上正常工作。

如果您想要真正的快速读写访问,请使用内存中的SQLite数据库。RAM比磁盘快几个数量级。


17
OP并不询问效率和速度,而是询问并发访问。Web服务器与此无关。关于内存数据库也是如此。
Jarekczek'7

1
您在一定程度上是对的,但效率/速度确实起作用。更快的访问意味着更少的等待锁定时间,从而减少了SQLite并发性能的弊端。特别是,如果您的写入操作很少且速度很快,那么数据库对于用户而言似乎根本就不会有任何并发​​问题。
Caboose

1
您将如何管理对内存中sqlite数据库的并发访问?
P-Gn

43

是的,SQLite可以很好地处理并发,但是从性能的角度来看并不是最好的。据我所知,没有例外。详细信息在SQLite的网站上:https : //www.sqlite.org/lockingv3.html

这条语句很有趣:“分页器模块确保所有更改都立即发生,要么所有更改都发生,要么全部不发生,两个或多个进程不会尝试以不兼容的方式同时访问数据库”


2
以下是有关不同平台(即NFS文件系统和Windows)上的问题的一些评论(尽管可能仅与Windows的旧版本有关)
Nate 2012年

1
是否可以将SQLite3数据库加载到RAM中以供PHP中的所有用户使用?我猜它没有程序性
Anon343224user 2013年

1
@foxyfennec ..一个起点,尽管SQLite可能不是此用例的最佳数据库。sqlite.org/inmemorydb.html
kingPuppy

41

是的,它确实。让我们找出原因

SQLite是事务性的

SQLite中单个事务中的所有更改要么完全发生,要么根本不发生

这种ACID支持以及并发读/写以两种方式提供-使用所谓的日志记录(称为“旧方法”)或预写日志记录(称为“新方法”)

日记(旧方法)

在这种模式下,SQLite使用DATABASE-LEVEL 锁定。这是要理解的关键点。

这意味着,每当需要读取/写入某些内容时,它都会首先获得ENTIRE数据库文件的锁。多个阅读器可以共存并并行阅读

在写入过程中,请确保已获取排他锁,并且没有其他进程同时在进行读/写操作,因此写操作是安全的。

这就是为什么他们在这里说SQlite实现可序列化事务

烦恼

因为它每次都需要锁定整个数据库,并且每个人都在等待处理写并发的过程,所以这种并发的写/读性能相当低。

回滚/中断

在将某些内容写入数据库文件之前,SQLite首先将要更改的块保存在临时文件中。如果在写入数据库文件的过程中发生故障,它将选择该临时文件并从中还原更改

预写日志记录或WAL(新方法)

在这种情况下,所有写操作都会附加到一个临时文件(预写日志)中,并且此文件会定期与原始数据库合并。当SQLite搜索某些内容时,它将首先检查该临时文件,如果找不到任何内容,则继续处理主数据库文件。

结果,读者不会与作家竞争,并且与旧方式相比,性能要好得多。

注意事项

SQlite严重依赖于底层文件系统锁定功能,因此应谨慎使用,此处有更多详细信息

您还可能会遇到数据库锁定错误,尤其是在日志记录模式下,因此在设计应用程序时应考虑到此错误


35

似乎没有人提到WAL(预写日志)模式。确保事务被正确组织并设置为WAL模式,在人们进行更新的同时阅读内容时,无需保持数据库锁定。

唯一的问题是,在某些时候需要将WAL重新合并到主数据库中,并在与数据库的最后一个连接关闭时执行此操作。对于一个非常繁忙的站点,您可能会发现所有连接都需要花费几秒钟的时间,但是每天点击10万次也不成问题。


有趣,但仅适用于单台计算机,不适用于通过网络访问数据库的方案。
Bobík

1
值得一提的是,作家等待的默认超时是5秒,database is locked而作家将提出该错误
mirhossein

16

在2019年,有两个新的并发写入选项尚未发布,但可以在单独的分支中使用。

“ PRAGMA journal_mode = wal2”

与常规的“ wal”模式相比,此日志模式的优势在于,编写者可以继续写入一个wal文件,而另一个则被检查点。

开始同步-链接到详细文档

BEGIN CONCURRENT增强功能允许多个写程序在数据库处于“ wal”或“ wal2”模式下同时处理写事务,尽管系统仍对COMMIT命令进行序列化。

当使用“ BEGIN CONCURRENT”打开写事务时,实际上将锁定数据库推迟到执行COMMIT之前。这意味着以BEGIN CONCURRENT开始的任何数量的事务都可以同时进行。系统使用乐观的页面级别锁定来防止发生冲突的并发事务。

它们一起出现在begin-concurrent-wal2或每个出现在单独的自己的分支中


1
我们是否知道这些功能何时会进入发行版?他们真的可以派上用场。
彼得·摩尔

2
不知道。您可以从分支轻松构建。对于.NET,我有一个具有低级接口和WAL2的库+开始并发+ FTS5:github.com/Spreads/Spreads.SQLite
VB

哦,当然谢谢。我更想知道稳定性。SQLite在发布方面非常出色,但我不知道在生产代码中使用分支会带来多大的风险。
彼得·摩尔

2
大致参见github.com/Expensify/Bedrock/issues/65和Bedrock。他们在生产中使用它并推送了这些begin concurrent东西。
VB

13

SQLite在数据库级别具有读者-作者锁定。多个连接(可能属于不同的进程)可以同时从同一数据库读取数据,但是只有一个可以写入数据库。

SQLite支持无限数量的同时读取器,但它只能在任何时刻允许一个写入器。在许多情况下,这不是问题。作家排队。每个应用程序都会快速完成其数据库的工作并继续运行,并且锁定不会持续超过几十毫秒。但是有些应用程序需要更多的并发性,而这些应用程序可能需要寻求不同的解决方案。-适用于SQLite @ SQLite.org

读写器锁启用独立的事务处理,并使用数据库级别的排他和共享锁来实现。

在连接上对数据库执行写操作之前,必须获得排他锁。获得排他锁后,其他连接的读取和写入操作都将被阻止,直到再次释放该锁为止。

并发写入的实现细节

SQLite具有一个锁定表,该锁定表有助于在写操作期间尽可能晚地锁定数据库,以确保最大程度的并发性。

初始状态为UNLOCKED,在此状态下,连接尚未访问数据库。当进程连接到数据库,甚至使用BEGIN启动了事务时,该连接仍处于UNLOCKED状态。

在UNLOCKED状态之后,下一个状态是SHARED状态。为了能够从数据库读取(不写入)数据,连接必须首先通过获得SHARED锁进入SHARED状态。多个连接可以同时获取和维护SHARED锁,因此多个连接可以同时从同一数据库读取数据。但是,即使仅释放一个SHARED锁,也没有连接可以成功完成对数据库的写入。

如果连接要写入数据库,则必须首先获得RESERVED锁。

尽管多个SHARED锁可以与一个RESERVED锁共存,但一次只能激活一个RESERVED锁。RESERVED与PENDING的不同之处在于,当有RESERVED锁时,可以获取新的SHARED锁。-文件锁定和并发在SQLite的第3版@ SQLite.org

一旦连接获得RESERVED锁,它就可以开始处理数据库修改操作,尽管这些修改只能在缓冲区中完成,而不能实际写入磁盘。对读取内容所做的修改将保存在内存缓冲区中。当连接要提交修改(或事务)时,必须将RESERVED锁升级为EXCLUSIVE锁。为了获得锁,您必须首先将锁提升到PENDING锁。

PENDING锁意味着持有该锁的进程希望尽快写入数据库,并且正在等待所有当前SHARED锁清除,以便可以获取EXCLUSIVE锁。如果PENDING锁处于活动状态,则不允许对数据库使用任何新的SHARED锁,尽管允许现有的SHARED锁继续。

需要EXCLUSIVE锁才能写入数据库文件。文件上仅允许一个EXCLUSIVE锁,并且不允许任何其他类型的锁与EXCLUSIVE锁共存。为了最大化并发性,SQLite致力于最小化持有EXCLUSIVE锁的时间。-文件锁定和并发在SQLite的第3版@ SQLite.org

因此,您可能会说SQLite安全地通过多个进程写入同一数据库来安全地处理并发访问,因为它不支持它!当第二个作者达到重试限制时,您将获得SQLITE_BUSYSQLITE_LOCKED第二个作者的报酬。


谢谢。一个有2个编写者的代码示例对于理解其工作原理非常有用。
Basj

2
简而言之@ Basj,sqlite在数据库文件上具有读写锁定。这与并发写入文件相同。使用WAL时,仍然不能同时进行写入,但是WAL可以加快写入速度,并且读写可以并发。并且WAL引入了新的锁,例如WAL_READ_LOCK,WAL_WRITE_LOCK。
obgnaw

2
您是否可以使用一个队列并让多个线程提供该队列,而只有一个线程使用该队列中的SQL语句写入数据库。像这样
加百利

7

这个线程很旧,但是我认为最好分享在sqlite上完成的测试结果:我运行了2个python程序实例(同一程序的不同进程),在事务中执行了SELECT和UPDATE sql命令,且EXCLUSIVE锁定和超时设置为10秒钟获得锁定,结果令人沮丧。每个实例都在10000个步骤循环中执行:

  • 用排他锁连接到数据库
  • 选择一行以读取计数器
  • 用等于计数器加1的新值更新行
  • 与数据库的紧密连接

即使sqlite授予了事务独占锁定的权限,实际执行的周期总数也不等于20000,而是更少(两个进程都计入单个计数器的迭代总数)。Python程序几乎没有引发任何单个异常(在选择20次执行期间仅一次)。测试时的sqlite修订版是3.6.20和python v3.3 CentOS 6.5。在我看来,最好为这种工作找到更可靠的产品,或者将对sqlite的写入限制为单个唯一的进程/线程。


5
似乎您需要说一些神奇的话才能锁定python,如下所述:stackoverflow.com/a/12848059/1048959 尽管python sqlite文档使您相信这with con已经足够了。
Dan Stahlke 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.