【问题标题】:Multiple access to a single SQLite database file via System.Data.SQLite and c#通过 System.Data.SQLite 和 c# 对单个 SQLite 数据库文件进行多次访问
【发布时间】:2013-03-01 06:54:04
【问题描述】:

正如我从SQLite FAQ 看到的那样,它在任何时候都支持多进程读取(SELECT)和仅一个进程写入(INSERT、UPDATE、DELETE)数据库:

SQLite 使用读/写锁来控制对数据库的访问。 当任何进程要写入时,它必须锁定整个数据库文件 在其更新期间。但这通常只需要几个 毫秒。其他进程只是等待作者完成然后 继续他们的业务

我正在通过 c# 使用System.Data.SQLite 适配器。

请问有人可以解释一下吗?究竟这个过程是如何进行的?

如果已经在同一个数据库上执行另一个写入 SQLiteCommand,这个过程是否会自动工作并且写入 SQLiteCommand 会简单地等待?

或者它可能会抛出异常?什么样的?

抱歉,我没有找到有关此机制的信息 :)

谢谢。

更新:

我发现帖子说 exception will be raised 带有特定的错误代码

这个说法正确吗?

【问题讨论】:

  • 还没有 :) 我更喜欢在编码之前阅读教程 - 以获得优化和正确的解决方案...
  • 如果您更喜欢optimized and correct solutions,请不要将 SQLite 用于未优化的东西;这是一个很棒的数据库,但它不是一个很棒的多用户数据库。
  • 我的项目需要免费且轻量级(我的意思是零配置和无服务器)数据库。所以大多数“多用户优化”的数据库都不适合我:)

标签: c# database sqlite locking system.data.sqlite


【解决方案1】:

我自己调查过:

我创建了一个示例 SQLite 数据库 c:\123.db,其中一个表 Categories 包含两个字段:ID(唯一标识符)和 Name(nvarchar)。

然后我编写了一些多线程代码来模拟对数据库的多次写入访问(如果您使用此代码,请不要忘记在您的项目中添加 System.Data.SQLite 引用):

using System;
using System.Data.SQLite;
using System.Threading.Tasks;

namespace SQLiteTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var tasks = new Task[100];

            for (int i = 0; i < 100; i++)
            {
                tasks[i] = new Task(new Program().WriteToDB);
                tasks[i].Start();
            }

            foreach (var task in tasks)
                task.Wait();
        }

        public void WriteToDB()
        {
            try
            {
                using (SQLiteConnection myconnection = new SQLiteConnection(@"Data Source=c:\123.db"))
                {
                    myconnection.Open();
                    using (SQLiteTransaction mytransaction = myconnection.BeginTransaction())
                    {
                        using (SQLiteCommand mycommand = new SQLiteCommand(myconnection))
                        {
                            Guid id = Guid.NewGuid();

                            mycommand.CommandText = "INSERT INTO Categories(ID, Name) VALUES ('" + id.ToString() + "', '111')";
                            mycommand.ExecuteNonQuery();

                            mycommand.CommandText = "UPDATE Categories SET Name='222' WHERE ID='" + id.ToString() + "'";
                            mycommand.ExecuteNonQuery();

                            mycommand.CommandText = "DELETE FROM Categories WHERE ID='" + id.ToString() + "'";
                            mycommand.ExecuteNonQuery();
                        }
                        mytransaction.Commit();
                    }
                }
            }
            catch (SQLiteException ex)
            {
                if (ex.ReturnCode == SQLiteErrorCode.Busy)
                    Console.WriteLine("Database is locked by another process!");
            }
        }
    }
}

我的 Core2Duo E7500 上的结果是从未引发异常!

看起来 SQLite 已经针对我的需要进行了足够优化(锁定/解锁非常快,通常只需几毫秒,正如 SQLite FAQ 告诉我们的那样) - 太好了!

请注意,无需为 SQLiteException 检索整数 ErrorCode - 您可以改用特殊的枚举 ReturnCode 字段。所有代码均描述为here

希望这些信息对某人有所帮助。

【讨论】:

  • 您的代码证明了一个单个进程中的多个线程可以无缝访问SQLite数据库。您是否能够测试多个不同的进程是否可以访问同一个数据库?
  • 嗯,首先我想到的是在 Test1.exe 中编译该代码,而不是添加 Process.Start("Test1.exe")Main 的第一行并将其重新编译为 Test2.exe。似乎可以工作,但我从未测试过...
  • 好主意!我刚刚使用这种方法进行了测试,它可以工作。多进程访问与 SQLite 配合得很好。
  • 只是想补充一下。我正在编写一个基于 NLog / SignalR 的“保证交付”日志服务,该服务使用 System.Data.SQLite 写入本地数据库,以便后台线程将该数据发送到 LogStash,并且我有 40 个线程将日志消息发送到本地SignalR 集线器。当 single process 正在处理消息时,它肯定会抛出 SQLite 错误 (5): database is locked 异常来自其他进程的其他线程。
  • @JamesEby SQlite 使用内部日志,因此当由于块状态或其他原因而失败时,它会重试直到完成。因此,不是内部条件,当然您的插入也不会丢失。实际上,如果您看到该消息,则表明发生了错误,并且稍后发生了重试。当您使用螺纹插入时,这种“锁定”错误很常见。除此之外,如果您从线程进行大量读取,则使用 WAL 日志模型,因此写入和读取在单独的线程中,因此来自线程的写入不会影响可能的数千个并发读取。
猜你喜欢
  • 1970-01-01
  • 2011-09-18
  • 1970-01-01
  • 2016-07-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-27
  • 2015-03-10
相关资源
最近更新 更多