【问题标题】:Why the SQLite database file is locked using WAL Mode and Pooling enabled?为什么使用 WAL 模式锁定 SQLite 数据库文件并启用池?
【发布时间】:2021-08-06 07:06:04
【问题描述】:

我在我的 C# 程序中使用 SQLite 数据库。

程序每秒在数据库中保存值,并且每次都检查表是否已经存在:

class DataManager : IDataManager
{
    private bool machineTableIsCreated = false;
    private bool machineAlreadyInDb = false;
    private bool machineValueTableAlreadyExists = false;

    public DataManager()
    {
        machineTableIsCreated = CheckIfMachineTableExists();
    }

    //Writing the Values in a CSV-File
    public void WriteInCsv(dynamic value, string name)
    {
        string strFilePath = @"C:\SVN\" + name + ".csv";
        File.AppendAllText(strFilePath, Convert.ToString(value) + "\n");
    }

    //Writing the Values into the Database
    public void WriteInDb(string name, string netId, int port, List<BeckhoffVariable> variables)
    {
        BeckhoffMachineDto machine = new BeckhoffMachineDto
        {
            Name = name,
            NetId = netId,
            Port = port,
            Variables = variables
        };

        
        if (!machineTableIsCreated)
        {
            CreateMachineTable();
            machineTableIsCreated = true;
        }

        machineAlreadyInDb = CheckIfMachineIsAlreadyInDb(machine);
        if (!machineAlreadyInDb)
        {
            InsertMachineInDb(machine);
        }

        machineValueTableAlreadyExists = CheckIfMachineValueTableExists(machine.Name);
        if (!machineValueTableAlreadyExists)
        {
            //Creates Table for the Machine where its values are stored
            CreateTable(machine.Name);
        }

        //Inserts the Value read from the Machine into the DB
        InsertData(machine.Name, machine.Variables);
    }

    private bool CheckIfMachineValueTableExists(string name)
    {
        //Open connection to DB 
        using (var connection = new SQLiteConnection("Data Source=C:/SVN/trunk/database.db;PRAGMA journal_mode=WAL;Pooling=True;"))
        {
            connection.Open();

            //Creating Command to execute
            using (var command = connection.CreateCommand())
            {
                command.CommandText = $"SELECT * FROM SQLITE_MASTER WHERE type ='table' AND name ='{name}';";

                //read result of command
                using (var reader = command.ExecuteReader())
                {
                    //Check if result isnt null
                    if (reader.HasRows)
                    {
                        //connection.Close();
                        return true;
                    }
                    //connection.Close();
                    return false;
                }
            }
        }
    }

    private bool CheckIfMachineIsAlreadyInDb(BeckhoffMachineDto machine)
    {
        using (var connection = new SQLiteConnection("Data Source=C:/SVN/trunk/database.db;PRAGMA journal_mode=WAL;Pooling=True;"))
        {
            connection.Open();

            using (var command = connection.CreateCommand())
            {
                command.CommandText = $"SELECT * FROM machines WHERE Name = '{machine.Name}' AND NetId = '{machine.NetId.Replace('.', '-')}' AND Port = '{machine.Port}';";
                command.CommandTimeout = 1;

                using (var reader = command.ExecuteReader())
                {
                    if (reader.HasRows)
                    {
                        //connection.Close();
                        return true;
                    }
                    //connection.Close();
                    return false;
                }
            }
        }
    }

    private bool CheckIfMachineTableExists()
    {
        using (var connection = new SQLiteConnection("Data Source=C:/SVN/trunk/database.db;PRAGMA journal_mode=WAL;Pooling=True;"))
        {
            connection.Open();

            using (var command = connection.CreateCommand())
            {
                command.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name='machines';";
                command.CommandTimeout = 1;

                using (var reader = command.ExecuteReader())
                {
                    if (reader.HasRows)
                    {
                        //connection.Close();
                        return true;
                    }
                    //connection.Close();
                    return false;
                }
            }
        }
    }

