System.Data.SQLite Close()不释放数据库文件


94

我在尝试删除文件之前关闭数据库时遇到问题。代码只是

 myconnection.Close();    
 File.Delete(filename);

并且Delete引发一个例外,表明该文件仍在使用中。几分钟后,我在调试器中重新尝试了Delete(),所以这不是时间问题。

我有交易代码,但是在Close()调用之前根本没有运行。因此,我相当确定这不是公开交易。打开和关闭之间的sql命令只是选择。

ProcMon显示我的程序和我的防病毒软件正在查看数据库文件。它没有显示我的程序在close()之后释放db文件。

Visual Studio 2010,C#,System.Data.SQLite版本1.0.77.0,Win7

我看到了一个类似两年的错误,但变更日志说它已修复。

还有什么我可以检查的吗?有没有办法获取任何打开的命令或事务的列表?


新的工作代码:

 db.Close();
 GC.Collect();   // yes, really release the db

 bool worked = false;
 int tries = 1;
 while ((tries < 4) && (!worked))
 {
    try
    {
       Thread.Sleep(tries * 100);
       File.Delete(filename);
       worked = true;
    }
    catch (IOException e)   // delete only throws this on locking
    {
       tries++;
    }
 }
 if (!worked)
    throw new IOException("Unable to close file" + filename);

您是否尝试过:myconnection.Close(); myconnection.Dispose(); ?
2013年

1
使用sqlite-net时,可以使用SQLiteAsyncConnection.ResetPool(),有关详细信息,请参见此问题
Uwe Keim

Answers:


110

前一阵子在为C#编写一个DB抽象层时遇到了同样的问题,而我却从来没有真正去发现问题所在。当您尝试使用我的库删除SQLite数据库时,我刚抛出一个异常。

无论如何,今天下午我一直在仔细检查所有内容,以为我会尝试找出为什么它会一劳永逸地做到这一点,所以这是到目前为止我所发现的。

调用时发生的事情SQLiteConnection.Close()是(连同许多检查和其他操作)SQLiteConnectionHandle指向SQLite数据库实例的处置。这是通过调用来完成的SQLiteConnectionHandle.Dispose(),但是直到CLR的垃圾收集器执行一些垃圾收集后,它才真正释放指针。由于SQLiteConnectionHandle覆盖了CriticalHandle.ReleaseHandle()要调用的函数sqlite3_close_interop()(通过另一个函数),因此不会关闭数据库。

从我的角度来看,这是一种非常糟糕的处理方式,因为程序员实际上并不确定数据库何时关闭,但这是已经完成的方式,所以我想我们现在必须使用它,或者提交对System.Data.SQLite进行了一些更改。欢迎任何志愿者这样做,很遗憾,我没有时间在明年之前这样做。

TL; DR 解决方案是在呼叫之后SQLiteConnection.Close()和之前强制执行GC File.Delete()

这是示例代码:

string filename = "testFile.db";
SQLiteConnection connection = new SQLiteConnection("Data Source=" + filename + ";Version=3;");
connection.Close();
GC.Collect();
GC.WaitForPendingFinalizers();
File.Delete(filename);

祝您好运,希望对您有所帮助


1
是! 谢谢!看起来GC可能需要一点时间才能完成工作。
汤姆·塞鲁

1
您可能还想看看C#SQLite,我已经将所有代码移到了使用它的位置。当然,如果您运行的是性能至关重要的产品,那么C可能比C#快,但是我是托管代码的忠实
拥护者

1
我知道这很老了,但是感谢您为我节省了一些痛苦。此错误也影响SQLite的Windows Mobile / Compact Framework构建。
StrayPointer 2013年

2
做得好!立即解决了我的问题。在11年的C#开发中,我从未需要使用GC.Collect:现在,这是我被迫使用的第一个示例。
Pilsator 2014年

10
GC.Collect(); 可以,但是System.Data.SQLite.SQLiteConnection.ClearAllPools(); 使用库的API处理问题。
亚伦·休顿

57

只是GC.Collect()没有为我工作。

我必须添加GC.WaitForPendingFinalizers()之后GC.Collect()才能继续删除文件。


5
这并不奇怪,GC.Collect()只需启动一个异步的垃圾回收,以确保清除了所有垃圾,您必须明确地等待它。
ChrisWue

2
我经历过同样的事情,不得不添加GC.WaitForPendingFinalizers()。这是在1.0.103中
Vort3x 2016年

18

就我而言,我在创建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)
    {
        using (var command = connection.CreateCommand())
        {
            command.CommandText = commandText;
            return command.ExecuteScalar();
        }
    }
}

using语句确保即使发生异常也将调用Dispose。

然后,执行命令也容易得多。

value = connection.ExecuteScalar(commandText)
// Command object created and disposed

6
我非常建议您避免此类吞咽异常
Tom McKearney 2013年

