在SQLite提交期间,数据库文件被莫名其妙地锁定

我正在为SQLite数据库执行大量INSERTS。 我只使用一个线程。 我批处理写入以提高性能,并在发生崩溃时保持一定的安全性。 基本上我在内存中缓存了一堆数据,然后在我认为合适时,我遍历所有数据并执行INSERTS。 代码如下所示:

public void Commit() { using (SQLiteConnection conn = new SQLiteConnection(this.connString)) { conn.Open(); using (SQLiteTransaction trans = conn.BeginTransaction()) { using (SQLiteCommand command = conn.CreateCommand()) { command.CommandText = "INSERT OR IGNORE INTO [MY_TABLE] (col1, col2) VALUES (?,?)"; command.Parameters.Add(this.col1Param); command.Parameters.Add(this.col2Param); foreach (Data o in this.dataTemp) { this.col1Param.Value = o.Col1Prop; this. col2Param.Value = o.Col2Prop; command.ExecuteNonQuery(); } } this.TryHandleCommit(trans); } conn.Close(); } } 

我现在使用以下噱头来使事情最终发挥作用:

  private void TryHandleCommit(SQLiteTransaction trans) { try { trans.Commit(); } catch (Exception e) { Console.WriteLine("Trying again..."); this.TryHandleCommit(trans); } } 

