System.Data.SQLite Close () ไม่ปล่อยไฟล์ฐานข้อมูล


97

ฉันมีปัญหาในการปิดฐานข้อมูลก่อนที่จะพยายามลบไฟล์ รหัสเป็นเพียง

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

และลบแสดงข้อยกเว้นว่าไฟล์ยังคงใช้งานอยู่ ฉันได้ลองลบ () อีกครั้งในโปรแกรมดีบั๊กหลังจากนั้นไม่กี่นาทีดังนั้นจึงไม่ใช่ปัญหาเรื่องเวลา

ฉันมีรหัสธุรกรรม แต่ไม่ทำงานเลยก่อนการเรียกปิด () ดังนั้นฉันค่อนข้างแน่ใจว่าไม่ใช่ธุรกรรมที่เปิดอยู่ คำสั่ง sql ระหว่างเปิดและปิดเป็นเพียงการเลือก

ProcMon แสดงโปรแกรมของฉันและโปรแกรมป้องกันไวรัสของฉันกำลังดูไฟล์ฐานข้อมูล มันไม่แสดงโปรแกรมของฉันที่ปล่อยไฟล์ db หลังจากปิด ()

Visual Studio 2010, C #, System.Data.SQLite เวอร์ชัน 1.0.77.0, Win7.0

ฉันเห็นบั๊กอายุสองปีแบบนี้ แต่บันทึกการเปลี่ยนแปลงบอกว่าได้รับการแก้ไขแล้ว

มีอะไรให้ตรวจสอบอีกไหม? มีวิธีรับรายการคำสั่งหรือธุรกรรมที่เปิดอยู่หรือไม่?


ใหม่รหัสการทำงาน:

 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

คำตอบ:


113

พบปัญหาเดียวกันเมื่อไม่นานมานี้ในขณะที่เขียน DB Abstraction Layer สำหรับ C # และฉันไม่เคยรู้เลยว่าปัญหาคืออะไร ฉันเพิ่งทิ้งข้อยกเว้นเมื่อคุณพยายามลบ SQLite DB โดยใช้ไลบรารีของฉัน

อย่างไรก็ตามบ่ายวันนี้ฉันกำลังมองหาทุกอย่างอีกครั้งและคิดว่าฉันจะพยายามค้นหาว่าทำไมมันถึงทำแบบนั้นครั้งแล้วครั้งเล่านี่คือสิ่งที่ฉันได้พบ

สิ่งที่เกิดขึ้นเมื่อคุณโทรSQLiteConnection.Close()คือ (พร้อมกับการตรวจสอบและสิ่งอื่น ๆ อีกมากมาย) SQLiteConnectionHandleที่ชี้ไปยังอินสแตนซ์ฐานข้อมูล SQLite จะถูกกำจัด สิ่งนี้ทำได้ผ่านการโทรไปยังSQLiteConnectionHandle.Dispose()แต่จะไม่ปล่อยตัวชี้จริง ๆ จนกว่า Garbage Collector ของ CLR จะดำเนินการรวบรวมขยะ เนื่องจากSQLiteConnectionHandleแทนที่CriticalHandle.ReleaseHandle()ฟังก์ชันที่จะเรียกsqlite3_close_interop()(ผ่านฟังก์ชันอื่น) จึงไม่ได้ปิดฐานข้อมูล

จากมุมมองของฉันนี่เป็นวิธีที่แย่มากในการทำสิ่งต่างๆเนื่องจากโปรแกรมเมอร์ไม่แน่ใจจริงๆว่าเมื่อใดที่ฐานข้อมูลถูกปิด แต่นั่นเป็นวิธีที่ทำไปแล้วดังนั้นฉันเดาว่าเราต้องอยู่กับมันไปก่อนหรือยอมทำ การเปลี่ยนแปลงเล็กน้อยใน System.Data.SQLite อาสาสมัครทุกคนสามารถทำได้ แต่น่าเสียดายที่ฉันหมดเวลาทำเช่นนั้นก่อนปีหน้า

TL; DR วิธีการแก้ปัญหาคือการบังคับ GC หลังจากเรียกร้องให้และก่อนที่จะเรียกร้องให้SQLiteConnection.Close()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