    private void InsertMachineInDb(BeckhoffMachineDto machine)
    {
        using (var connection = new SQLiteConnection("Data Source=C:/SVN/trunk/database.db;PRAGMA journal_mode=WAL;Pooling=True;"))
        {
            connection.Open();

            using (var command = connection.CreateCommand())
            {
                command.CommandText = "INSERT INTO machines (Name, NetId, Port, VariableCoolingFlow, VariableCoolingIn, VariableCoolingOut, " +
                "VariableTempAc, VariableTempDc, VariableTempAcBelow, VariableTempDcBelow) VALUES(@machineName, @machineNetId," +
                " @machinePort, @machineVar0, @machineVar1, @machineVar2, @machineVar3, @machineVar4, @machineVar5, @machineVar6); ";

                command.CommandType = CommandType.Text;
                command.Parameters.Add(new SQLiteParameter("@machineName", machine.Name));
                command.Parameters.Add(new SQLiteParameter("@machineNetId", machine.NetId.Replace('.', '-')));
                command.Parameters.Add(new SQLiteParameter("@machinePort", machine.Port));
                command.Parameters.Add(new SQLiteParameter("@machineVar0", machine.Variables[0].Name.Replace('.', '-')));
                command.Parameters.Add(new SQLiteParameter("@machineVar1", machine.Variables[1].Name.Replace('.', '-')));
                command.Parameters.Add(new SQLiteParameter("@machineVar2", machine.Variables[2].Name.Replace('.', '-')));
                command.Parameters.Add(new SQLiteParameter("@machineVar3", machine.Variables[3].Name.Replace('.', '-')));
                command.Parameters.Add(new SQLiteParameter("@machineVar4", machine.Variables[4].Name.Replace('.', '-')));
                command.Parameters.Add(new SQLiteParameter("@machineVar5", machine.Variables[5].Name.Replace('.', '-')));
                command.Parameters.Add(new SQLiteParameter("@machineVar6", machine.Variables[6].Name.Replace('.', '-')));

                command.ExecuteNonQuery();
            }
            //connection.Close();
        }
    }

    private static void CreateMachineTable()
    {
        using (var connection = new SQLiteConnection("Data Source=C:/SVN/trunk/database.db;PRAGMA journal_mode=WAL;Pooling=True;"))
        {
            connection.Open();

            using (var command = connection.CreateCommand())
            {
                command.CommandText = "CREATE TABLE machines (Name VARCHAR(40), NetId VARCHAR(30), Port VARCHAR(40), VariableCoolingFlow VARCHAR(45)," +
                " VariableCoolingIn VARCHAR(45), VariableCoolingOut VARCHAR(45), VariableTempAc VARCHAR(45), VariableTempDc VARCHAR(45)," +
                " VariableTempAcBelow VARCHAR(45), VariableTempDcBelow VARCHAR(45));";
                command.ExecuteNonQuery();
            }
            //connection.Close();
        }
    }

    //Creates The Tables for the Values a Machine has
    private static void CreateTable(string name)
    {
        using (var connection = new SQLiteConnection("Data Source=C:/SVN/trunk/database.db;PRAGMA journal_mode=WAL;Pooling=True;"))
        {
            connection.Open();

            using (var command = connection.CreateCommand())
            {
                command.CommandText = $"CREATE TABLE {name} (TemperatureCoolingFlow VARCHAR(20), TemperatureCoolingOut VARCHAR(20), TemperatureCoolingIn VARCHAR(20)," +
                " TemperatureDTSAc VARCHAR(20), TemperatureDTSDc VARCHAR(20), TemperatureDTSAcBelow VARCHAR(20), TemperatureDTSDcBelow VARCHAR(20))";

                command.ExecuteNonQuery();
            }
            //connection.Close();
        }
    }

    //Inserts the Machine Values in the Table of the Machine
    private static void InsertData(string name, List<BeckhoffVariable> variables)
    {
        using (var connection = new SQLiteConnection("Data Source=C:/SVN/trunk/database.db;PRAGMA journal_mode=WAL;Pooling=True;"))
        {
            connection.Open();

            using (var command = connection.CreateCommand())
            {
                command.CommandText = $"INSERT INTO {name} (TemperatureCoolingFlow, TemperatureCoolingOut, TemperatureCoolingIn, TemperatureDTSAc," +
                $" TemperatureDTSDc, TemperatureDTSAcBelow, TemperatureDTSDcBelow) VALUES(@machineVar0, @machineVar1, @machineVar2, " +
                $" @machineVar3, @machineVar4, @machineVar5, @machineVar6); ";

                command.CommandType = CommandType.Text;
                command.Parameters.Add(new SQLiteParameter("@machineVar0", Convert.ToString(variables[0].Value)));
                command.Parameters.Add(new SQLiteParameter("@machineVar1", Convert.ToString(variables[1].Value)));
                command.Parameters.Add(new SQLiteParameter("@machineVar2", Convert.ToString(variables[2].Value)));
                command.Parameters.Add(new SQLiteParameter("@machineVar3", Convert.ToString(variables[3].Value)));
                command.Parameters.Add(new SQLiteParameter("@machineVar4", Convert.ToString(variables[4].Value)));
                command.Parameters.Add(new SQLiteParameter("@machineVar5", Convert.ToString(variables[5].Value)));
                command.Parameters.Add(new SQLiteParameter("@machineVar6", Convert.ToString(variables[6].Value)));

                command.ExecuteNonQuery();
            }
            //connection.Close();
        }
    }

