System.Data.SQLite Close ()가 데이터베이스 파일을 해제하지 않습니다.


95

파일을 삭제하기 전에 데이터베이스를 닫는 데 문제가 있습니다. 코드는

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

그리고 Delete는 파일이 아직 사용 중이라는 예외를 발생시킵니다. 몇 분 후에 디버거에서 Delete ()를 다시 시도 했으므로 타이밍 문제가 아닙니다.

트랜잭션 코드가 있지만 Close () 호출 전에 전혀 실행되지 않습니다. 그래서 나는 그것이 공개 거래가 아니라고 확신합니다. 열기와 닫기 사이의 SQL 명령은 선택 사항입니다.

ProcMon은 데이터베이스 파일을보고있는 프로그램과 바이러스 백신을 보여줍니다. close () 후에 db 파일을 해제하는 프로그램이 표시되지 않습니다.

Visual Studio 2010, C #, System.Data.SQLite 버전 1.0.77.0, Win7

이와 같은 2 년 된 버그를 보았지만 변경 로그에 수정되었다고 표시됩니다.

내가 확인할 수있는 다른 것이 있습니까? 열려있는 명령 또는 트랜잭션 목록을 얻는 방법이 있습니까?


새로운 작동 코드 :

 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 (); ?
UGEEN

1
sqlite-net을 사용할 때를 사용할 수 있습니다 . 자세한 내용 SQLiteAsyncConnection.ResetPool()이 문제 를 참조하세요.
Uwe Keim 2016

답변:


110

얼마 전 C # 용 DB 추상화 계층을 작성하는 동안 동일한 문제가 발생했으며 실제로 문제가 무엇인지 알아 내지 못했습니다. 내 라이브러리를 사용하여 SQLite DB를 삭제하려고 할 때 예외가 발생했습니다.

어쨌든, 오늘 오후에 나는 모든 것을 다시 살펴 보았고 그것이 왜 단번에 그렇게했는지 알아 내려고 노력할 것이라고 생각했습니다. 그래서 여기에 제가 지금까지 찾은 것이 있습니다.

호출 할 때 발생 SQLiteConnection.Close()하는 일은 SQLiteConnectionHandleSQLite 데이터베이스 인스턴스를 가리키는 여러 검사 및 기타 사항과 함께 삭제 됩니다. 이 작업은에 대한 호출을 통해 수행 SQLiteConnectionHandle.Dispose()되지만 CLR의 가비지 수집기가 일부 가비지 수집을 수행 할 때까지 실제로 포인터를 해제하지 않습니다. 호출 SQLiteConnectionHandleCriticalHandle.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가 작업을 완료하는 데 약간이 필요할 수 있습니다.
Tom Cerul 2011

1
C # SQLite를보고 싶을 수도 있습니다. 방금 모든 코드를 사용하도록 옮겼습니다. 물론, 당신이 뭔가 성능 중요한 다음 C를 실행하는 경우 아마 빠른 C #을보다,하지만 난 관리 코드의 팬 ...이다
벤자민 Pannell

1
오래되었다는 건 알지만 고통을 덜어 주셔서 감사합니다. 이 버그는 SQLite의 Windows Mobile / Compact Framework 빌드에도 영향을줍니다.
StrayPointer 2013

2
훌륭한 일! 내 문제를 즉시 해결했습니다. 11 년 동안 C # 개발을하면서 GC.Collect를 사용할 필요가 없었습니다. 이제 이것이 제가 그렇게해야하는 첫 번째 예입니다.
Pilsator 2014-10-17

10
GC.Collect (); 작동하지만 System.Data.SQLite.SQLiteConnection.ClearAllPools (); 라이브러리의 API를 사용하여 문제를 처리합니다.
Aaron Hudon 2015-08-06

57

그냥 GC.Collect()나를 위해 작동하지 않았다.

파일 삭제를 진행하기 위해 GC.WaitForPendingFinalizers()나중에 추가해야했습니다 GC.Collect().


5
이것은 놀라운 일이 아닙니다. GC.Collect()비동기식 가비지 콜렉션을 시작하기 만하면 모든 것이 정리되었는지 확인하려면 명시 적으로 기다려야합니다.
ChrisWue