1
คุณอาจต้องการดู C # SQLite ฉันเพิ่งย้ายรหัสทั้งหมดไปใช้งาน แน่นอนว่าถ้าคุณใช้งานบางอย่างที่สำคัญประสิทธิภาพ C อาจเร็วกว่า C # แต่ฉันเป็นแฟนของโค้ดที่มีการจัดการ ...
Benjamin Pannell

1
ฉันรู้ว่ามันเก่าแล้ว แต่ขอบคุณที่ช่วยฉันเจ็บ ข้อบกพร่องนี้ยังส่งผลต่อการสร้าง SQLite ของ Windows Mobile / Compact Framework
StrayPointer

2
การทำงานที่ดี! แก้ไขปัญหาของฉันทันที ใน 11 ปีของการพัฒนา C # ฉันไม่เคยมีความจำเป็นต้องใช้ GC เลยรวบรวม: ตอนนี้เป็นตัวอย่างแรกที่ฉันถูกบังคับให้ทำ
Pilsator

10
GC.Collect (); ใช้งานได้ แต่ System.Data.SQLite.SQLiteConnection.ClearAllPools (); จัดการกับปัญหาโดยใช้ API ของไลบรารี
Aaron Hudon

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คำสั่งเพื่อให้แน่ใจว่าจะเรียกว่าทิ้งแม้ว่าข้อยกเว้นเกิดขึ้น

จากนั้นมันก็ง่ายกว่ามากในการดำเนินการคำสั่งเช่นกัน

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

6
ฉันไม่แนะนำให้กลืนข้อยกเว้นเช่นนี้มากนัก
Tom McKearney

17

มีปัญหาที่คล้ายกันแม้ว่าโซลูชันตัวเก็บขยะจะไม่สามารถแก้ไขได้

พบการทิ้งSQLiteCommandและSQLiteDataReaderสิ่งของหลังการใช้งานช่วยให้ฉันใช้เครื่องเก็บขยะได้เลย

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

2
ตรง ตรวจสอบให้แน่ใจว่าคุณทิ้งทุกอย่างSQLiteCommandแม้ว่าคุณจะรีไซเคิลSQLiteCommandตัวแปรในภายหลัง
Bruno Bieri

สิ่งนี้ได้ผลสำหรับฉัน ฉันยังแน่ใจว่าจะกำจัดธุรกรรมใด ๆ
Jay-Nicolas Hackleman

1
เยี่ยมมาก! คุณช่วยฉันได้พอสมควร มันแก้ไขข้อผิดพลาดเมื่อฉันเพิ่มcommand.Dispose();ทุกSQLiteCommandสิ่งที่ดำเนินการ
Ivan B

นอกจากนี้ตรวจสอบให้แน่ใจว่าคุณได้ปล่อย (เช่น.Dispose()) วัตถุอื่น ๆ เช่น SQLiteTransaction ถ้าคุณมี
Ivan B

14

สิ่งต่อไปนี้ใช้ได้ผลสำหรับฉัน:

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

ข้อมูลเพิ่มเติม : การเชื่อมต่อถูกรวบรวมโดย SQLite เพื่อปรับปรุงประสิทธิภาพหมายความว่าเมื่อคุณเรียกใช้เมธอดปิดบนอ็อบเจ็กต์การเชื่อมต่อการเชื่อมต่อกับฐานข้อมูลอาจยังมีชีวิตอยู่ (ในพื้นหลัง) เพื่อให้วิธีการเปิดถัดไปเร็วขึ้นเมื่อคุณทราบเช่นนั้น คุณไม่ต้องการการเชื่อมต่อใหม่อีกต่อไปการเรียก ClearAllPools จะปิดการเชื่อมต่อทั้งหมดที่ยังมีชีวิตอยู่ในพื้นหลังและที่จับไฟล์ไปยังไฟล์ db จะถูกปล่อยออกมาจากนั้นไฟล์ db อาจถูกลบลบหรือใช้โดยกระบวนการอื่น


1
โปรดเพิ่มคำอธิบายว่าเหตุใดจึงเป็นวิธีแก้ปัญหาที่ดี
Matas Vaitkevicius

SQLiteConnectionPool.Shared.Reset()นอกจากนี้คุณยังสามารถใช้ การดำเนินการนี้จะปิดการเชื่อมต่อทั้งหมดที่เปิดอยู่ โดยเฉพาะอย่างยิ่งนี่เป็นวิธีแก้ปัญหาหากคุณใช้SQLiteAsyncConnectionที่ไม่มีClose()วิธีการ
Lorenzo Polidori

