Delphi FireDAC SQLite “database is locked“ 解决办法

SQLite 多线程操作优化与事务处理策略
本文探讨了在多线程环境中如何有效地处理SQLite数据库,重点介绍了事务处理、锁定机制、缓存策略和日志模式。推荐的设置组合包括:JournalMode WAL、Synchronous off/Normal、LockedMode normal、SharedCache true、TxOptions.isolation xiSnapshot、UpdateOptions.lockWait true及BusyTimeout 10秒,以避免databaseislocked错误。同时建议事务时间尽量短,确保并发性能。

为了解决多线程,多进程操作sqlite。进行了一些实测。以下为个人实测得出结论,非官方文档:

名词解释:

写状态: 处理事务中,并执行了修改数据操作,但未提交事务。

写锁:处理事务中,写数据。

读锁:处理事务中,读数据。

相关参数解读:

UpdateOptions.lockwait : 其它进程处于事务或写状态时,会阻止当前进程进入事务或写数据,这时是否等待,等待时间为BusyTimeout,如果超时,会报database is locked错误。

BusyTimeout: 超时时间,默认为10000.(10秒),如果UpdateOptions.lockwait = false,则此参数无效。

TxOptions.isolation : 事务级别

xiSerializible 或xiSnapshot 同一时刻只能有一个进程处于事务中。当一个进程进入事务时,其它进程进入事务时会等待。

设置成其它参数时,可以两个进程同时进入事务状态,但如果一个进程于事务中执行了写数据。不管这个事务是否已commit. 其它进程中的事务进行写操作都会报database is locked.

SharedCache:共享缓存,建议设置上。实测进程间也是可以一个事务提交,另一个进程立刻就可以读出。

LockedMode: 这个简单,必须设置成normal,不然其它连接无法访问数据库。

Synchronous: 这个影响数据是否会实质写盘,实测设置为off没有任务影响。进程间数据依然是同步。这个会影响写入速度。

JournalMode:WAL 这个推荐就设置成WAL,具体不解释。 几种方式这种还是最好的。

delphi中如果一条写语句没有包装在事务中,则会自动发起一个事务。

sqlite锁定方式为文件锁,即当一个进程处于写锁中,即写入操作后,尚未提交。其它进程不可以执行写入操作。与具体写入哪个表无关。

总结在避免出现database is locked. 以下设置组合最好:

JounalMode:WAL

Synchronous:off|Normal

LockedMode:normal

SharedCache:true

TxOptions.isolation:xiSnapshot.

UpdateOptions.lockWait=true

Busytimeout:10秒。

同时操作时注意事务时间尽可能短,不要超过10秒。

基本上以上设置后,不会出现database is locked错误了。

By default, all SQLite driver settings are set for the high-performance single connection access to a database in a stable environment. The PRAGMA command allows you to configure SQLite. Many FireDAC SQLite driver parameters correspond to the pragmas. Additionally, SQLiteAdvanced allows you to specify multiple pragmas separated by ';' as a single connection parameter.

The additional SQLite use cases are:

NoApplication specificsDescription
1Reading large DB.Set CacheSize to a higher number of pages, which will be used to cache the DB data. The total cache size will be CacheSize * <db page size>.
2Exclusive updating of DB.Consider to set JournalMode to WAL (more).
3Long updating transactions.Set CacheSize to a higher number of pages, that will allow you to run transactions with many updates without overloading the memory cache with dirty pages.
4A few concurrent updating processes.Set LockingMode to Normal to enable shared DB access. Set Synchronous to Normal or Full to make committed data visible to others. Set UpdateOptions.LockWait to True to enable waiting for locks. Increase BusyTimeout to raise a lock waiting time. Consider to set JournalMode to WAL.
5A few concurrent updating threads.See (4). Also set SharedCache to False to minimize locking conflicts.
6A few concurrent updating transactions.See (4) or (5). Also set TxOptions.Isolation to xiSnapshot or xiSerializible to avoid possible transaction deadlocks.
7High safety.Set Synchronous to Full to protect the DB from the committed data losses. Also see (3). Consider encrypting the database to provide integrity.
8High confidentiality.Encrypt database to provide confidentiality and integrity.
9Development time.Set LockingMode to Normal to enable simultaneous use of the SQLite DB in the IDE and a debugged program.

事务可以是延迟的、立即的或排他的。默认事务行为是 DEFERRED。

DEFERRED 意味着事务直到第一次访问数据库才真正开始。在内部,BEGIN DEFERRED 语句仅在数据库连接上设置一个标志,该标志关闭通常在最后一条语句完成时发生的自动提交。这会导致自动启动的事务一直持续到显式 COMMIT 或 ROLLBACK 或直到由错误或 ON CONFLICT ROLLBACK 子句引发回滚。如果 BEGIN DEFERRED 之后的第一条语句是 SELECT,则启动读取事务。如果可能,后续的写入语句会将事务升级为写入事务,或者返回 SQLITE_BUSY。如果 BEGIN DEFERRED 之后的第一条语句是写入语句,则启动写入事务。

