问题 如何在不锁定数据库的情况下使用数据读取器执行SQLite查询


我使用System.Data.Sqlite来访问C#中的SQLite数据库。我有一个查询必须读取表中的行。在遍历行并且在阅读器打开时,必须执行某些SQL更新。我遇到了“数据库被锁定”的异常。

SQLite文档 状态:

当进程想要从数据库文件中读取时,它遵循以下步骤:

  1. 打开数据库文件并获取SHARED锁。

该文档进一步说明了“共享”锁定:

可以读取数据库但不写入。任意数量的进程可以同时保存SHARED锁,因此可以有许多同时读取器。但是,当一个或多个SHARED锁处于活动状态时,不允许其他线程或进程写入数据库文件。

常问问题 状态:

多个进程可以同时打开同一个数据库。多个进程可以同时执行SELECT。但是,只有一个进程可以随时对数据库进行更改。

这本书 SQLite的权威指南 状态:

...一个连接可以选择有一个 读未提交 隔离级别使用 read_uncommited 附注。如果设置为 真正,然后连接不会在它读取的表上放置读锁定。因此,另一个writer实际上可以更改表,因为read-uncommitted模式下的连接既不会阻塞也不会被任何其他连接阻塞。

我尝试将pragma设置为在SQL查询命令语句中读取未提交,如下所示:

PRAGMA read_uncommitted = 1;
SELECT Column1, Column2 FROM MyTable

使用不同连接的同一线程上的SQL更新仍然失败,并出现“数据库已锁定”异常。然后,我尝试将隔离级别设置为在连接实例上读取未提交。同样的例外仍然没有变化。

如何在不锁定数据库的情况下使用开放数据读取器来遍历数据库中的行,以便我可以执行更新?

更新:

以下两个答案都有效。然而,我已经从使用默认回滚日志转移到现在使用预写日志,这提供了改进的数据库读写并发性。


12238
2018-03-20 06:29


起源



答案:


使用 WAL 模式。


8
2018-03-20 19:45



我正在使用ADO.Net。根据 sqlite.phxsoftware.com System.Data.SQLite.dll的版本是2010年4月18日的1.0.66.0,SQLite的版本是3.6.23.1。似乎我需要版本3.7+来使用WAL。有什么建议么?是否有更新的ADO.Net提供商? - Elan
是的,看 system.data.sqlite.org/index.html/doc/trunk/www/index.wiki  2011年2月版本1.0.68.0是与SQLite 3.7.5的代码合并 - Doug Currie
看起来很有希望;很遗憾,我没有看到任何准备好或可供下载的版本......你提到1.0.68.0的版本是否正式发布,或者这是一个alpha / beta? - Elan
源代码可供下载;正如我所理解的那样,由于各种构建工具和目标的复杂性,构建系统仍然是一个正在进行的工作。 - Doug Currie
使用System.Data.SQLite.Core,您可以通过在连接字符串中添加“journal mode = Wal”来完成此操作 - Foole


答案:


使用 WAL 模式。


8
2018-03-20 19:45



我正在使用ADO.Net。根据 sqlite.phxsoftware.com System.Data.SQLite.dll的版本是2010年4月18日的1.0.66.0,SQLite的版本是3.6.23.1。似乎我需要版本3.7+来使用WAL。有什么建议么?是否有更新的ADO.Net提供商? - Elan
是的,看 system.data.sqlite.org/index.html/doc/trunk/www/index.wiki  2011年2月版本1.0.68.0是与SQLite 3.7.5的代码合并 - Doug Currie
看起来很有希望;很遗憾,我没有看到任何准备好或可供下载的版本......你提到1.0.68.0的版本是否正式发布,或者这是一个alpha / beta? - Elan
源代码可供下载;正如我所理解的那样,由于各种构建工具和目标的复杂性,构建系统仍然是一个正在进行的工作。 - Doug Currie
使用System.Data.SQLite.Core,您可以通过在连接字符串中添加“journal mode = Wal”来完成此操作 - Foole


我无法使用开源数据提供程序来实现此功能 这里。但是,我能够使用免费的标准版来使用它 dotConnect 如下:

创建以下DLL导入,以便我们可以为SQLite启用共享缓存。

[DllImport("sqlite3.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int sqlite3_enable_shared_cache(int enable);

执行上述功能以启用共享缓存。注意,这只需要为整个过程执行一次 - 请参阅 SQLite文档

sqlite3_enable_shared_cache(1);

然后使用pragma语句为数据读取器使用的SQL查询语句添加前缀,如下所示:

PRAGMA read_uncommitted = 1;
SELECT Column1, Column2 FROM MyTable

现在可以在数据读取器处于活动状态时自由更新和插入行。可以找到有关共享缓存的其他SQLite文档 这里

更新:

较新版本的Devart SQLite数据提供程序现在以改进的方式支持此功能。 要启用共享缓存,可以进行以下调用:

Devart.Data.SQLite.SQLiteConnection.EnableSharedCache();

可以将未提交的读取配置到连接字符串中,例如,如下所示:

Devart.Data.SQLite.SQLiteConnectionStringBuilder builder = new SQLiteConnectionStringBuilder();
builder.ReadUncommitted = true;
builder.DateTimeFormat = Devart.Data.SQLite.SQLiteDateFormats.Ticks;
builder.DataSource = DatabaseFilePath;
builder.DefaultCommandTimeout = 300;
builder.MinPoolSize = 0;
builder.MaxPoolSize = 100;
builder.Pooling = true;
builder.FailIfMissing = false;
builder.LegacyFileFormat = false;
builder.JournalMode = JournalMode.Default;
string connectionString = builder.ToString();

2
2018-03-21 21:14