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

时间:2009-08-03 20:01:11

标签: c# 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是本地文件。
  • 所有这些活动都封装在一个线程/进程中。
  • 病毒防护已关闭(虽然我认为仅在通过网络连接时才相关?)。
  • 根据苏格兰人的帖子,我实施了以下更改:
  • 日记帐模式设为持续
  • 通过System.Windows.Forms.Application.AppData Windows调用
  • 存储在C:\ Docs + Settings \ ApplicationData中的数据库文件
  • 无内部异常
  • 见证了两台不同的机器(尽管硬件和软件非常相似)
  • 一直在运行进程监视器 - 没有多余的进程将自己附加到数据库文件 - 问题肯定在我的代码中......

有没有人知道这里有什么想法?

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

布赖恩

更新

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

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

欢迎提出更多建议!

10 个答案:

答案 0 :(得分:9)

您似乎无法将命令与您创建的事务相关联。 而不是:

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日记机制来存储未完成的事务状态。

答案 1 :(得分:5)

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

答案 2 :(得分:4)

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

基本上有一个交易位于链条的上方,通过ado的魔力,连接自动加入。结果是,即使我们认为我们在一个线程上写入数据库,写入也没有确实发生,直到最顶层的交易提交。在这个'不确定'点,数据库被写入,导致它被锁定在我们无法控制之外。

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

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

答案 3 :(得分:3)

值得关注的事项:

  • 不要在多个线程/进程之间使用连接。

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

答案 4 :(得分:3)

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

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

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

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

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

答案 5 :(得分:2)

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

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

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

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

答案 6 :(得分:2)

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

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


此处讨论日记帐模式选项:http://www.sqlite.org/pragma.html。你可以试试TRUNCATE。

在SQL Lite异常期间是否存在堆栈跟踪?

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

答案 7 :(得分:2)

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

答案 8 :(得分:2)

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

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

  

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

     

例如,如果你给你的所有数据库扩展名.sdb(SQLite数据库,好名字嘿?我当时选择它就这么认为......)你发现SDB扩展已经与APPFIX PACKAGES关联了。 / p>      

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

     

这意味着,请留在这里,每当您向数据库写任何内容时,Windows XP系统都认为血腥可执行文件已更改,并将您的ENTIRE 800 meg数据库复制到系统还原目录....

     

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

答案 9 :(得分:0)

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