2
나는 똑같은 경험을했고 GC.WaitForPendingFinalizers ()를 추가해야했습니다. 1.0.103
Vort3x

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
난 아주 많이 이런 삼키는 예외에 대해 추천
톰 McKearney

17

유사한 문제가 있었지만 가비지 수집기 솔루션으로 해결되지 않았습니다.

사용 후 폐기 SQLiteCommandSQLiteDataReader개체를 발견 하면 가비지 수집기를 사용하여 전혀 구할 수 없었습니다.

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

2
바로 그거죠. 나중에 변수 SQLiteCommand를 재활용하더라도 모든 것을 폐기 해야합니다 SQLiteCommand.
브루노 Bieri

이것은 나를 위해 일했습니다. 나는 또한 모든 거래를 처리했습니다.
Jay-Nicolas Hackleman

1
큰! 당신은 저를 꽤 많은 시간을 절약했습니다. 실행 된 command.Dispose();모든 항목에 추가 할 때 오류가 수정되었습니다 SQLiteCommand.
Ivan B

또한 .Dispose()SQLiteTransaction과 같은 다른 개체가있는 경우 릴리스 (예 :)해야합니다.
Ivan B

13

다음은 나를 위해 일했습니다.

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

추가 정보 : 연결은 성능 향상을 위해 SQLite에 의해 풀링됩니다. 즉, 연결 개체에서 Close 메서드를 호출하면 데이터베이스에 대한 연결이 여전히 활성화되어 (백그라운드에서) 다음 Open 메서드가 더 빨라질 수 있습니다. 더 이상 새 연결을 원하지 않는 경우 ClearAllPools를 호출하면 백그라운드에 살아있는 모든 연결이 닫히고 db 파일에 대한 파일 핸들이 해제됩니다. 그런 다음 db 파일이 제거, 삭제 또는 다른 프로세스에서 사용될 수 있습니다.


1
이것이 문제에 대한 좋은 해결책 인 이유에 대한 설명을 추가해 주시겠습니까?
Matas Vaitkevicius 2014

당신은 또한 사용할 수 있습니다 SQLiteConnectionPool.Shared.Reset(). 이렇게하면 열려있는 모든 연결이 닫힙니다. 특히 방법 SQLiteAsyncConnection이없는 경우 사용 하는 경우의 솔루션 Close()입니다.
Lorenzo Polidori 2015

9

비슷한 문제가 발생하여 해결책을 시도했지만 GC.Collect언급했듯이 파일이 잠기지 않는 데 오랜 시간이 걸릴 수 있습니다.

SQLiteCommandTableAdapters에서 기본 s 처리와 관련된 대체 솔루션을 찾았습니다 . 자세한 내용 은 이 답변 을 참조하십시오 .


당신이 옳았! 어떤 경우에는 간단한 'GC.Collect'가 저에게 효과가 있었고 다른 경우에는 GC.Collect를 호출하기 전에 연결과 관련된 모든 SqliteCommand를 처리해야했습니다. 그렇지 않으면 작동하지 않습니다!
Eitan HS

1
SQLiteCommand에서 Dispose를 호출하는 것이 저에게 효과적이었습니다. 제쳐두고-GC.Collect에 전화하는 경우 뭔가 잘못하고 있습니다.
Natalie Adams

@NathanAdams는 EntityFramework로 작업 할 때 처리 할 수있는 단일 명령 개체가 없습니다. 따라서 EntityFramework 자체 또는 EF 래퍼 용 ​​SQLite도 일부 잘못하고 있습니다.
springy76

귀하의 대답은 올바른 것이어야합니다. 감사합니다.
Ahmed Shamel 2017

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가 생성 하는 일부 는 폐기되지 않고 여전히 연결 속성이 닫힌 연결로 설정된 것 같습니다. 나는 이것을 폐기하려고 시도했지만 Entity Framework는 다음 DbContext읽기 중에 예외를 throw합니다. EF는 연결이 닫힌 후에도 여전히 사용하는 것 같습니다.

