两个不同线程中的两个SQLite连接的并发问题

时间:2018-11-28 13:16:41

标签: sqlite system.data.sqlite

我正在C#中使用System.Data.SQLite

我有

线程1(UI)-写入表1 线程2(工作人员)写入table1

因此,我有两个线程同时写入同一数据库。我分批写入数据,每个批次都有一个事务。需要批处理以避免将数据库锁定太长时间,以便其他线程可以对数据库进行写访问。

但这不起作用。我希望thread1能够在thread2的事务批处理之间写入数据库,但是除非我在批处理之间具有Thread.Sleep(100),否则不会发生这种情况。请注意,Thread.Sleep(10)的值太小也不起作用。我了解这与线程上下文切换有关,但我不明白为什么少量的Thread.Sleep不能完成这项工作。

因为使用Thread.Sleep不好,有没有办法控制谁获得数据库锁的优先级?

P.S。即使没有交易,这似乎也是一个问题。如果我有一个包含很多插入语句的循环,那么如果没有Thread.Sleep

,另一个线程将无法在插入语句之间执行任何操作

2 个答案:

答案 0 :(得分:1)

我不认为SQlite支持真正的并发写入事务...我在Android上使用过“非独占”事务,并且它们被记录为允许并发读取(我从内存中用自己的话说)虽然可能正在进行写操作。

现在更重要了……查看SQLite交易文档:

https://sqlite.org/lang_transaction.html

  

因此,对于延迟的事务,BEGIN语句本身会执行   文件系统没有任何东西。在第一次读取之前不获取锁   或写操作。对数据库的第一次读取操作   创建共享锁,并且第一个写操作创建一个保留   锁。因为锁的获取被推迟到   需要时,另一个线程或进程可能会创建一个   单独的事务,并在开始BEGIN之后将其写入数据库   当前线程已执行。如果交易是立即的,则   所有数据库上都获得了RESERVED锁   命令执行,而无需等待数据库被使用。

(重点-我的)

好的,所以现在我们了解到写入数据库需要RESERVED锁。

让我们看看这里是什么:

https://sqlite.org/lockingv3.html#reserved_lock

  

“保留”锁定表示该进程正在计划写入   将来某个时候的数据库文件,但目前是   只是从文件中读取。 可能只有一个“保留”锁处于活动状态   一次,尽管多个共享锁可以与一个共享共存   保留锁。 RESERVED与PENDING的不同之处在于新的SHARED锁   可以在有RESERVED锁的情况下获取。

好的,因此这确认SQLite需要一个RESERVED锁才能写入数据库,并且还告诉我们一次只能存在一个RESERVED锁->仅允许一个事务具有写访问权限,其他事务将等待

现在,如果您尝试从两个线程中交错写入事务,则每个线程都执行多个(粒状)事务-那么这是一个主意:

  • 用Thread.Yield替换Thread.Sleep

https://docs.microsoft.com/en-us/dotnet/api/system.threading.thread.yield

这可能有助于解决“当前写入线程的Sleep并未导致上下文切换到我们想要的线程”的问题。

即使使用Yield,也无法保证OS /运行时将切换到所需的线程,但是...也许值得一试,至少您不会人为地使代码运行速度变慢。

  • 鉴于我们对SQLite的“只允许写入一个事务”的了解,我将考虑以下模式:

1-创建一个新线程,其任务是处理数据库写入

2-从当前的两个线程开始对该线程的写操作排队

3-让“操作”是独立的/足够的对象,包含他们打算写入的所有数据

4-最后,使用带有闩锁的回调(我相信在C#中是CountDownEvent)知道何时完成操作,以便您当前的线程可以等待完成

那么,您只有一个写线程(就SQlite而言),而您当前的两个线程之间仍然是并发的。

伪代码:

// Write thread

while (item = blockingQueue.getNextItemToWrite()) {
 item.executeWrite(database)
 item.signalCompletion()
}

// Thread 1

item = new WriteItem(some data that needs to be written)
WriteThread.enqeue(item)
item.awaitCompletion()

// Thread 2 - same as Thread 1

WriteItem基类具有一个CountDownEvent,该事件是1)由awaitCompletion等待和2)由signalCompletion发出信号。

我确定有一种方法可以将其包装到更优雅的帮助器类中,并可能使用async / await。

答案 1 :(得分:0)

看看busy_timeout。在理想情况下,应该允许您的两个线程(假设它们不共享连接)在它们方便时进行读写。如果可以避免的话,为什么还要为睡眠安排时间呢?

接下来,您正在正确使用事务。您是否研究了三种不同的交易行为? https://sqlite.org/lang_transaction.html

但是,这并不能解决线程1可能试图在线程2处于锁定状态的问题。为此,请参见PRAGMA busy_timeout。只需将每个连接的编译指示设置为1000(ms)。如果线程2锁定了数据库,而线程1试图获取锁定,则它将仅等待1000毫秒,直到失败并出现超时错误。 (https://sqlite.org/pragma.html#pragma_busy_timeout