9

ฉันประสบปัญหาที่คล้ายกันฉันได้ลองวิธีแก้ปัญหาแล้วGC.Collectแต่ตามที่ระบุไว้อาจใช้เวลานานก่อนที่ไฟล์จะไม่ถูกล็อค

ฉันพบทางเลือกอื่นที่เกี่ยวข้องกับการกำจัดสิ่งที่อยู่ภายใต้SQLiteCommandTableAdapters โปรดดูคำตอบนี้สำหรับข้อมูลเพิ่มเติม


คุณพูดถูก! ในบางกรณี 'GC.Collect' ที่เรียบง่ายใช้งานได้สำหรับฉันในบางกรณีฉันต้องทิ้ง SqliteCommands ใด ๆ ที่เกี่ยวข้องกับการเชื่อมต่อก่อนที่จะเรียก GC รวบรวมมิฉะนั้นจะไม่ทำงาน!
Eitan HS

1
การเรียก Dispose บน SQLiteCommand ใช้งานได้สำหรับฉัน นอกเหนือจากความคิดเห็น - หากคุณกำลังโทรหา GC รวบรวมว่าคุณกำลังทำอะไรผิดพลาด
Natalie Adams

@NathanAdams เมื่อทำงานกับ EntityFramework ไม่มีอ็อบเจ็กต์คำสั่งเดียวที่คุณสามารถกำจัดได้ ดังนั้น EntityFramework เองหรือ SQLite for EF wrapper ก็ทำผิดในบางครั้งเช่นกัน
springy76

คำตอบของคุณควรเป็นคำตอบที่ถูกต้อง ขอบคุณมาก.
Ahmed Shamel

6

ฉันมีปัญหาเดียวกันกับ EF และSystem.Data.Sqlite.

สำหรับฉันฉันพบSQLiteConnection.ClearAllPools()และGC.Collect()จะลดความถี่ในการล็อกไฟล์ แต่ก็ยังคงเกิดขึ้นเป็นครั้งคราว (ประมาณ 1% ของเวลา)

ฉันได้ทำการตรวจสอบและดูเหมือนว่ามีบางส่วนSQLiteCommandที่ EF สร้างขึ้นไม่ได้ถูกกำจัดและยังคงมีการตั้งค่าคุณสมบัติการเชื่อมต่อเป็นการเชื่อมต่อแบบปิด ฉันพยายามกำจัดสิ่งเหล่านี้ แต่ Entity Framework จะโยนข้อยกเว้นในระหว่างการDbContextอ่านครั้งต่อไป- ดูเหมือนว่าบางครั้ง EF จะยังคงใช้พวกเขาหลังจากปิดการเชื่อมต่อ

วิธีการแก้ปัญหาของผมก็คือเพื่อให้แน่ใจว่าสถานที่ให้บริการการเชื่อมต่อถูกตั้งค่าให้Nullเมื่อปิดการเชื่อมต่อเหล่านี้SQLiteCommands ดูเหมือนว่าเพียงพอแล้วที่จะคลายล็อกไฟล์ ฉันได้ทดสอบรหัสด้านล่างแล้วและไม่พบปัญหาการล็อกไฟล์หลังจากการทดสอบสองสามพันครั้ง:

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เมื่อชี้ไปที่การเชื่อมต่อที่ปิดอยู่


ฉันต้องตั้งค่าการเชื่อมต่อเป็น null ในส่วน DisposingCommand ของสิ่งนี้หรือบางครั้งฉันจะได้รับ ObjectDisposedExceptions
Elliot

นี่เป็นคำตอบที่ไม่ได้ประเมินในความคิดของฉัน มันช่วยแก้ปัญหาการล้างข้อมูลของฉันที่ฉันไม่สามารถทำได้ด้วยตัวเองเนื่องจากเลเยอร์ EF มีความสุขมากที่ได้ใช้สิ่งนี้กับแฮ็ค GC ที่น่าเกลียดนั้น ขอบคุณ!
Jason Tyler

หากคุณใช้โซลูชันนี้ในสภาพแวดล้อมแบบมัลติเธรดรายการ OpenCommands ควรเป็น [ThreadStatic]
Bero

5

