【问题标题】:Database file is inexplicably locked during SQLite commitSQLite 提交期间数据库文件被莫名其妙地锁定
【发布时间】:2009-08-03 20:01:11
【问题描述】:

我正在对 SQLite 数据库执行大量 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 是本地文件。
  • 所有这些活动都封装在一个线程/进程中。
  • 病毒防护已关闭(尽管我认为这仅在您通过网络连接时才相关?)。
  • 根据 Scotsman 的帖子,我实施了以下更改:
  • 日志模式设置为持久
  • 通过System.Windows.Forms.Application.AppData windows 调用存储在 C:\Docs + Settings\ApplicationData 中的 DB 文件
  • 没有内部异常
  • 在两台不同的机器上见证(尽管硬件和软件非常相似)
  • 一直在运行 Process Monitor - 没有无关的进程将自己附加到 DB 文件 - 问题肯定出在我的代码中...

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

我知道我刚刚丢掉了一堆乱七八糟的代码,但我试图解决这个问题已经太久了。感谢任何完成此问题的人!

布莱恩

更新:

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

上面的代码在技术上是有效的,但是它是不确定的!除了永远处于空档旋转之外,不能保证做任何事情。在实践中,它似乎在第 1 次和第 10 次迭代之间起作用。如果我以合理的间隔批量提交我的提交,损害将得到减轻,但我真的不想让事情处于这种状态......

欢迎提出更多建议!

【问题讨论】:

  • 虽然您不希望有较小的提交 - 当您将 begin tran 和 commit tran 移动到内部批处理循环时问题仍然存在吗?

标签: c# sqlite


【解决方案1】:

您似乎未能将命令与您创建的事务关联起来。 而不是:

using (SQLiteCommand command = conn.CreateCommand())

你应该使用:

using (SQLiteCommand command = new SQLiteCommand("<INSERT statement here>", 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 日志机制来存储不完整的事务状态。

【讨论】:

  • 另外,我很高兴你提到缓存。最初我是按照你建议的方式做的,但是因为我担心它会导致这个提交失败的混乱,所以我放弃了。一旦我得到它的工作,我会回去并发布一些适当的代码示例......
  • 好吧,我尝试了这两种方法 - 通过传入事务进行构造并设置属性 post-hoc。两者都没有缓解我的问题。谢谢你的建议!
【解决方案2】:

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

【讨论】:

  • 所以...问题只是锁定文件的另一个进程吗?
【解决方案3】:

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

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

解决方案是通过确保我们使用以下内容来确保 sqlite 数据库不直接参与环境事务:

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

【讨论】:

    【解决方案4】:

    注意事项:

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

    • 当病毒扫描程序检测到文件的更改并尝试对其进行扫描时,我已经看到了这种情况。它会在短时间内锁定文件并造成严重破坏。

    【讨论】:

      【解决方案5】:

      我今天开始面临同样的问题:我正在学习 asp.net mvc,完全从头开始构建我的第一个应用程序。有时,当我写入数据库时​​,我会得到同样的异常,说数据库文件被锁定。

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

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

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

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

      【讨论】:

        【解决方案6】:

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

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

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

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

        【讨论】:

          【解决方案7】:

          这些人遇到了类似的问题(主要是日志文件被锁定,可能是 TortoiseSVN 交互...查看参考文章)。

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


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

          异常进入 SQL Lite 期间是否有堆栈跟踪?

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

          【讨论】:

          • 使用 persist 可能降低了这个问题的频率,虽然没有消除它。 UserAppData 也是一个不错的选择。谢谢!
          • 那你会怎么想?虽然我确定这不应该是一个问题,但您是否可能将提交的时间太接近(时间上)?
          • 哎呀,我没有在上面看到你的 cmets。你在另一台机器上试过这个吗?是否存在内部异常?
          • 你试过截断吗?抛出异常时是否有堆栈跟踪?
          【解决方案8】:

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

          【讨论】:

          • 不幸的是,这并没有成功,但无论如何它可能是更好的方法,所以我已经相应地更新了我的代码......谢谢!
          【解决方案9】:

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

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

          要非常非常小心地为数据库命名,尤其是扩展名

          例如,如果您为所有数据库提供扩展名 .sdb(SQLite 数据库,好名字嘿?我还是这么想的,当我选择它时...)您会发现 SDB 扩展名已经与 APPFIX PACKAGES 相关联。

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

          这意味着,待在我这里,每次你向数据库写入任何内容时,Windows XP 系统都会认为该死的可执行文件发生了变化,并将整个 800 兆的数据库复制到系统还原目录......

          我推荐 DB 或 DAT 之类的东西。

          【讨论】:

            【解决方案10】:

            虽然在 COMMIT 上报告了锁,但锁在 INSERT/UPDATE 命令上。检查您的代码中是否有未在早期释放的记录锁。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2014-01-23
              • 2013-04-22
              • 2012-03-12
              • 2019-02-05
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多