【问题标题】:SQLite keeps the database locked even after the connection is closed即使在连接关闭后,SQLite 也会保持数据库锁定
【发布时间】:2012-09-14 00:08:46
【问题描述】:

我在 ASP.NET 应用程序(框架 4.0)中使用 System.Data.SQLite 提供程序。 我遇到的问题是,当我在 SQLite 数据库的表中插入某些内容时,数据库被锁定并且即使在连接被释放后锁定也没有被释放。

尝试访问该文件时,错误是:“进程无法访问文件'catalog.sqlite',因为它正在被另一个进程使用。”

我的代码非常简单,我打开连接,从 SQLServer 数据库读取一些数据,将该数据插入 SQLite(通过 SQLiteDataAdapter),然后关闭连接并处理所有内容以确保安全。但是,当我在填充数据后尝试压缩文件时,仍会出现该错误。

我在 StackOverflow 上阅读了各种建议,但没有一个有助于解决问题(关闭防病毒软件、更改事务模型、在压缩文件之前等待几秒钟、将所有插入调用包装到交易等。但没有一个有助于解决这个问题。

也许有一些特定于 ASP.NET 的东西(多线程是问题?即使我在开发机器上测试它,只有一次调用该函数并且没有并发?)

作为旁注,我尝试避免使用 DataTable 和 SQLiteDataAdapter 并直接使用 SQLiteCommand,这样它就很有魅力。当然,我可以继续将查询构建为字符串,而不是使用数据适配器,但是当有一个框架可以做到这一点时,我觉得有点尴尬。

【问题讨论】:

  • 你是否将命令和连接包装在 using 语句中?
  • 这个锁是你的应用程序的,也就是说,锁在退出时会消失吗?
  • @Arran 我没有使用 using 来包装连接。但即使使用没有锁定的版本(也就是使用命令而不是数据适配器的版本),我也不会包装它。
  • @CL。是的,锁定是由于我的申请。如果我停止开发 Web 服务器,文件将被解锁。
  • 请显示来源,至少您如何创建和关闭/处置所有对象。

标签: c# sqlite system.data.sqlite


【解决方案1】:

以下内容对我有用:

MySQLiteConnection.Close();
SQLite.SQLiteConnection.ClearAllPools()

【讨论】:

    【解决方案2】:

    遇到了同样的问题。 只需安装 SQLite 1.0.111(通过 nuget)即可解决。

    不必更改我的代码上的任何内容,只需更新到该版本即可。以前我使用 1.0.85 文件被锁定,虽然连接被关闭和释放。

    【讨论】:

      【解决方案3】:

      我只是在锁定计算机时遇到了这里提到的问题,即使在解锁之后,它工作正常,否则很幸运,因为我最近才开始锁定它,而软件在几天前才发布,还没有人知道它。

      无论如何,我拥有关闭连接和 ClearAllPools 等所有东西,但缺少 aTableAdapter.Adapter.Dispose() 并修复了它。

      【讨论】:

        【解决方案4】:

        我遇到了同样的问题,只能通过在 using 语句中处理 DbCommand 来解决,但使用 Pooling = true 我的问题已解决!!

                        SQLiteConnectionStringBuilder builder = new SQLiteConnectionStringBuilder
                        {
                            Pooling = true
                        };
        

        【讨论】:

          【解决方案5】:

          如前所述,SQLite 对象必须被销毁。但是,有一个奇怪的行为:在调用 Dispose 命令期间必须打开连接。例如:

          using(var connection = new SqliteConnection("source.db"))
          {
              connection.Open();
              using(var command = connection.CreateCommand("select..."))
              {
                  command.Execute...
              }
          }
          

          工作正常,但是:

          using(var connection = new SqliteConnection("source.db"))
          {
              connection.Open();
              using(var command = connection.CreateCommand("select..."))
              {
                  command.Execute...
                  connection.Close();
              }
          }
          

          提供相同的文件锁

          【讨论】:

            【解决方案6】:

            确保正确处理任何 IDisposable(例如 SQLiteConnection、SQLiteCommand 等)可以解决此问题。我应该重申,必须将“使用”作为一种习惯,以确保妥善处理一次性资源。

            【讨论】:

              【解决方案7】:

              在我的例子中,我创建了 SQLiteCommand 对象而没有明确地处理它们。

              var command = connection.CreateCommand();
              command.CommandText = commandText;
              value = command.ExecuteScalar();
              

              我将命令包装在 using 语句中,它解决了我的问题。

              static public class SqliteExtensions
              {
                  public static object ExecuteScalar(this SQLiteConnection connection, string commandText)
                  {
                      // Added using
                      using (var command = connection.CreateCommand())
                      {
                          command.CommandText = commandText;
                          return command.ExecuteScalar();
                      }
                  }
              }
              

              那你就可以这样使用了

              connection.ExecuteScalar(commandText);
              

              【讨论】:

              • 我强烈建议不要吞下这样的异常!
              • 这究竟是如何吞下异常的?当我使用它时,如果命令失败,我会在 using 语句的末尾抛出异常。
              • 我删除了吞噬异常的代码,所以它不再这样做了。如果您真的想吞下异常,可以在 using 周围的代码中添加Try-Catch block
              【解决方案8】:

              我也有同样的问题。我的场景是在获取 SQLite 数据库文件中的数据后,我想删除该文件,但它总是抛出错误“...using by other process”。即使我处理了 SqliteConnection 或 SqliteCommand 错误仍然发生。我已通过调用GC.Collect() 修复了该错误。

              代码 sn-p

              public void DisposeSQLite()
              {
                  SQLiteConnection.Dispose();
                  SQLiteCommand.Dispose();
              
                  GC.Collect();
              }
              

              希望对您有所帮助。

              【讨论】:

              • 我也遇到了同样的问题,我不得不使用 GC.Collect();参考:system.data.sqlite.org/index.html/tktview/…希望下个Sqlite版本能解决这个问题。
              • @klaydze @Assassinbeast 处理您提到的问题的一种更简洁的方法是调用 System.Data.SQLite.SQLiteConnection.ClearAllPools(); 而不是两个 dispose 调用 + GC.Collect
              • 在某些情况下需要等待GC任务完成才能使用带有GC.WaitForPendingFinalizers()的sqlite文件
              【解决方案9】:

              这是我遇到此错误时发现的最佳 Google 搜索结果之一。但是,没有任何回复对我有帮助,因此在进行了更多搜索和谷歌搜索后,我想出了这段代码,该代码适用于 http://www.tsjensen.com/blog/post/2012/11/10/SQLite-on-Visual-Studio-with-NuGet-and-Easy-Instructions.aspx 的一些代码

              但是,我根本不必使用 NuGet。我的程序所做的是每次打开服务器时都会从服务器下载一个 db 文件。然后,如果用户更新了该数据库,它将被上传给每个人,以便他们下次打开同一个程序时获取。在更新本地文件并尝试将其上传到我们的 SharePoint 后,我​​收到文件正在使用中的错误。现在可以正常使用了。

              Public Function sqLiteGetDataTable(sql As String) As DataTable
                  Dim dt As New DataTable()
                  Using cnn = New SQLiteConnection(dbConnection)
                      cnn.Open()
                      Using cmd As SQLiteCommand = cnn.CreateCommand()
                          cmd.CommandText = sql
                          Using reader As System.Data.SQLite.SQLiteDataReader = cmd.ExecuteReader()
                              dt.Load(reader)
                              reader.Dispose()
                          End Using
                          cmd.Dispose()
                      End Using
                      If cnn.State <> System.Data.ConnectionState.Closed Then
                          cnn.Close()
                      End If
                      cnn.Dispose()
                  End Using
                  Return dt
              End Function
              

              【讨论】:

                【解决方案10】:

                在大多数情况下,如果您没有正确配置阅读器和命令,就会出现问题。存在命令和读取器无法正确处理的情况。

                场景 1: 如果您正在运行 boolean 函数。在达到结果之前,finally 块中的代码不会执行。如果您要在执行代码时评估函数 isDataExists 的结果(如果它适合结果),那么这是一个大问题,即

                    if(isDataExists){
                        // execute some code
                    }
                

                正在评估的函数

                    public bool isDataExists(string sql)
                    {
                        try
                        {
                            OpenConnection();
                            SQLiteCommand cmd = new SQLiteCommand(sql, connection);
                            reader = cmd.ExecuteReader();
                            if (reader != null && reader.Read())
                            {
                                return true;
                            }
                            else
                            {
                                return false;
                            }
                        }
                        catch (Exception expMsg)
                        {
                            //Exception
                        }
                        finally
                        {
                            if (reader != null)
                            {
                                reader.Dispose();
                            }
                            CloseConnection();
                        }
                        return true;
                    }
                

                解决方案:将阅读器和命令放在 try 块中,如下所示

                            OpenConnection();
                            SQLiteCommand cmd = new SQLiteCommand(sql, connection);
                            reader = cmd.ExecuteReader();
                            if (reader != null && reader.Read())
                            {
                                cmd.Dispose();
                                CloseConnection();
                                return true;
                            }
                            else
                            {
                                cmd.Dispose();
                                CloseConnection();
                                return false;
                            }
                

                最后处置阅读器和命令,以防万一出现问题

                        finally
                        {
                            if (reader != null)
                            {
                                reader.Dispose();
                            }
                            CloseConnection();
                        }
                

                【讨论】:

                • 如果您在try 块中释放命令,在ExecuteReader() 期间发生的异常将导致命令不被释放。你应该使用 using 块,或者如果你更喜欢自己写出来,你可以嵌套多个 try/finally 块。
                【解决方案11】:

                我在使用System.Data.Sqlite.dll 1.0.82.0 版附带的设计器生成的数据集/表格适配器时遇到了同样的问题——关闭连接后,我们无法使用System.IO.FileStream 读取数据库文件。我正确地处理了连接和表格适配器,但我没有使用连接池。

                根据我的第一次搜索(例如thisthis thread),库本身似乎存在问题——对象未正确释放和/或池问题(我不使用)。

                阅读您的问题后,我尝试仅使用 SQLiteCommand 对象来复制问题,但我发现当您不处置它们时会出现问题。 更新 2012-11-27 19:37 UTCthis ticket 进一步证实了 System.Data.SQLite,其中开发人员解释说“所有 SQLiteCommand 和与连接关联的 SQLiteDataReader 对象 [应该] 正确处理”。

                然后我打开生成的 TableAdapters,我看到没有实现 Dispose 方法——所以实际上创建的命令没有被释放。我实现了它,负责处理所有命令,我没有问题。

                这是 C# 中的代码,希望对您有所帮助。请注意,代码是从original in Visual Basic 转换而来的,因此可能会出现一些转换错误。

                //In Table Adapter    
                protected override void Dispose(bool disposing)
                {
                   base.Dispose(disposing);
                
                    Common.DisposeTableAdapter(disposing, _adapter, _commandCollection);
                }
                
                public static class Common
                {
                    /// <summary>
                    /// Disposes a TableAdapter generated by SQLite Designer
                    /// </summary>
                    /// <param name="disposing"></param>
                    /// <param name="adapter"></param>
                    /// <param name="commandCollection"></param>
                    /// <remarks>You must dispose all the command,
                    /// otherwise the file remains locked and cannot be accessed
                    /// (for example, for reading or deletion)</remarks>
                    public static void DisposeTableAdapter(
                        bool disposing,
                        System.Data.SQLite.SQLiteDataAdapter adapter,
                        IEnumerable<System.Data.SQLite.SQLiteCommand> commandCollection)
                    {
                        if (disposing) {
                            DisposeSQLiteTableAdapter(adapter);
                
                            foreach (object currentCommand_loopVariable in commandCollection)
                            {
                                currentCommand = currentCommand_loopVariable;
                                currentCommand.Dispose();
                            }
                        }
                    }
                
                    public static void DisposeSQLiteTableAdapter(
                            System.Data.SQLite.SQLiteDataAdapter adapter)
                    {
                        if (adapter != null) {
                            DisposeSQLiteTableAdapterCommands(adapter);
                
                            adapter.Dispose();
                        }
                    }
                
                    public static void DisposeSQLiteTableAdapterCommands(
                            System.Data.SQLite.SQLiteDataAdapter adapter)
                    {
                        foreach (object currentCommand_loopVariable in {
                            adapter.UpdateCommand,
                            adapter.InsertCommand,
                            adapter.DeleteCommand,
                            adapter.SelectCommand})
                        {
                            currentCommand = currentCommand_loopVariable;
                            if (currentCommand != null) {
                                currentCommand.Dispose();
                            }
                        }
                    }
                }
                

                2013-07-05 17:36 UTC 更新gorogm's answer 强调了两件重要的事情:

                • 根据 System.Data.SQLite 官方网站上的changelog,从版本 1.0.84.0 开始,应该不需要上面的代码,因为库会处理这个问题。我没有测试过这个,但在最坏的情况下你只需要这个 sn-p:

                  //In Table Adapter    
                  protected override void Dispose(bool disposing)
                  {
                    base.Dispose(disposing);
                  
                    this.Adapter.Dispose();
                  }
                  
                • 关于TableAdapterDispose 调用的实现:最好将其放在部分类中,这样数据集重新生成不会影响此代码(以及您可能使用的任何其他代码)需要补充)。

                【讨论】:

                • 我看到使用 Entity 框架和最新的 1.82.0 sqlite 的内存泄漏非常大。你认为这是问题所在吗?
                • 可能,因为(我认为,我在 EF 方面的经验很少)可能有与 SQLiteCommands 相关的非托管资源等待释放。 This SO threadthis ticket 似乎证实了你的假设。为了避免泄漏,您可以尝试在 EF 中禁用“多个活动结果集”,或者您可以尝试使用像 C#-SQLite 这样的托管库。希望这会有所帮助。
                【解决方案12】:

                我发现 edymtt 关于责备 TableAdapters / Datasets 的答案是正确的,但我没有修改每次重新生成的 TableAdapter 代码文件,而是找到了另一个解决方案:手动调用 TableAdapter 的子元素上的 .Dispose。 (在 .NET 4.5 中,最新的 SQLite 1.0.86)

                using (var db = new testDataSet())
                {
                    using (testDataSetTableAdapters.UsersTableAdapter t = new testDataSetTableAdapters.UsersTableAdapter())
                    {
                        t.Fill(db.Users);
                        //One of the following two is enough
                        t.Connection.Dispose(); //THIS OR
                        t.Adapter.Dispose();    //THIS LINE MAKES THE DB FREE
                    }
                    Console.WriteLine((from x in db.Users select x.Username).Count());
                }
                

                【讨论】:

                • 当我在设计器中更改数据集时,我不必修改TableAdapters -- 我已经使用partial classes 实现了Dispose。感谢您指出在最新版本的 System.Data.SQLite 中不再需要我的代码(请参阅 changelog 了解 1.0.84 版。
                猜你喜欢
                • 2011-02-16
                • 1970-01-01
                • 1970-01-01
                • 2019-09-11
                • 1970-01-01
                • 1970-01-01
                • 2016-02-10
                • 2015-09-10
                • 1970-01-01
                相关资源
                最近更新 更多