我像这样创建我的数据库:

  public DataBase(String path) { //build connection string SQLiteConnectionStringBuilder connString = new SQLiteConnectionStringBuilder(); connString.DataSource = path; connString.Version = 3; connString.DefaultTimeout = 5; connString.JournalMode = SQLiteJournalModeEnum.Persist; connString.UseUTF16Encoding = true; using (connection = new SQLiteConnection(connString.ToString())) { //check for existence of db FileInfo f = new FileInfo(path); if (!f.Exists) //build new blank db { SQLiteConnection.CreateFile(path); connection.Open(); using (SQLiteTransaction trans = connection.BeginTransaction()) { using (SQLiteCommand command = connection.CreateCommand()) { command.CommandText = DataBase.CREATE_MATCHES; command.ExecuteNonQuery(); command.CommandText = DataBase.CREATE_STRING_DATA; command.ExecuteNonQuery(); //TODO add logging } trans.Commit(); } connection.Close(); } } } 

然后我导出连接字符串并使用它来获取程序不同部分的新连接。

在看似随机的时间间隔内,尽管忽略或以其他方式解决此问题的速度太快,但我得到了未处理的SQLiteException:数据库文件被锁定。 当我尝试提交事务时会发生这种情况。 在此之前似乎没有发生任何错误。 这并不总是发生。 有时整个事情顺利进行。

  • 在提交完成之前,不会对这些文件执行任何读取操作。
  • 我有最新的SQLite二进制文件。
  • 我正在编译.NET 2.0。
  • 我正在使用VS 2008。
  • db是本地文件。
  • 所有这些活动都封装在一个线程/进程中。
  • 病毒防护已关闭(虽然我认为仅在您通过网络连接时才有意义?)。
  • 根据苏格兰人的post,我实施了以下更改:
  • 日记帐模式设置为持久
  • 通过System.Windows.Forms.Application.AppData窗口调用存储在C:\ Docs + Settings \ ApplicationData中的DB文件
  • 没有内在的例外
  • 见证了两台不同的机器(尽管硬件和软件非常相似)
  • 一直在运行进程监视器 – 没有多余的进程将自己附加到数据库文件 – 问题肯定在我的代码中……

有没有人知道这里发生了什么?

我知道我只是丢掉了一大堆代码,但我一直在努力解决这个问题。 我要感谢任何使这个问题结束的人!

布赖恩

更新:

感谢您的建议到目前为止! 我已经实现了许多建议的更改。 我觉得我们越来越接近答案……但是……

上面的代码在技术上有效,但它是不确定的! 除了永久中立旋转之外,我们无法保证做任何事情。 在实践中,它似乎在第1次和第10次迭代之间的某处工作。 如果我以合理的间隔对我的提交进行批处理,那么损害将会减轻,但我真的不想让事情处于这种状态……

更多建议欢迎!

在运行程序时运行Sysinternals Process Monitor并过滤文件名,以排除任何其他进程对其执行任何操作并查看程序对文件执行的操作。 远射,但可能会给出一个线索。

看起来您无法将命令与您创建的事务相关联。 代替:

 using (SQLiteCommand command = conn.CreateCommand()) 

你应该使用:

 using (SQLiteCommand command = new SQLiteCommand("", conn, trans)) 

或者您可以在构建后设置其Transaction属性。

我们在这里 – 你对失败的处理是错误的:

该命令的ExecuteNonQuery方法也可能失败,您并没有真正受到保护。 您应该将代码更改为:

  public void Commit() { using (SQLiteConnection conn = new SQLiteConnection(this.connString)) { conn.Open(); SQLiteTransaction trans = conn.BeginTransaction(); try { using (SQLiteCommand command = conn.CreateCommand()) { command.Transaction = trans; // Now the command is linked to the transaction and don't try to create a new one (which is probably why your database gets locked) command.CommandText = "INSERT OR IGNORE INTO [MY_TABLE] (col1, col2) VALUES (?,?)"; command.Parameters.Add(this.col1Param); command.Parameters.Add(this.col2Param); foreach (Data o in this.dataTemp) { this.col1Param.Value = o.Col1Prop; this. col2Param.Value = o.Col2Prop; command.ExecuteNonQuery(); } } trans.Commit(); } catch (SQLiteException ex) { // You need to rollback in case something wrong happened in command.ExecuteNonQuery() ... trans.Rollback(); throw; } } } 

另一件事是你不需要在内存中缓存任何东西。 您可以依赖SQLite日记机制来存储未完成的事务状态。

使用带有TransactionScope类的嵌套事务我们遇到了类似的问题。 我们认为所有数据库操作都发生在同一个线程上……但是我们被事务机制抓住了……更具体地说是Ambient事务。

基本上有一个交易更高的链,通过ado的魔力,连接自动登入。结果是,即使我们认为我们在一个线程上写入数据库,写入并没有真正发生直到最顶层的交易被提交。 在这个’不确定’点,数据库被写入,导致它被锁定在我们的控制范围之外。

解决方案是确保sqlite数据库不直接参与环境事务,确保我们使用类似的东西:

 using(TransactionScope scope = new TransactionScope(TransactionScopeOptions.RequiresNew)) { ... scope.Complete() } 

需要注意的事项:

  • 不要跨多个线程/进程使用连接。

  • 我已经看到,当病毒扫描程序检测到文件的更改并尝试扫描时,就会发生这种情况。 它会将文件锁定一小段时间并造成严重破坏。

我今天开始面对同样的问题:我正在研究asp.net mvc,从头开始构建我的第一个应用程序。 有时,当我写入数据库时​​,我会得到相同的exception,说数据库文件已被锁定。

我发现它真的很奇怪,因为我完全确定当时只有一个连接打开(基于进程资源管理器的活动文件句柄列表)。

我还使用System.Data.SQLite .Net提供程序从头开始构建整个数据访问层,并且,当我计划它时,我特别注意连接和事务,以确保没有挂起任何连接或事务周围。

棘手的部分是在ExecuteNonQuery()命令上设置断点并在调试模式下运行应用程序会使错误消失! 谷歌搜索,我在这个网站上发现了一些有趣的东西: http : //www.softperfect.com/board/read.php?8,5775 。 在那里,有人回复了线程,建议作者将数据库路径放在反病毒忽略列表中。

我将数据库文件添加到我的防病毒(Microsoft Security Essentials)的忽略列表中,它解决了我的问题。 没有更多数据库锁定错误!

您的数据库文件与应用程序在同一台计算机上,还是存储在服务器上?

您应该在每个线程中创建一个新连接。 我会简单地创建一个连接,到处使用:connection = new SQLiteConnection(connString.ToString());

并在与应用程序相同的计算机上使用数据库文件并再次测试。

为什么两种不同的方式创建连接?

这些家伙有类似的问题(大多数情况下,看起来,日记文件被锁定,可能是TortoiseSVN交互…检查引用的文章)。

他们提出了一系列建议(正确的目录,将日记类型从删除更改为持久等)。 http://sqlite.phxsoftware.com/forums/p/689/5445.aspx#5445


日志模式选项在这里讨论: http : //www.sqlite.org/pragma.html 。 你可以试试TRUNCATE。

在SQL Liteexception期间是否存在堆栈跟踪?

您表示“以合理的间隔批量提交我的提交”。 间隔是多少?

我总是在using子句中使用Connection,Transaction和Command。 在你的第一个代码清单中,你的第三个代码(创建表格)没有。 我建议你也这样做,因为(谁知道?)也许创建表的命令会以某种方式继续锁定文件。 远射……但值得一试?

您是否正在运行Google桌面搜索(或其他文件索引器)? 如前所述, Sysinternals Process Monitor可以帮助您跟踪它。

另外,数据库的文件名是什么? 来自PerformanceTuningWindows :

非常非常小心您为数据库命名的内容,尤其是扩展名

例如,如果你给你的所有数据库扩展名.sdb(SQLite数据库,好名字嘿?我还是这样选择它…)你发现SDB扩展已经与APPFIX PACKAGES相关联。

现在,这是可爱的部分,APPFIX是Windows XP识别的可执行文件/软件包,它将(强调我的)添加数据库到系统恢复function

这意味着,在这里和我在一起,每当你向数据库写任何内容时,Windows XP系统认为血腥的可执行文件已经改变,并将你的ENTIRE 800 meg数据库复制到系统恢复目录….

我推荐像DB或DAT这样的东西。