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

C# 数据库文件在SQLite提交期间被莫名其妙地锁定,c#,sqlite,C#,Sqlite,我正在对SQLite数据库执行大量插入。我只用一根线。我对写操作进行批处理以提高性能,并在崩溃时提供一点安全性。基本上,我在内存中缓存了一堆数据,然后在我认为合适的时候,循环所有这些数据并执行插入。其代码如下所示: public void Commit() { using (SQLiteConnection conn = new SQLiteConnection(this.connString)) { conn.Open()

我正在对SQLite数据库执行大量插入。我只用一根线。我对写操作进行批处理以提高性能,并在崩溃时提供一点安全性。基本上,我在内存中缓存了一堆数据,然后在我认为合适的时候,循环所有这些数据并执行插入。其代码如下所示:

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

我知道我刚刚扔掉了一堆乱七八糟的代码,但我一直在努力解决这个问题,时间太长了。我要感谢所有能把这个问题讲完的人

布莱恩

更新:

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

上面的代码在技术上是可行的,但是它是不确定的!它不能保证永远在中立状态下除了旋转之外还能做任何事情。实际上,它似乎在第1次迭代和第10次迭代之间的某个地方起作用。如果我在合理的时间间隔内批量提交,损害将会减轻,但我真的不想让事情处于这种状态


欢迎更多建议

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

您应该在每个线程中创建一个新连接。我将简化连接的创建,在任何地方使用:connection=newsqliteconnection(connString.ToString())

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

为什么要使用两种不同的方式创建连接

需要注意的事项:

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

  • 我见过这样的情况:病毒扫描程序会检测到文件的更改并尝试扫描它。它会在很短的时间间隔内锁定文件并造成严重破坏


这些家伙也遇到了类似的问题(看起来,大多数情况下,日志文件被锁定,可能是龟的互动……请查看参考文章)

他们提出了一套建议(正确的目录,将日志类型从删除改为持久化,等等)


此处讨论了日志模式选项:。你可以试试截断

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


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

在运行程序时运行文件名并对其进行筛选,以排除是否有其他进程对其进行了任何操作,并查看您的程序对文件的具体操作。很长,但可能会给出一个线索。

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

你有运行谷歌桌面搜索(或其他文件索引器)吗?如前所述,可以帮助您找到它

另外,数据库的文件名是什么?发件人:

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

例如,如果您为所有数据库提供扩展名.sdb(SQLite数据库,很好的名称,嘿?我还是这么认为的,当我选择它的时候…),您会发现sdb扩展名已经与APPFIX包关联

现在,这里是最可爱的部分,APPFIX是一个Windows XP可以识别的可执行文件/包,它将(强调我的)将数据库添加到系统还原功能中

这意味着,请跟我在一起,每次您向数据库写入任何内容时,Windows XP系统都会认为一个血腥的可执行文件已更改,并将您的整个800兆数据库复制到系统还原目录

我推荐DB或DAT之类的产品


看起来您未能将该命令与已创建的事务相链接。 而不是:

using (SQLiteCommand command = conn.CreateCommand())
你应使用:

using (SQLiteCommand command = new SQLiteCommand("<INSERT statement here>", conn, trans))

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

我们在使用TransactionScope类的嵌套事务时遇到了非常类似的问题。我们认为所有数据库操作都发生在同一个线程上…但是我们被事务机制捕获了…更具体地说是
   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;
            }
        }
    }
using(TransactionScope scope = new TransactionScope(TransactionScopeOptions.RequiresNew))
{
  ...
  scope.Complete()
}