내 해결책은 Null연결이 이러한 SQLiteCommands에서 닫힐 때 Connection 속성이 설정되어 있는지 확인하는 것 입니다. 이것은 파일 잠금을 해제하기에 충분한 것 같습니다. 아래 코드를 테스트했지만 수천 번의 테스트 후에도 파일 잠금 문제를 보지 못했습니다.

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닫힌 연결을 가리킬 때 연결이로 설정됩니다 .


또한 DisposingCommand 부분에서 연결을 null로 설정해야했습니다. 그렇지 않으면 가끔 ObjectDisposedExceptions가 발생합니다.
Elliot

이것은 내 의견으로는 과소 평가 된 답변입니다. EF 레이어로 인해 스스로 할 수 없었던 정리 문제를 해결했습니다. 추악한 GC 해킹에 이것을 사용하게되어 매우 기쁩니다. 감사합니다!
Jason Tyler

다중 스레드 환경에서이 솔루션을 사용하는 경우 OpenCommands 목록은 [ThreadStatic]이어야합니다.
Bero

3

사용하다 GC.WaitForPendingFinalizers()

예:

Con.Close();  
GC.Collect();`
GC.WaitForPendingFinalizers();
File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");

3

비슷한 문제가있었습니다. Garbage Collector를 호출해도 도움이되지 않았습니다. 나중에 문제를 해결할 방법을 찾았습니다

작성자는 또한 데이터베이스를 삭제하기 전에 해당 데이터베이스에 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.SQLite2016 년 3 월에 1.0.99.0으로 업그레이드 할 때까지 문제를 인지하지 못했습니다.


2

GC를 전혀 다룰 필요가 없을 수도 있습니다. 모든 sqlite3_prepare것이 완료 되었는지 확인하십시오 .

각각 sqlite3_prepare에 대해 특파원이 필요합니다 sqlite3_finalize.

올바르게 마무리하지 않으면 sqlite3_close연결이 닫히지 않습니다.


1

나는 비슷한 문제로 어려움을 겪고 있었다. 부끄러워 ... 마침내 Reader 가 닫히지 않았다는 것을 깨달았 습니다. 어떤 이유로 해당 연결이 닫히면 리더가 닫힐 것이라고 생각했습니다. 분명히 GC.Collect ()는 나를 위해 작동하지 않았습니다.
Reader를 "using : 문으로 감싸는 것도 좋은 생각입니다. 여기에 간단한 테스트 코드가 있습니다.

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 일 그의 답변 참조). 환상적입니다. 모든 잠금 문제가 사라졌습니다! 감사합니다 올리버.


나는이 대신 답변의 코멘트를해야한다고 생각
케빈 월리스

1
Kevin, 저도 동의하지만 50 개의 평판이 필요하기 때문에 (분명히) 언급 할 수 없었습니다.
Tony Sullivan

0

가비지 콜렉터를 기다리는 동안 데이터베이스가 항상 해제되지 않을 수 있습니다. 예를 들어 PrimaryKey에 대한 기존 값이있는 행을 삽입하려고 시도하는 경우와 같이 SQLite 데이터베이스에서 일부 유형의 예외가 발생하면 사용자가 처리 할 때까지 데이터베이스 파일이 유지됩니다. 다음 코드는 SQLite 예외를 포착하고 문제가있는 명령을 취소합니다.

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

문제가있는 명령의 예외를 처리하지 않으면 Garbage Collector가 처리 할 수없는 예외가 있습니다. 이러한 명령에 대해 처리되지 않은 예외가 있으므로 가비지가 아닙니다. 이 처리 방법은 가비지 수집기를 기다리는 데 잘 작동했습니다.


0

이것은 나를 위해 작동하지만 때로는 프로세스가 닫힐 때 저널 파일 -wal -shm이 삭제되지 않는 것으로 나타났습니다. 모든 연결이 닫 혔을 때 SQLite가 -wal -shm 파일을 제거하도록하려면 마지막 연결이 읽기 전용이 아니어야합니다. 이것이 누군가를 도울 수 있기를 바랍니다.


0

나를 위해 일한 최고의 답변.

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

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

File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.