我正在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
,另一个线程将无法在插入语句之间执行任何操作答案 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锁->仅允许一个事务具有写访问权限,其他事务将等待
现在,如果您尝试从两个线程中交错写入事务,则每个线程都执行多个(粒状)事务-那么这是一个主意:
https://docs.microsoft.com/en-us/dotnet/api/system.threading.thread.yield
这可能有助于解决“当前写入线程的Sleep并未导致上下文切换到我们想要的线程”的问题。
即使使用Yield,也无法保证OS /运行时将切换到所需的线程,但是...也许值得一试,至少您不会人为地使代码运行速度变慢。>
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)