SQLite3是否安全地处理来自同一个DB读取/写入的多个进程的并发访问?有没有平台例外?
如果大多数并发访问是读取(例如SELECT),SQLite可以很好地处理它们.但如果你开始同时写,锁争用可能会成为一个问题.然后很大程度上取决于文件系统的速度,因为SQLite引擎本身非常快,并且有许多聪明的优化来最小化争用.特别是SQLite 3.
对于大多数台式机/笔记本电脑/平板电脑/手机应用程序,SQLite足够快,因为没有足够的并发性.(Firefox广泛使用SQLite作为书签,历史等)
对于服务器应用程序,前段时间有人说在一般情况下(例如博客,论坛),SQLite数据库可以完美地处理每天少于100K的页面浏览量,而我还没有看到任何相反的证据.实际上,使用现代磁盘和处理器,95%的网站和Web服务可以与SQLite一起使用.
如果您想要快速读/写访问,请使用内存中的SQLite数据库.RAM比磁盘快几个数量级.
是的,SQLite很好地处理并发性,但从性能角度来看并不是最好的.据我所知,没有例外.详细信息在SQLite的网站上:https://www.sqlite.org/lockingv3.html
这个陈述很有意思:"寻呼机模块确保一次性发生变化,发生所有变更或者没有变更,两个或多个进程不会同时以不兼容的方式访问数据库"
似乎没有人提到WAL(Write Ahead Log)模式.确保事务处理正确并且启用了WAL模式,在进行更新时人们正在阅读内容时无需锁定数据库.
唯一的问题是,在某些时候需要将WAL重新合并到主数据库中,并且在最后一次连接到数据库时关闭它.对于非常繁忙的站点,您可能会发现所有连接都需要几秒钟才能关闭,但每天100K的点击量应该不是问题.
是的,它确实.让我们找出原因
SQLite中单个事务中的所有更改要么完全发生,要么根本不发生
这种ACID支持以及并发读/写以两种方式提供 - 使用所谓的日记(让我们称之为" 旧方式 ")或预写日志(让我们称之为" 新方式 ")
在这种模式下,SQLite使用DATABASE-LEVEL 锁定.这是理解的关键点.
这意味着无论何时需要读取/写入内容,它都会首先获取ENTIRE数据库文件的锁定.多个读者可以并行共存和阅读
在写入期间,它确保获得独占锁定,并且没有其他进程同时读取/写入,因此写入是安全的.
这就是为什么在这里他们说SQlite的实现序列化交易
因为它需要每次都锁定整个数据库,并且每个人都在等待进程处理写入并发性受损并且此类并发写入/读取性能相当低
在向数据库文件写入内容之前,SQLite首先将要更改的块保存在临时文件中.如果在写入数据库文件的过程中发生崩溃,它将获取此临时文件并从中恢复更改
在这种情况下,所有写入都附加到临时文件(预写日志),并且此文件定期与原始数据库合并.当SQLite搜索某些东西时,它会首先检查这个临时文件,如果没有找到,则继续使用主数据库文件.
因此,与Old Way相比,读者不会与作家竞争,而且表现要好得多.
SQlite在很大程度上取决于底层文件系统锁定功能,因此应谨慎使用,此处有更多详细信息
您也可能会遇到数据库锁定错误,尤其是在日记模式下,因此您的应用程序需要在设计时考虑到此错误
SQLite支持无限数量的同时读者,但它只允许一个编写者在任何时刻.在许多情况下,这不是问题.作家排队.每个应用程序都可以快速完成数据库工作并继续运行,并且锁定持续时间超过几十毫秒.但是有些应用程序需要更多的并发性,而这些应用程序可能需要寻求不同的解决方案.
在DOC中很清楚.
在事务处理中,SQLite通过数据库级别的独占锁和共享锁实现独立的事务处理.这就是为什么多个进程可以同时从同一个数据库读取数据的原因,但只有一个进程可以写入数据库.
在进程或线程想要对数据库执行写操作之前,必须获得独占锁.获得独占锁之后,不会再发生其他读或写操作.
实现细节,例如两个写:SQLite有一个锁表,可以帮助在最后一刻锁定不同的写数据库,以确保最大的并发性.
初始状态为"UNLOCKED",在此状态下,连接尚未访问数据库.当数据库连接到数据库并且甚至已使用BEGIN启动事务时,连接仍处于"未锁定"状态.
解锁状态的下一个状态是SHARED状态.为了能够从数据库读取(不写入)数据,连接必须首先进入SHARED状态,也就是说,首先获得SHARED锁定.多个连接可以同时获取和维护SHARED锁,也就是说,多个连接可以同时从同一个数据库读取数据.但即使只发布了一个SHARED锁,也不允许任何连接写入数据库.
如果连接想要写一个数据库,它必须首先得到一个RESERVED锁.
虽然多个SHARED锁可以与单个RESERVED锁共存,但一次只能激活一个RESERVED锁.RESERVED与PENDING的不同之处在于,当存在RESERVED锁时,可以获取新的SHARED锁.
一旦连接获得RESERVED锁,它就可以开始处理数据库修改操作,尽管这些修改只能在缓冲区中完成,而不是实际写入磁盘.对读出内容的修改保存在内存缓冲区中.当连接想要提交修改(或事务)时,必须将保留锁升级为独占锁.要获得锁定,您必须先将锁解除挂锁.
PENDING锁意味着持有锁的进程想要尽快写入数据库,并且只是等待所有当前的SHARED锁清除,以便它可以获得EXCLUSIVE锁.如果PENDING锁处于活动状态,则不允许对数据库使用新的SHARED锁,但允许继续使用现有的SHARED锁.
为了写入数据库文件,需要一个EXCLUSIVE锁.文件上只允许一个EXCLUSIVE锁,并且不允许任何其他类型的锁与EXCLUSIVE锁共存.为了最大化并发性,SQLite可以最大限度地减少EXCLUSIVE锁定所需的时间.
因此,SQLite安全地处理来自同一个数据库的多个进程的并发访问,因为它不支持它.当它达到重试限制时,你将获得SQLITE_BUSY
或SQLITE_LOCKED
为第二个编写器获取.
在2019年,有两个新的并发写入选项尚未发布,但可在不同的分支中使用.
"PRAGMA journal_mode = wal2"
这种日志模式优于常规"wal"模式的优点是作者可以继续写入一个wal文件,而另一个是检查点.
BEGIN CONCURRENT - 链接到详细的文档
该BEGIN并发增强允许多个作家,以写处理交易simultanously如果数据库是在"沃"或"wal2"模式,但系统仍然连载COMMIT命令.
当使用"BEGIN CONCURRENT"打开写事务时,实际锁定数据库将被推迟,直到执行COMMIT.这意味着以BEGIN CONCURRENT开始的任何数量的事务可以同时进行.系统使用乐观页面级锁定来防止提交冲突的并发事务.
它们一起存在于begin-concurrent-wal2中或者每个都存在于一个单独的分支中.
这个线程很旧但我认为分享我在sqlite上完成的测试结果会很好:我运行2个python程序实例(不同进程同一个程序)执行语句SELECT和UPDATE sql命令在事务中使用EXCLUSIVE锁定和超时设置为获得锁定10秒钟,结果令人沮丧.每个实例都在10000步循环中完成:
使用独占锁连接到db
选择一行读取计数器
使用等于计数器的新值更新行加1
与db的紧密连接
即使sqlite在事务上授予了独占锁定,实际执行的周期总数也不等于20 000但更少(两个进程计算的单个计数器的迭代总数).Python程序几乎没有抛出任何单个异常(在选择20次执行期间只有一次).测试时的sqlite修订版是3.6.20和python v3.3 CentOS 6.5.在我看来,最好为这种工作找到更可靠的产品,或者将sqlite的写入限制为单一的唯一进程/线程.