16

有一个类似的问题,尽管垃圾收集器解决方案没有解决它。

发现使用后的处置SQLiteCommandSQLiteDataReader对象根本就用垃圾收集器救了我。

SQLiteCommand command = new SQLiteCommand(sql, db);
command.ExecuteNonQuery();
command.Dispose();

2
究竟。SQLiteCommand即使SQLiteCommand以后回收变量,也要确保处置所有对象。
布鲁诺·比耶里

这对我有用。我还确保处理所有交易。
Jay-Nicolas Hackleman,

1
大!你救了我很多时间。当我添加command.Dispose();到每个SQLiteCommand执行的程序时,它修复了该错误。
伊万·B

另外,请确保释放(例如.Dispose())其他对象(如SQLiteTransaction)(如果有)。
伊万·B

13

以下为我工作:

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

更多信息:连接由SQLite合并以提高性能。这意味着当您在连接对象上调用Close方法时,与数据库的连接可能仍处于活动状态(在后台),因此下一个Open方法变得更快。如果您不再需要新的连接,则调用ClearAllPools将关闭所有在后台仍处于活动状态的连接,并释放与db文件的文件句柄,然后db文件可能会被删除,删除或由另一个进程使用。


1
您能否添加解释说明为什么这是解决问题的好方法。
Matas Vaitkevicius

您也可以使用SQLiteConnectionPool.Shared.Reset()。这将关闭所有打开的连接。特别是,如果您使用的SQLiteAsyncConnection是没有Close()方法的解决方案。
Lorenzo Polidori 2015年

9

我遇到了类似的问题,我尝试使用该解决方案,GC.Collect但是如上所述,文件锁定之前可能要花很长时间。

我发现了一个替代解决方案,涉及SQLiteCommand在TableAdapters中处置基础的s,有关其他信息,请参见此答案


你是对的!在某些情况下,简单的'GC.Collect'对我有用,在另一些情况下,我必须在调用GC.Collect之前处置与该连接关联的所有SqliteCommands,否则它将不起作用!
Eitan HS

1
在SQLiteCommand上调用Dispose对我有用。顺便提一句-如果您正在调用GC.Collect,则说明您做错了什么。
Natalie Adams

@NathanAdams在使用EntityFramework时,没有一个可以处置的命令对象。因此,无论是EntityFramework本身还是EF包装程序的SQLite都在做某些错误。
springy76

您的答案应该是正确的。非常感谢。
艾哈迈德·沙梅尔

5

试试这个...这个尝试上面所有的代码 ...对我有用

    Reader.Close()
    connection.Close()
    GC.Collect()
    GC.WaitForPendingFinalizers()
    command.Dispose()
    SQLite.SQLiteConnection.ClearAllPools()

希望有帮助


1
WaitForPendingFinalizers使所有的差异,我
托德

5

我在EF和上遇到了同样的问题System.Data.Sqlite

对我来说,我发现SQLiteConnection.ClearAllPools()GC.Collect()可以减少文件锁定发生的频率,但是仍然偶尔发生(大约1%的时间)。

我一直在调查,似乎是SQLiteCommandEF创建的某些s没有被丢弃,并且仍将其Connection属性设置为关闭的连接。我尝试处置这些,但是实体框架在下一次DbContext读取时会引发异常-似乎EF有时在关闭连接后仍会使用它们。

我的解决方案是确保Null在这些连接上关闭连接时将Connection属性设置为SQLiteCommand。这似乎足以释放文件锁定。我一直在测试以下代码,经过数千次测试后未发现任何文件锁定问题:

public static class ClearSQLiteCommandConnectionHelper
{
    private static readonly List<SQLiteCommand> OpenCommands = new List<SQLiteCommand>();

    public static void Initialise()
    {
        SQLiteConnection.Changed += SqLiteConnectionOnChanged;
    }

    private static void SqLiteConnectionOnChanged(object sender, ConnectionEventArgs connectionEventArgs)
    {
        if (connectionEventArgs.EventType == SQLiteConnectionEventType.NewCommand && connectionEventArgs.Command is SQLiteCommand)
        {
            OpenCommands.Add((SQLiteCommand)connectionEventArgs.Command);
        }
        else if (connectionEventArgs.EventType == SQLiteConnectionEventType.DisposingCommand && connectionEventArgs.Command is SQLiteCommand)
        {
            OpenCommands.Remove((SQLiteCommand)connectionEventArgs.Command);
        }

        if (connectionEventArgs.EventType == SQLiteConnectionEventType.Closed)
        {
            var commands = OpenCommands.ToList();
            foreach (var cmd in commands)
            {
                if (cmd.Connection == null)
                {
                    OpenCommands.Remove(cmd);
                }
                else if (cmd.Connection.State == ConnectionState.Closed)
                {
                    cmd.Connection = null;
                    OpenCommands.Remove(cmd);
                }
            }
        }
    }
}