ลองดู ... อันนี้ลองทุกข้อ รหัสทั้งหมด ... ใช้ได้ผลสำหรับฉัน

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

หวังว่าจะช่วยได้


1
WaitForPendingFinalizers สร้างความแตกต่างให้กับฉัน
ทอดด์


3

มีปัญหาที่คล้ายกัน การโทรหาพนักงานเก็บขยะไม่ได้ช่วยฉัน ฉันพบวิธีแก้ปัญหาแล้ว

ผู้เขียนยังเขียนว่าเขาทำการเลือกแบบสอบถามไปยังฐานข้อมูลนั้นก่อนที่จะพยายามลบ ฉันมีสถานการณ์เดียวกัน

ฉันมีรหัสต่อไปนี้:

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.

นอกจากนี้ฉันไม่จำเป็นต้องปิดการเชื่อมต่อฐานข้อมูลและโทรหา Garbage Collector สิ่งที่ฉันต้องทำคือปิดโปรแกรมอ่านที่สร้างขึ้นขณะเรียกใช้แบบสอบถาม SELECT


2

ฉันเชื่อว่าการโทรหาSQLite.SQLiteConnection.ClearAllPools()เป็นวิธีแก้ปัญหาที่สะอาดที่สุด เท่าที่ฉันรู้ว่ามันไม่เหมาะสมที่จะเรียกด้วยตนเองGC.Collect()ในสภาพแวดล้อม WPF แม้ว่าฉันจะไม่สังเกตเห็นปัญหาจนกว่าฉันจะอัปเกรดเป็นSystem.Data.SQLite1.0.99.0 ใน 3/2559


2

บางทีคุณอาจไม่จำเป็นต้องจัดการกับ GC เลย โปรดตรวจสอบว่าsqlite3_prepareสรุปแล้วทั้งหมดหรือไม่

สำหรับแต่ละคุณจะต้องผู้สื่อข่าวsqlite3_preparesqlite3_finalize

หากคุณทำไม่ถูกต้องsqlite3_closeจะไม่ปิดการเชื่อมต่อ


1

ฉันกำลังดิ้นรนกับปัญหาที่คล้ายกัน อัปยศกับฉัน ... ในที่สุดฉันก็รู้ว่าReaderไม่ได้ถูกปิด ด้วยเหตุผลบางประการฉันคิดว่า Reader จะถูกปิดเมื่อปิดการเชื่อมต่อที่เกี่ยวข้อง เห็นได้ชัดว่า GC.Collect () ไม่ได้ผลสำหรับฉัน
การห่อ 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

ฉันใช้ SQLite 1.0.101.0 กับ EF6 และมีปัญหากับไฟล์ที่ถูกล็อคหลังจากที่การเชื่อมต่อและเอนทิตีทั้งหมดถูกกำจัด

สิ่งนี้แย่ลงเมื่อการอัปเดตจาก EF ทำให้ฐานข้อมูลถูกล็อกหลังจากเสร็จสิ้น GC.Collect () เป็นวิธีแก้ปัญหาเดียวที่ช่วยได้และฉันก็เริ่มสิ้นหวัง

ด้วยความสิ้นหวังฉันลอง ClearSQLiteCommandConnectionHelper ของ Oliver Wickenden (ดูคำตอบของเขาในวันที่ 8 กรกฎาคม) ยอดเยี่ยม. ปัญหาการล็อคทั้งหมดหายไป! ขอบคุณโอลิเวอร์


ฉันคิดว่านี่ควรเป็นความคิดเห็นแทนคำตอบ
Kevin Wallis

1
เควินฉันเห็นด้วย แต่ฉันไม่ได้รับอนุญาตให้แสดงความคิดเห็นเพราะฉันต้องการชื่อเสียง 50 (เห็นได้ชัด)
Tony Sullivan

0

การรอ Garbage Collector อาจไม่ปล่อยฐานข้อมูลตลอดเวลาและนั่นก็เกิดขึ้นกับฉัน เมื่อมี Exception บางประเภทเกิดขึ้นในฐานข้อมูล SQLite เช่นพยายามแทรกแถวที่มีค่าที่มีอยู่สำหรับ PrimaryKey ระบบจะเก็บไฟล์ฐานข้อมูลไว้จนกว่าคุณจะกำจัดทิ้ง รหัสต่อไปนี้ตรวจจับข้อยกเว้น 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.