    //Returns a List of all Machines in the Database
    public List<BeckhoffMachineDto> getAllMachines()
    { 
        List<BeckhoffMachineDto> machines = new List<BeckhoffMachineDto>();

        using (var connection = new SQLiteConnection("Data Source=C:/SVN/trunk/database.db;PRAGMA journal_mode=WAL;Pooling=True;"))
        {
            connection.Open();

            machineTableIsCreated = CheckIfMachineTableExists();
            if (!machineTableIsCreated)
            {
                CreateMachineTable();
                machineTableIsCreated = true;
            }

            using (var command = connection.CreateCommand())
            {
                command.CommandText = "SELECT * FROM machines";

                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        BeckhoffMachineDto currMachine = new BeckhoffMachineDto();
                        currMachine.Name = (string)reader[0];
                        currMachine.NetId = reader[1].ToString().Replace('-', '.');
                        currMachine.Port = Convert.ToInt32(reader[2].ToString());
                        currMachine.Variables = new List<BeckhoffVariable>();

                        for (int i = 3; i <= 9; i++)
                        {
                            currMachine.Variables.Add(new BeckhoffVariable(reader[i].ToString().Replace('-', '.')));
                        }
                        machines.Add(currMachine);
                    }
                    //connection.Close();
                }
            }
        }
        return machines;
    }

    //Deletes the Machine from machines Table and deletes the whole Table from the given machine
    public void DeleteMachine(BeckhoffMachineDto selectedMachine)
    {
        using (var connection = new SQLiteConnection("Data Source=C:/SVN/trunk/database.db;PRAGMA journal_mode=WAL;Pooling=True;"))
        {
            connection.Open();

            using (var command = connection.CreateCommand())
            {
                command.CommandText = $"DROP TABLE {selectedMachine.Name}";
                command.ExecuteNonQuery();

                command.CommandText = $"DELETE FROM machines WHERE Name = '{selectedMachine.Name}' AND NetId = '{selectedMachine.NetId.Replace('.', '-')}' AND Port = '{selectedMachine.Port}'";
                command.ExecuteNonQuery();
            }
            //connection.Close();
        }
    }
}

问题是它在运行程序时被锁定了好几次。

我已经开启了 WAL 模式并开启了池化。

我真的不明白它是如何被锁定的。

SQLite error (5): database is locked in "SELECT * FROM machines WHERE Name = 'Machine1' AND NetId = '10.10.10.1.1.1' AND Port = '111';"

SQLite error (5): database is locked in "SELECT * FROM machines WHERE Name = 'Machine1' AND NetId = '10.10.10.1.1.1' AND Port = '111';"

SQLite error (5): database is locked in "SELECT * FROM machines WHERE Name = 'Machine1' AND NetId = '10.10.10.1.1.1' AND Port = '111';"

SQLite error (5): database is locked in "SELECT * FROM machines WHERE Name = 'Machine1' AND NetId = '10.10.10.1.1.1' AND Port = '111';"

SQLite error (5): database is locked in "SELECT * FROM machines WHERE Name = 'Machine1' AND NetId = '10.10.10.1.1.1' AND Port = '111';"

问题在于,当我将输入的值写入数据库时​​,它会从数据库中选择一些内容并引发数据库锁定错误,但 WAL 和池应该防止这些错误。

有人知道我可以做些什么来解决我的问题吗?

【问题讨论】:

  • 我从 SQLite.org 安装了 System.Data.SQLite NuGet 包
  • 我也有 SQLitePCLRaw.core
  • 我想我发现了问题:我无法在 3.7 下的 SQLite 版本中激活 WAL 模式,但是我如何知道我拥有的版本以及如何将其更新到较新的版本?
  • 感谢您的帮助,但不幸的是,这不是我的问题。我真的不知道如何修复数据库锁定错误,所以我假设我必须使用与 sqlite 不同的数据库。很伤心:(
  • 如果不是这个,我不知道,对不起。我现在只使用 SQLiteNet ORM,在我放弃了伟大但速度慢的 SQLite ODBC 驱动程序之后,我发现所有其他提供者经过多年测试,既不有趣也不复杂,或者不能很好地与 WinForms 和数据绑定一起工作,因此我不知道他们,我在处理 SQLite 并发方面并不那么先进。因此,如果没有人可以帮助您,请尝试重新设计您的系统,以创建一个真正的多层客户端-服务器,从而拥有一个干净而健壮的架构。

标签: c# sqlite wal database-concurrency database-locking


【解决方案1】:

显然,解决这个问题的方法是将sqlite数据库的同步pragma改为NORMAL!使用 WAL 模式时,同步编译指示的最佳设置是 NORMAL,但 db 的默认设置是 FULL。但是 FULL 不适用于 WAL。当我更改它时,不再有数据库锁定错误。

【讨论】:

  • 所以普通同步模式不是正确的答案。它有助于将锁定的错误降至最低,但它们仍然会出现。尝试关闭同步模式或其他不同的编译指示设置,让您的项目发挥最佳效果!
  • 对于未来的项目:如果你在整个程序中有很多数据库访问,也许不要使用 SQLite,而所有这些都以很小的间隔使用数据库。
猜你喜欢
  • 2016-08-26
  • 1970-01-01
  • 1970-01-01
  • 2013-06-11
  • 2019-02-05
  • 2021-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多