要使用,只需ClearSQLiteCommandConnectionHelper.Initialise();在应用程序加载开始时调用。然后,这将保留活动命令的列表,并将它们的“连接”设置为Null指向关闭的连接。


我还必须在此的DisposedCommand部分中将连接设置为null,否则我偶尔会得到ObjectDisposedExceptions。
Elliot '18年

我认为这是一个被低估的答案。它解决了由于EF层而无法完成的清理问题。很高兴在丑陋的GC hack上使用它。谢谢!
杰森·泰勒

如果在多线程环境中使用此解决方案,则OpenCommands列表应为[ThreadStatic]。
贝罗


3

遇到类似的问题。致电垃圾收集器并没有帮助我。后来我找到了解决问题的方法

作者还写道,在尝试删除该数据库之前,他已对该数据库执行SELECT查询。我有同样的情况。

我有以下代码:

SQLiteConnection bc;
string sql;
var cmd = new SQLiteCommand(sql, bc);
SQLiteDataReader reader = cmd.ExecuteReader();
reader.Read();
reader.Close(); // when I added that string, the problem became solved.

另外,我不需要关闭数据库连接并调用垃圾收集器。我要做的就是关闭执行SELECT查询时创建的阅读器


2

我相信致电SQLite.SQLiteConnection.ClearAllPools()是最干净的解决方案。据我所知GC.Collect(),在WPF环境中手动调用是不合适的。虽然,直到System.Data.SQLite3/2016中升级到1.0.99.0 之前,我都没有注意到问题。


2

也许您根本不需要处理GC。请检查是否全部sqlite3_prepare确定。

对于每个sqlite3_prepare,您都需要一个通讯员sqlite3_finalize

如果未正确完成,sqlite3_close将不会关闭连接。


1

我正在努力解决类似的问题。真可惜...我终于意识到读者没有关闭。由于某种原因,我当时认为关闭相应的连接后将关闭阅读器。显然,GC.Collect()对我不起作用。
用“ using:”语句包装Reader也是一个好主意。这是一个快速的测试代码。

static void Main(string[] args)
{
    try
    {
        var dbPath = "myTestDb.db";
        ExecuteTestCommand(dbPath);
        File.Delete(dbPath);
        Console.WriteLine("DB removed");
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }
    Console.Read();
}

private static void ExecuteTestCommand(string dbPath)
{
    using (var connection = new SQLiteConnection("Data Source=" + dbPath + ";"))
    {
        using (var command = connection.CreateCommand())
        {
            command.CommandText = "PRAGMA integrity_check";
            connection.Open();
            var reader = command.ExecuteReader();
            if (reader.Read())
                Console.WriteLine(reader.GetString(0));

            //without next line database file will remain locked
            reader.Close();
        }
    }   
}

0

我在EF6上使用SQLite 1.0.101.0,并且在处理所有连接和实体后文件被锁定时遇到麻烦。

随着EF的更新使数据库完成后保持锁定状态,情况变得更糟。GC.Collect()是唯一有用的解决方法,我开始感到绝望。

无奈之下,我尝试了奥利弗·维肯登(Oliver Wickenden)的ClearSQLiteCommandConnectionHelper(请参阅他7月8日的回答)。太棒了 所有锁定问题都消失了!谢谢奥利弗。


我认为这应该是评论而不是答案
Kevin Wallis

1
凯文(Kevin),我同意,但是由于我需要50的声誉(显然),所以不允许我发表评论。
托尼·沙利文

0

等待Garbage Collector可能不会一直释放数据库,这发生在我身上。例如,当SQLite数据库中发生某种类型的Exception(异常),尝试插入具有PrimaryKey的现有值的行时,它将保留数据库文件,直到您将其处置为止。以下代码捕获SQLite异常并取消有问题的命令。

SQLiteCommand insertCommand = connection.CreateCommand();
try {
    // some insert parameters
    insertCommand.ExecuteNonQuery();
} catch (SQLiteException exception) {
    insertCommand.Cancel();
    insertCommand.Dispose();
}

如果您不处理有问题的命令的异常,则垃圾收集器将无法对其进行任何处理,因为这些命令存在一些未处理的异常,因此它们不会成为垃圾。这种处理方法对我等待垃圾收集器效果很好。


0

这对我有用,但是我注意到有时关闭进程时,日记文件-wal -shm不会被删除。如果您希望SQLite在所有连接都关闭时删除-wal -shm文件,则最后关闭的连接必须为非只读。希望这会帮助某人。


0

对我有用的最佳答案。

dbConnection.Close();
System.Data.SQLite.SQLiteConnection.ClearAllPools();

GC.Collect();
GC.WaitForPendingFinalizers();

File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.