IMMEDIATE 导致数据库连接立即开始新的写入,而无需等待写入语句。如果另一个写入事务已在另一个数据库连接上处于活动状态,则 BEGIN IMMEDIATE 可能会因SQLITE_BUSY而失败。

EXCLUSIVE 与 IMMEDIATE 相似之处在于立即启动写入事务。EXCLUSIVE 和 IMMEDIATE 在WAL 模式下是相同的,但在其他日志模式下,EXCLUSIVE 会阻止其他数据库连接在事务进行时读取数据库。

插个题外话:在firedac连接mysql时,可以直接设置TXOptions.isolation = xiReadCommitted 就可以了。这个是记录级锁。 一个事务修改一个条记录时,不影响其它进程的对其它表或其它记录的的更新。

xiReadCommitted:脏读。处于事务中时,读不到其它表的更新,但写数据时是受其它事务影响的,按数据库当前最新数据进行的。

### 回答1: 当出现"sqlite database is locked"的错误时,意味着有其他进程或线程正在访问或修改数据库文件,并且已经对其进行了独占锁定。SQLite数据库使用一种称为独占性写入锁(exclusive write lock)的算法来处理并发访问。 造成数据库被锁定的常见情况有以下几种: 1. 在一个连接中同时启动多个事务并尝试修改数据库SQLite数据库一次只能有一个写入事务,其他事务必须等待直到当前事务完成。 2. 在一个连接中同时启动多个写操作(如插入、更新或删除)线程,并尝试同时修改数据库。 3. 一个事务在长时间运行(例如,处理大量数据)而没有释放锁定,导致其他连接无法获取锁定。 为解决"sqlite database is locked"错误,可以采取以下几种方法: 1. 确保在一个连接中只有一个事务在操作数据库:在一个事务执行完毕前,不要同时启动其他事务。 2. 在使用写操作之前,检查数据库是否已经被锁定。可以使用SQLite的函数`sqlite3_busy_timeout()`来设置等待锁定的超时时间,以避免长时间阻塞。 3. 优化事务操作:将事务分解为多个较小的事务,或者使用批量插入、更新和删除操作来减少每个操作的次数。 4. 如果可能的话,通过使用缓存机制或调整读写操作的时间,尽量减少数据库的并发读写操作。 最后,需要注意的是,使用SQLite时要正确处理并发访问以及锁定,以保证数据库的一致性和可用性。 ### 回答2: "sqlite database is locked" 是SQLite数据库中的一个错误信息,表示当前的数据库文件被其他进程锁定,无法执行请求的操作。 当多个进程或线程同时访问同一个SQLite数据库文件时,会出现锁定现象。这是为了确保数据的完整性和一致性,避免多个进程同时修改数据库而导致冲突和损坏。 出现"sqlite database is locked"错误的情况有以下几个可能原因: 1. 并发访问:如果多个进程或线程同时试图访问数据库,其中一个进程可能会独占数据库文件资源,而其他进程则被阻塞并显示此错误。 2. 锁定问题:一个进程已经开始了一个事务并锁定了数据库文件,而另一个进程尝试访问相同的数据库文件,发生冲突。 3. 操作冲突:当一个进程正在执行一个写操作时,另一个进程也尝试执行写操作,会产生冲突。SQLite不支持并发写操作,因此会产生"sqlite database is locked"错误。 解决这个问题的方法有: 1. 等待:可以通过等待一段时间,让占用数据库资源的进程完成操作,然后再次尝试访问数据库。 2. 优化并发操作:通过合理地规划数据库操作顺序、使用合适的事务管理和锁定机制,以减少并发访问时的冲突机会。 3. 重新设计应用程序:如果并发访问需求较高,可以重新设计应用程序架构,使用数据库服务器或其他分布式数据库解决方案,来更好地支持并发操作。 总之,"sqlite database is locked"错误表示数据库文件被其他进程锁定,我们需要合理规划并发访问,使用合适的解决方法来解决这个问题。 ### 回答3: SQLite数据库被锁是一个常见的错误,通常是由于多个进程或线程同时尝试对同一个数据库进行读写操作而导致的。当一个进程或线程正在执行一个事务并且没有释放锁时,其他进程或线程将无法访问该数据库,从而导致数据库被锁。 解决这个问题的方法有几种: 1. 等待:可以尝试在一段时间内等待数据库锁的释放,然后重试执行操作。这可能需要根据具体情况调整等待时长。 2. 优化访问模式:检查代码中的访问模式,并确保每个进程或线程都按照正确的顺序访问数据库,以避免出现冲突。 3. 使用事务:事务可以保证对数据库的原子性操作,最大限度地减少锁定时间。确保在适当的时候开始和提交事务。 4. 关闭连接:在某些情况下,关闭所有尝试访问数据库的连接,并重新打开连接可能有助于解决锁定问题。 总的来说,解决SQLite数据库被锁问题的关键在于识别并处理潜在的并发访问冲突,采取适当的措施以确保线程安全并最大限度地减少对数据库的访问冲突。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值