当前位置: 首页 > news >正文

C# 下 SQLite 并发操作与锁库问题的 5 种解决方案

开篇:你是否被 SQLite 并发锁库困扰?

在当今数字化的时代浪潮中,数据已然成为了企业与开发者们手中最为宝贵的资产之一。C# 作为一门广泛应用于各类软件开发的强大编程语言,常常需要与数据库进行紧密交互,以实现数据的高效存储、查询与管理。而 SQLite,这款以轻量级、嵌入式著称的数据库,因其占用资源极少、处理速度快、易于管理和传输等诸多优势,备受开发者们的青睐,成为了众多项目中的首选数据库方案。

想象一下这样的场景:您正在开发一个多线程的应用程序,其中多个线程需要同时对 SQLite 数据库进行读写操作。在高并发的压力之下,突然之间,程序报错,“database is locked”(数据库已锁定)的错误信息赫然出现在眼前,犹如一道晴天霹雳,整个系统的运行戛然而止。这不仅会导致用户体验大打折扣,严重时甚至可能引发数据丢失、业务中断等灾难性的后果,让之前所有的努力付诸东流。

究竟是什么原因导致了这一棘手的问题呢?其实,这是由于 SQLite 自身的并发机制特性所决定的。它在同一时刻仅允许单个线程进行写入操作,如果多个线程同时发起写入请求,就像是多辆车同时争抢一条狭窄的单行道,必然会造成交通堵塞,导致某些线程无法在限定时间内顺利完成写入操作,进而引发锁库问题。

那么,面对如此严峻的挑战,我们该如何巧妙化解呢?别着急,今天我们就将深入剖析 C# 下 SQLite 并发操作与锁库问题,为您呈上精心整理的 5 种解决方案,助您轻松应对并发挑战,让您的程序在数据的海洋中畅游无阻!

一、SQLite 并发操作基础剖析

(一)认识 SQLite:轻量级数据库的优势

SQLite,作为一款在数据库领域极具特色的产品,以其轻量级、嵌入式的特性脱颖而出。它的设计理念聚焦于简洁高效,无需独立的服务器进程,就能在各种环境中稳定运行。这意味着无论是资源紧张的嵌入式设备,如智能家居中的传感器节点、可穿戴设备,还是对配置便捷性要求极高的小型应用程序,SQLite 都能轻松嵌入,成为数据存储与管理的得力助手。

从资源占用的角度来看,SQLite 堪称 “节俭大师”。相较于那些大型商业数据库,它的内存需求极低,通常仅需几百 KB 的内存就能开启工作,这使得它在内存资源有限的设备中如鱼得水,不会给系统带来沉重的负担。在处理速度方面,SQLite 也毫不逊色,采用了高效的存储结构与算法,能够快速响应数据的读写请求。例如,在一些实时性要求较高的场景中,如移动应用的本地数据缓存,SQLite 能够迅速为用户提供所需数据,提升应用的响应速度与流畅度。

而其将整个数据库存储在单一文件中的设计,更是一大亮点。这种单文件存储方式带来了无与伦比的便利性。一方面,管理变得轻而易举,无论是备份数据、迁移数据库,还是在不同设备或项目之间共享数据,只需简单地复制、移动这个文件即可。就好比您在开发一款跨平台的小型工具软件,使用 SQLite 作为数据库,当需要将软件从 Windows 平台移植到 Linux 平台时,只需带上那个小巧的数据库文件,无需复杂的数据库迁移操作,轻松实现数据的无缝对接。另一方面,部署也变得异常简单,对于嵌入式应用和移动应用开发者来说,将应用程序与数据库文件一同打包发布,用户拿到手后即可直接使用,无需进行繁琐的数据库配置,大大降低了开发与部署的难度。

(二)并发操作隐患:为何会出现锁库

然而,就如同阳光背后总会有阴影,SQLite 在享受轻量级与便捷性带来的诸多优势时,也不得不面对并发操作带来的挑战。在多线程或多进程并发访问的场景下,由于 SQLite 自身的设计架构,同一时刻仅允许单个线程进行写入操作。这就好比一座狭窄的独木桥,一次只能允许一个人通过,如果多个线程同时试图对数据库进行写入,就如同多个人同时争抢这座独木桥,必然会造成混乱与拥堵。

当多个线程同时发起写入请求时,数据库为了保证数据的一致性与完整性,会对写入操作进行严格的互斥控制。也就是说,在一个线程正在执行写入操作的过程中,其他线程的写入请求只能被迫等待。而如果等待的时间过长,超过了系统预设的超时时间(通常默认是 5 秒钟,不过在某些特定的编译配置下可以修改这个超时时间),就会触发 “database is locked”(数据库已锁定)错误。这不仅会导致当前线程的写入操作失败,还可能使依赖这些数据的后续业务逻辑陷入混乱,严重影响系统的正常运行。

为了更深入地理解这一过程,我们可以想象一个电商系统中的订单处理场景。在购物高峰期,多个订单处理线程同时尝试将新订单写入 SQLite 数据库。如果此时没有合理的并发控制机制,这些线程就会相互竞争写入权限。一旦某个线程获得写入锁开始执行插入订单数据的操作,其他线程就只能在一旁干着急。要是这个过程中出现一些复杂的业务逻辑处理,导致写入操作耗时较长,那么等待的线程就很可能在超时之后收到那令人沮丧的 “database is locked” 错误,进而可能引发订单丢失、用户投诉等一系列问题,给企业带来不必要的损失。

二、5 种解决方案全解析

(一)读写锁(ReaderWriterLock):精细的读写控制

1. 代码示例:构建安全的数据访问通道

在 C# 编程世界中,当我们试图驯服 SQLite 并发操作这头 “猛兽” 时,读写锁(ReaderWriterLock)无疑是一件强有力的武器。下面这段示例代码,将向您展示如何巧妙地运用它来构建安全、高效的数据访问通道:

using System;
using System.Data.SQLite;
using System.Threading;class DatabaseManager
{private static SQLiteConnection _connection;private static readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();public static SQLiteConnection GetConnection(){if (_connection == null){_lock.EnterWriteLock();try{if (_connection == null){_connection = new SQLiteConnection("Data Source=database.db");}}finally{_lock.ExitWriteLock();}}return _connection;}public static void InsertUser(string name){var connection = GetConnection();_lock.EnterWriteLock();try{using (var transaction = connection.BeginTransaction()){try{using (var command = new SQLiteCommand(connection)){command.CommandText = "INSERT INTO Users (Name) VALUES (@name)";command.Parameters.AddWithValue("@name", name);command.ExecuteNonQuery();}transaction.Commit();}catch (Exception ex){transaction.Rollback();}}}finally{_lock.ExitWriteLock();}}public static void SelectUsers(){var connection = GetConnection();_lock.EnterReadLock();try{using (var command = new SQLiteCommand("SELECT * FROM Users", connection)){using (var reader = command.ExecuteReader()){while (reader.Read()){Console.WriteLine(reader["Name"]);}}}}finally{_lock.ExitReadLock();}}
}

从这段代码中,我们可以清晰地看到整个数据访问流程的精细架构。首先,通过引入 System.Data.SQLite 和 System.Threading 这两个关键的命名空间,为后续操作奠定基础。前者赋予我们与 SQLite 数据库交互的能力,后者则提供了强大的线程同步工具 —— 读写锁。

在连接管理方面,采用了单例模式来创建并共享 SQLiteConnection 对象。这种设计模式就像是在程序中设立了一个 “数据库连接总管”,确保整个应用程序在运行期间只有一个共享的数据库连接实例,避免了频繁创建和销毁连接带来的资源浪费,同时也保证了数据操作的一致性。

而读写锁的运用更是点睛之笔。在获取数据库连接的 GetConnection 方法中,当首次创建连接时,使用 _lock.EnterWriteLock () 方法获取写锁,这就好比在建造连接这座 “桥梁” 时,拉起了一道禁止他人通行的 “警戒线”,确保在连接创建过程中不会受到其他线程的干扰,保证连接的完整性与正确性。一旦连接创建成功,立即通过 _lock.ExitWriteLock () 方法释放写锁,让其他线程可以正常访问连接。

在插入数据的 InsertUser 方法中,同样先获取写锁,然后开启一个数据库事务,将插入操作包裹其中。这一系列操作就像是在一个封闭的 “安全屋” 中执行机密任务,确保在多线程环境下,写操作的独占性与原子性。即使在写入过程中出现异常,如网络波动导致数据库暂时不可写,事务也能通过 Rollback 回滚操作,保证数据的一致性,避免出现脏数据。

查询数据的 SelectUsers 方法则相对 “温柔” 许多,使用 _lock.EnterReadLock () 方法获取读锁,这意味着多个线程可以同时持有读锁,并行地进行查询操作,就像多个游客可以同时参观博物馆的展览一样,互不干扰,大大提高了查询的并发性能。查询结束后,通过 _lock.ExitReadLock () 方法及时释放读锁,为后续的读写操作腾出空间。

2. 代码解析:深入理解读写锁机制

深入研读上述代码,我们能更透彻地理解读写锁机制的精妙之处。引入 System.Data.SQLite 和 System.Threading 命名空间,如同为程序开启了两扇通往不同世界的大门。前者引领我们走进 SQLite 数据库的交互天地,让数据的增删改查成为可能;后者则为我们提供了应对多线程并发挑战的有力武器 —— 读写锁,确保在复杂的并发环境中,数据的读写操作能够有条不紊地进行。

单例模式在其中扮演着 “资源管家” 的重要角色。通过它创建并共享的 SQLiteConnection 对象,成为了整个程序与数据库沟通的唯一桥梁。这不仅避免了因频繁创建连接而导致的资源浪费,还确保了所有数据操作都基于同一个连接,有效防止了因连接不一致而引发的数据混乱问题,就像一个团队只有一个统一的指挥中心,才能保证行动的协调一致。

读写锁(ReaderWriterLockSlim)的核心作用在于对连接对象的访问进行精细控制。当执行写操作时,如 InsertUser 方法中的操作,EnterWriteLock 方法被调用,此时线程如同获得了一把 “独家钥匙”,独占对连接的访问权。这意味着在同一时刻,其他任何线程,无论是想要进行写操作还是读操作,都只能在门外等待,直到持有写锁的线程完成任务并通过 ExitWriteLock 方法释放锁。这种独占性确保了写操作的完整性与一致性,避免了多个线程同时写入导致的数据冲突与损坏,就像在一份重要文件上进行修改时,必须确保只有一个人在操作,才能保证文件内容的正确性。

而对于读操作,如 SelectUsers 方法所示,EnterReadLock 方法允许多个线程同时获取读锁,并行地对数据库进行查询。这是因为读操作本身不会修改数据,多个线程同时读取数据不会引发数据冲突,反而能充分利用系统资源,提高查询效率,就像多个读者可以同时阅读同一本书籍,互不干扰,还能加快知识的传播速度。当所有读操作完成后,通过 ExitReadLock 方法及时释放读锁,将资源交还给系统,以便其他线程能够按需使用。

在实际应用场景中,想象一个在线文档编辑系统,多个用户可能同时对文档进行阅读(查询操作),此时读写锁的读锁机制允许这些用户快速获取文档内容,提升系统的响应速度;而当有用户进行保存(写入操作)时,写锁机制则确保在保存过程中,不会有其他操作干扰,保证文档数据的完整性,避免出现数据丢失或混乱的情况,为用户提供流畅、可靠的编辑体验。

(二)事务(Transaction):保障数据完整性

1. 代码示例:巧用事务确保数据一致

接下来,让我们一同审视利用事务(Transaction)来解决 SQLite 并发问题的代码示例:

using System;
using System.Data.SQLite;class DatabaseManager
{private static SQLiteConnection _connection;public static SQLiteConnection GetConnection(){if (_connection == null){_connection = new SQLiteConnection("Data Source=database.db");}return _connection;}public static void InsertUser(string name){var connection = GetConnection();using (var transaction = connection.BeginTransaction()){try{using (var command = new SQLiteCommand(connection)){command.CommandText = "INSERT INTO Users (Name) VALUES (@name)";command.Parameters.AddWithValue("@name", name);command.ExecuteNonQuery();}transaction.Commit();}catch (Exception ex){transaction.Rollback();}}}public static void SelectUsers(){var connection = GetConnection();using (var command = new SQLiteCommand("SELECT * FROM Users", connection)){using (var reader = command.ExecuteReader()){while (reader.Read()){Console.WriteLine(reader["Name"]);}}}}
}

这段代码基于 System.Data.SQLite 命名空间构建,简洁而有力地展示了事务在保障数据一致性方面的关键作用。首先,同样采用单例模式管理 SQLiteConnection 对象,确保整个应用程序使用唯一的数据库连接,避免连接混乱带来的数据不一致风险。

在插入数据的 InsertUser 方法中,重点聚焦于事务的运用。当需要插入新用户数据时,通过 connection.BeginTransaction () 方法开启一个事务,这就如同开启了一个 “数据保险箱”,将后续的插入操作包裹其中。在事务内部,构建一个 SQLiteCommand 对象,精心设置好插入语句以及参数,精准地将新用户信息插入到数据库中。如果插入过程一帆风顺,没有任何异常抛出,那么通过 transaction.Commit () 方法提交事务,将数据正式写入数据库,就像将珍贵的物品安全地存入保险箱后,锁上柜门,确保数据永久保存。然而,一旦在插入过程中遭遇意外,如数据库磁盘空间不足、主键冲突等异常情况,catch 块中的 transaction.Rollback () 方法就会立即发挥作用,将事务回滚,撤销之前所有的操作,仿佛 “时光倒流”,保证数据库状态回到事务开始之前,避免出现半完成状态的无效数据,确保数据的完整性与一致性。

查询数据的 SelectUsers 方法则相对直接,无需事务的包裹。直接通过构建 SQLiteCommand 对象,执行查询语句,并利用 ExecuteReader 方法遍历结果集,将查询到的用户信息逐一输出,就像打开一本记录册,轻松读取其中的信息。

2. 代码解析:明晰事务的原子性优势

深入剖析这段代码,我们能深刻领悟事务的原子性在数据库操作中的核心优势。引入 System.Data.SQLite 命名空间,为我们搭建起与 SQLite 数据库沟通的桥梁,让数据操作指令得以顺利传达。

单例模式下的连接管理,确保了整个应用程序在数据操作过程中的连贯性与一致性。所有的数据读写请求都通过同一个数据库连接进行,避免了因连接切换或重复创建导致的数据不一致隐患,如同在一条稳定的轨道上行驶,不会偏离方向。

事务的运用则是这段代码的灵魂所在。在 InsertUser 方法中,BeginTransaction 方法开启的事务具有原子性,这意味着事务内部的所有操作被视为一个不可分割的整体,要么全部成功执行并提交,要么在遇到任何错误时全部回滚,就像一个紧密团结的团队,要么一起成功冲过终点线,要么全体退回起点,重新出发。这种原子性保障了数据的一致性,即使在高并发环境下,多个线程同时尝试插入数据,只要每个线程的插入操作都在各自独立的事务中进行,就不会出现部分数据插入成功、部分失败的混乱局面,有效避免了因并发写操作导致的数据损坏与不一致问题。

在实际的业务场景中,想象一个电商系统的订单处理流程。当多个订单同时涌入,需要插入数据库时,每个订单的插入操作都被封装在独立的事务中。如果某个订单在插入过程中因为库存不足、支付异常等原因失败,事务的回滚机制能够确保该订单相关的所有数据操作都被撤销,不会在数据库中留下混乱的、不完整的订单信息,保证了订单数据的准确性与完整性,为后续的业务处理提供了坚实的数据基础。而查询操作,由于其本身不会修改数据,所以无需事务的额外保护,直接执行查询即可快速获取所需信息,满足系统对数据读取的需求。

(三)WAL 模式:提升并发写性能

1. 设置 WAL 模式:开启高效并发之门

WAL(Write-Ahead Logging)模式作为 SQLite 并发处理的一把 “利器”,为我们开启了高效并发的大门。以下是设置 WAL 模式并进行简单数据操作的示例代码:

using System;
using System.Data.SQLite;class DatabaseManager
{private static SQLiteConnection _connection;public static SQLiteConnection GetConnection(){if (_connection == null){_connection = new SQLiteConnection("Data Source=database.db;Journal Mode=WAL");_connection.Open();}return _connection;}public static void InsertUser(string name){var connection = GetConnection();using (var command = new SQLiteCommand(connection)){command.CommandText = "INSERT INTO Users (Name) VALUES (@name)";command.Parameters.AddWithValue("@name", name);command.ExecuteNonQuery();}}public static void SelectUsers(){var connection = GetConnection();using (var command = new SQLiteCommand("SELECT * FROM Users", connection)){using (var reader = command.ExecuteReader()){while (reader.Read()){Console.WriteLine(reader["Name"]);}}}}
}

在这段代码中,通过在连接字符串中巧妙设置 “Journal Mode=WAL”,轻松启用了 WAL 模式。这一小小的设置,如同为数据库引擎注入了一股强大的动力,使其在并发处理能力上得到显著提升。

代码的结构依旧简洁明了,首先利用单例模式确保唯一的 SQLiteConnection 对象。在 GetConnection 方法中,当创建连接时,将连接字符串配置为启用 WAL 模式,随后立即打开连接,为后续的数据操作做好准备,就像为一场盛大的演出搭建好舞台,并确保灯光、音响等一切设备就绪。

插入数据的 InsertUser 方法和查询数据的 SelectUsers 方法与之前的示例类似,插入时精心构建插入命令,设置参数并执行插入操作;查询时构建查询命令,遍历结果集输出信息。但在 WAL 模式的加持下,这些操作将展现出截然不同的并发性能。

2. 原理剖析:WAL 如何减少锁竞争

深入探究 WAL 模式的内部工作原理,我们能发现其减少锁竞争的神奇之处。传统的 SQLite 写操作模式,在写入数据时,需要对整个数据库文件加锁,这就如同在一条狭窄的道路上进行大型施工,所有车辆(其他线程的读写操作)都必须等待施工完成才能通行,导致并发性能极低。

而 WAL 模式则采用了一种更为巧妙的策略。它引入了一个名为 WAL 文件(Write-Ahead Logging 文件)的 “缓冲区”。当有线程发起写操作时,数据并不是直接写入主数据库文件,而是先暂存到 WAL 文件中。这就好比快递员在派送大量包裹时,先将包裹集中存放在一个临时仓库(WAL 文件),而不是直接一件件送到客户家中(主数据库文件)。在这个过程中,主数据库文件依然可以正常对外提供读服务,其他线程的查询操作不受影响,实现了读写并行,大大提高了并发性能。

随着 WAL 文件中的数据逐渐积累,当满足一定条件(如 WAL 文件大小达到阈值、事务提交等)时,数据库引擎会在后台将 WAL 文件中的数据合并(checkpoint)到主数据库文件中,这个过程通常是高效且短暂的,不会长时间阻塞其他操作。通过这种先暂存后合并的方式,WAL 模式有效减少了写操作对数据库的独占时间,降低了锁竞争的概率,让数据库在高并发环境下依然能够保持高效运行,就像优化了城市的交通管理系统,让车辆(数据操作)能够更加顺畅地通行,避免了交通堵塞(锁库)的发生。

在实际应用中,比如一个实时数据采集与分析系统,大量传感器不断采集数据并写入 SQLite 数据库,同时分析模块需要频繁查询数据进行实时分析。启用 WAL 模式后,写入数据的传感器线程可以快速将数据暂存到 WAL 文件,不会阻塞分析线程的查询操作,确保系统能够及时响应分析需求,提供准确的数据洞察,为系统的稳定高效运行提供有力保障。

(四)连接池:优化连接资源利用

1. 代码示例:搭建连接复用体系

连接池(Connection Pool)作为优化数据库连接资源利用的关键技术,为 C# 与 SQLite 的高效协作提供了坚实支撑。以下是利用连接池的示例代码:

using System;
using System.Data.SQLite;class DatabaseManager
{private static SQLiteConnection _connection;public static SQLiteConnection GetConnection(){if (_connection == null){_connection = new SQLiteConnection("Data Source=database.db;Max Pool Size=100;Pooling=True");_connection.Open();}return _connection;}public static void InsertUser(string name){var connection = GetConnection();using (var command = new SQLiteCommand(connection)){command.CommandText = "INSERT INTO Users (Name) VALUES (@name)";command.Parameters.AddWithValue("@name", name);command.ExecuteNonQuery();}}public static void SelectUsers(){var connection = GetConnection();using (var command = new SQLiteCommand("SELECT * FROM Users", connection)){using (var reader = command.ExecuteReader()){while (reader.Read()){Console.WriteLine(reader["Name"]);}}}}
}

在这段代码中,通过在连接字符串中精心配置 “Max Pool Size=100;Pooling=True” 参数,成功启用了连接池功能。这一配置就像是为数据库连接打造了一个 “资源共享池”,让连接资源得到更高效的利用。

代码依旧以单例模式管理 SQLiteConnection 对象,确保全局只有一个连接入口。在 GetConnection 方法中,当首次创建

三、方案对比:因地制宜选最优

为了帮助大家更清晰地了解这 5 种解决方案的特点、适用场景以及优缺点,我们精心制作了以下表格:

方案特点适用场景优点缺点
读写锁(ReaderWriterLock)对连接对象的访问进行精细控制,区分读、写操作,确保写独占、读并发读操作频繁且与写操作并发的场景,如在线文档系统、新闻资讯类应用精细控制读写权限,提高并发性能,保证数据一致性代码实现相对复杂,需要合理管理锁的获取与释放,否则易造成死锁
事务(Transaction)将多个数据库操作封装为一个原子单元,要么全部成功,要么全部失败回滚对数据完整性要求极高的场景,如金融交易系统、订单管理系统保证数据的原子性、一致性,有效防止数据损坏与不一致事务范围过大可能导致锁占用时间长,影响并发性能
WAL 模式引入 WAL 文件作为缓冲区,先写 WAL 文件再合并到主数据库,实现读写并行读写并发频繁的场景,如实时数据采集与分析系统、社交网络的动态更新显著提升写操作并发性能,减少锁竞争,提高系统响应速度WAL 文件可能占用额外磁盘空间,在特定场景下查询性能可能略有下降
连接池(Connection Pool)预先创建一定数量的数据库连接,放入连接池供复用频繁创建和销毁数据库连接的场景,如 Web 应用服务器、高并发的 API 服务提高连接复用率,减少连接创建与销毁开销,提升系统性能需要合理配置连接池参数,否则可能出现连接泄漏或资源浪费
多线程模式综合运用多种优化策略,如设置合适的同步模式、启用 WAL 模式、连接池等对整体并发性能有较高要求,追求极致性能优化的复杂应用结合多种优势,全面提升并发性能,适应复杂高并发环境配置相对复杂,需要深入了解各参数含义及相互影响,对开发者要求较高

通过这个表格,相信大家对每种方案都有了更为直观的认识。在实际项目开发中,我们需要根据具体的业务需求、数据访问模式以及系统性能要求,综合权衡,选择最适合的解决方案。例如,如果您正在开发一个小型的本地应用程序,读写操作相对简单且并发量不大,那么简单地使用事务来保证数据的一致性可能就足够了;而如果您面对的是一个大型的分布式系统,高并发读写是常态,那么可能需要结合 WAL 模式、连接池甚至多线程模式等多种手段,才能确保系统的稳定与高效运行。

四、实战演练:方案落地应用

纸上得来终觉浅,绝知此事要躬行。为了让大家更直观地感受这 5 种解决方案在实际场景中的应用效果,我们特意模拟了一个多线程并发读写 SQLite 数据库的场景,并分别用上述 5 种方案来解决可能出现的锁库问题。以下是详细的示例代码与执行结果分析:

(一)模拟场景设定

假设我们正在开发一个简单的用户管理系统,该系统需要支持多线程并发地插入新用户数据和查询用户列表。数据库中包含一个名为 “Users” 的表,其中有 “Id”(自增主键)和 “Name”(用户名)两个字段。在高并发环境下,多个线程同时尝试插入新用户或查询用户列表,这就极易引发锁库问题,我们将通过不同方案来化解这一难题。

(二)读写锁方案实战

1. 示例代码
using System;
using System.Data.SQLite;
using System.Threading;class DatabaseManager
{private static SQLiteConnection _connection;private static readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();public static SQLiteConnection GetConnection(){if (_connection == null){_lock.EnterWriteLock();try{if (_connection == null){_connection = new SQLiteConnection("Data Source=database.db");}}finally{_lock.ExitWriteLock();}}return _connection;}public static void InsertUser(string name){var connection = GetConnection();_lock.EnterWriteLock();try{using (var transaction = connection.BeginTransaction()){try{using (var command = new SQLiteCommand(connection)){command.CommandText = "INSERT INTO Users (Name) VALUES (@name)";command.Parameters.AddWithValue("@name", name);command.ExecuteNonQuery();}transaction.Commit();}catch (Exception ex){transaction.Rollback();}}}finally{_lock.ExitWriteLock();}}public static void SelectUsers(){var connection = GetConnection();_lock.EnterReadLock();try{using (var command = new SQLiteCommand("SELECT * FROM Users", connection)){using (var reader = command.ExecuteReader()){while (reader.Read()){Console.WriteLine(reader["Name"]);}}}}finally{_lock.ExitReadLock();}}
}class Program
{static void Main(){// 创建多个线程并发插入数据var insertThreads = new Thread[5];for (int i = 0; i < 5; i++){insertThreads[i] = new Thread(() =>{DatabaseManager.InsertUser($"User{i + 1}");});insertThreads[i].Start();}// 等待插入线程完成foreach (var thread in insertThreads){thread.Join();}// 创建多个线程并发查询数据var selectThreads = new Thread[3];for (int i = 0; i < 3; i++){selectThreads[i] = new Thread(() =>{DatabaseManager.SelectUsers();});selectThreads[i].Start();}// 等待查询线程完成foreach (var thread in selectThreads){thread.Join();}}
}
2. 执行结果分析

在这个示例中,我们首先创建了 5 个线程并发地插入新用户数据,每个线程插入一个不同名称的用户。接着,创建 3 个线程并发地查询用户列表。由于使用了读写锁进行精细的读写控制,写操作(插入数据)在获取写锁后独占资源,确保了数据的一致性,避免了多个线程同时写入导致的锁库问题。而读操作(查询用户列表)则通过获取读锁,实现了多个线程的并发执行,大大提高了查询效率。从执行结果来看,数据能够正确插入,查询也能快速返回结果,系统运行稳定,没有出现 “database is locked” 错误。

(三)事务方案实战

1. 示例代码
using System;
using System.Data.SQLite;
using System.Threading;class DatabaseManager
{private static SQLiteConnection _connection;public static SQLiteConnection GetConnection(){if (_connection == null){_connection = new SQLiteConnection("Data Source=database.db");}return _connection;}public static void InsertUser(string name){var connection = GetConnection();using (var transaction = connection.BeginTransaction()){try{using (var command = new SQLiteCommand(connection)){command.CommandText = "INSERT INTO Users (Name) VALUES (@name)";command.Parameters.AddWithValue("@name", name);command.ExecuteNonQuery();}transaction.Commit();}catch (Exception ex){transaction.Rollback();}}}public static void SelectUsers(){var connection = GetConnection();using (var command = new SQLiteCommand("SELECT * FROM Users", connection)){using (var reader = command.ExecuteReader()){while (reader.Read()){Console.WriteLine(reader["Name"]);}}}}
}class Program
{static void Main(){// 创建多个线程并发插入数据var insertThreads = new Thread[5];for (int i = 0; i < 5; i++){insertThreads[i] = new Thread(() =>{DatabaseManager.InsertUser($"User{i + 1}");});insertThreads[i].Start();}// 等待插入线程完成foreach (var thread in insertThreads){thread.Join();}// 创建多个线程并发查询数据var selectThreads = new Thread[3];for (int i = 0; i < 3; i++){selectThreads[i] = new Thread(() =>{DatabaseManager.SelectUsers();});selectThreads[i].Start();}// 等待查询线程完成foreach (var thread in selectThreads){thread.Join();}}
}
2. 执行结果分析

同样是 5 个插入线程和 3 个查询线程并发执行,在事务方案中,每个插入操作都被封装在独立的事务里。当某个插入线程遇到问题(如数据库临时故障、主键冲突等)时,事务能够自动回滚,保证数据的完整性。查询线程则不受影响,正常执行查询操作。从执行结果看,数据插入和查询都能顺利进行,即使在插入过程中模拟一些异常情况,也不会出现脏数据或锁库问题,确保了系统的可靠性,但由于事务的隔离性,在高并发写操作时,可能会因锁等待时间过长而略微影响并发性能。

(四)WAL 模式方案实战

1. 示例代码
using System;
using System.Data.SQLite;
using System.Threading;class DatabaseManager
{private static SQLiteConnection _connection;public static SQLiteConnection GetConnection(){if (_connection == null){_connection = new SQLiteConnection("Data Source=database.db;Journal Mode=WAL");_connection.Open();}return _connection;}public static void InsertUser(string name){var connection = GetConnection();using (var command = new SQLiteCommand(connection)){command.CommandText = "INSERT INTO Users (Name) VALUES (@name)";command.Parameters.AddWithValue("@name", name);command.ExecuteNonQuery();}}public static void SelectUsers(){var connection = GetConnection();using (var command = new SQLiteCommand("SELECT * FROM Users", connection)){using (var reader = command.ExecuteReader()){while (reader.Read()){Console.WriteLine(reader["Name"]);}}}}
}class Program
{static void Main(){// 创建多个线程并发插入数据var insertThreads = new Thread[5];for (int i = 0; i < 5; i++){insertThreads[i] = new Thread(() =>{DatabaseManager.InsertUser($"User{i + 1}");});insertThreads[i].Start();}// 等待插入线程完成foreach (var thread in insertThreads){thread.Join();}// 创建多个线程并发查询数据var selectThreads = new Thread[3];for (int i = 0; i < 3; i++){selectThreads[i] = new Thread(() =>{DatabaseManager.SelectUsers();});selectThreads[i].Start();}// 等待查询线程完成foreach (var thread in selectThreads){thread.Join();}}
}
2. 执行结果分析

启用 WAL 模式后,插入线程和查询线程并发执行时,写入操作不再阻塞读操作。插入线程将数据先写入 WAL 文件,此时查询线程可以正常从主数据库文件读取数据,实现了读写并行。从执行结果来看,系统的并发性能得到显著提升,插入和查询操作都能快速响应,几乎没有出现锁库等待的情况,大大提高了系统的吞吐量,不过需要注意 WAL 文件可能占用额外的磁盘空间。

(五)连接池方案实战

1. 示例代码
using System;
using System.Data.SQLite;
using System.Threading;class DatabaseManager
{private static SQLiteConnection _connection;public static SQLiteConnection GetConnection(){if (_connection == null){_connection = new SQLiteConnection("Data Source=database.db;Max Pool Size=100;Pooling=True");_connection.Open();}return _connection;}public static void InsertUser(string name){var connection = GetConnection();using (var command = new SQLiteCommand(connection)){command.CommandText = "INSERT INTO Users (Name) VALUES (@name)";command.Parameters.AddWithValue("@name", name);command.ExecuteNonQuery();}}public static void SelectUsers(){var connection = GetConnection();using (var command = new SQLiteCommand("SELECT * FROM Users", connection)){using (var reader = command.ExecuteReader()){while (reader.Read()){Console.WriteLine(reader["Name"]);}}}}
}class Program
{static void Main(){// 创建多个线程并发插入数据var insertThreads = new Thread[5];for (int i = 0; i < 5; i++){insertThreads[i] = new Thread(() =>{DatabaseManager.InsertUser($"User{i + 1}");});insertThreads[i].Start();}// 等待插入线程完成foreach (var thread in insertThreads){thread.Join();}// 创建多个线程并发查询数据var selectThreads = new Thread[3];for (int i = 0; i < 3; i++){selectThreads[i] = new Thread(() =>{DatabaseManager.SelectUsers();});selectThreads[i].Start();}// 等待查询线程完成foreach (var thread in selectThreads){thread.Join();}}
}
2. 执行结果分析

在连接池方案中,通过预先创建一定数量(这里配置最大连接数为 100)的数据库连接并放入连接池复用,减少了线程频繁创建和销毁连接的开销。在多线程并发插入和查询时,连接池能够快速分配可用连接,提高系统响应速度。从执行结果看,插入和查询操作都能高效执行,系统资源利用更加合理,避免了因连接创建销毁频繁导致的性能瓶颈,但如果连接池参数配置不当,如最大连接数设置过小,可能会出现连接不够用的情况,影响并发性能;设置过大则可能导致资源浪费。

(六)多线程模式方案实战

1. 示例代码
using System;
using System.Data.SQLite;
using System.Threading;class DatabaseManager
{private static SQLiteConnection _connection;public static SQLiteConnection GetConnection(){if (_connection == null){_connection = new SQLiteConnection("Data Source=database.db;Synchronous=Normal;Journal Mode=WAL;Pooling=True;Max Pool Size=100");_connection.Open();}return _connection;}public static void InsertUser(string name){var connection = GetConnection();using (var command = new SQLiteCommand(connection)){command.CommandText = "INSERT INTO Users (Name) VALUES (@name)";command.Parameters.AddWithValue("@name", name);command.ExecuteNonQuery();}}public static void SelectUsers(){var connection = GetConnection();using (var command = new SQLiteCommand("SELECT * FROM Users", connection)){using (var reader = command.ExecuteReader()){while (reader.Read()){Console.WriteLine(reader["Name"]);}}}}
}class Program
{static void Main(){// 创建多个线程并发插入数据var insertThreads = new Thread[5];for (int i = 0; i < 5; i++){insertThreads[i] = new Thread(() =>{DatabaseManager.InsertUser($"User{i + 1}");});insertThreads[i].Start();}// 等待插入线程完成foreach (var thread in insertThreads){thread.Join();}// 创建多个线程并发查询数据var selectThreads = new Thread[3];for (int i = 0; i < 3; i++){selectThreads[i] = new Thread(() =>{DatabaseManager.SelectUsers();});selectThreads[i].Start();}// 等待查询线程完成foreach (var thread in selectThreads){thread.Join();}}
}
2. 执行结果分析

多线程模式综合运用了多种优化策略,包括设置合适的同步模式(Synchronous=Normal)、启用 WAL 模式提高写并发性能、利用连接池优化连接资源利用。在这个示例中,5 个插入线程和 3 个查询线程并发执行时,系统展现出了卓越的性能表现。插入操作能够快速将数据写入,查询操作也能及时获取最新数据,几乎没有出现锁库导致的延迟,整体并发性能达到了较高水平。不过,这种模式的配置相对复杂,需要开发者深入了解各参数含义及相互影响,才能充分发挥其优势,否则可能因配置不当引发一些难以排查的问题。

通过以上实战演练,相信大家对这 5 种解决方案在实际应用中的表现有了更为直观、深入的理解。在面对不同的业务需求和并发场景时,您可以根据实际情况灵活选择最适合的方案,确保您的 C# 与 SQLite 数据库组合能够高效、稳定地运行。

五、总结与展望:攻克并发难题

至此,我们已经深入探讨了 C# 下 SQLite 并发操作与锁库问题的 5 种解决方案,从读写锁的精细读写控制、事务的原子性保障,到 WAL 模式的高效并发写优化、连接池的资源复用提升,再到多线程模式的综合性能突破,每一种方案都有其独特的魅力与适用场景。

在实际项目开发中,大家务必依据项目的具体需求、并发操作的规模以及数据的特性,精心挑选最合适的解决方案。这就如同为一场战役挑选最合适的武器,只有精准匹配,才能在数据的战场上百战不殆。

希望大家在今后的开发工作中,积极将这些解决方案付诸实践,不断探索与优化,让您的 C# 与 SQLite 组合发挥出最大的威力。同时,随着技术的不断发展,数据库领域也将持续涌现出新的优化策略与方法,让我们保持学习的热情,时刻关注技术的前沿动态,共同攻克并发难题,书写更加精彩的代码篇章!

如果您在实践过程中遇到任何问题,或者有更多关于 C# 与 SQLite 开发的心得体会,欢迎在评论区留言分享,让我们携手共进,共同成长!

相关文章:

C# 下 SQLite 并发操作与锁库问题的 5 种解决方案

开篇&#xff1a;你是否被 SQLite 并发锁库困扰&#xff1f; 在当今数字化的时代浪潮中&#xff0c;数据已然成为了企业与开发者们手中最为宝贵的资产之一。C# 作为一门广泛应用于各类软件开发的强大编程语言&#xff0c;常常需要与数据库进行紧密交互&#xff0c;以实现数据的…...

正则表达式基础

由于最近做的项目涉及到比较多的转义字符&#xff0c;转义字符长时间不用已经遗忘了&#xff0c;每次需要的时候就需要找到ai生成&#xff0c;或者网络上搜索&#xff0c;所以复习一遍。 正则表达式&#xff08;regular expression&#xff09; 功能&#xff1a;字符串模式匹…...

QT在 MacOS X上,如何检测点击程序坞中的Dock图标

最近在开发MacOS的qt应用&#xff0c;在做到最小化系统托盘功能时&#xff0c;发现关闭窗口后再次点击程序坞中的Dock图标不能将主界面再显示出来。查询里很多资料&#xff0c;发现是QT自身的问题&#xff0c;没有做相关的点击Dock图标的处理。 于是我参考了国内和国外的这两篇…...

Pgsql存储占用分析

基础命令 -- 查询表大小 SELECT pg_total_relation_size(table_name);-- 查询表大小&#xff08;不带索引&#xff09; SELECT pg_table_size(table_name);-- 查询表索引大小 SELECT pg_indexes_size(table_name);-- 查询表具体大小 SELECT pg_relation_size(table_name); SEL…...

【C语言】字符串函数详解

文章目录 Ⅰ. strcpy -- 字符串拷贝1、函数介绍2、模拟实现 Ⅱ. strcat -- 字符串追加1、函数介绍2、模拟实现 Ⅲ. strcmp -- 字符串比较1、函数介绍2、模拟实现 Ⅳ. strncpy、strncat、strncmp -- 可限制操作长度Ⅴ. strlen -- 求字符串长度1、函数介绍2、模拟实现&#xff08…...

CRMEB多商户商城系统JAVA版 B2B2C商家入驻平台系统独立版全开源

系统框架 基于Java vueuni-app开发&#xff0c;并采用业界主流开发框架SpringBoot; 前端开发中&#xff0c;Web PC管理端使用vue element山&#xff0c;移动端使用uni-app框架&#xff0c;前后端分离开发;...

【Linux】进程状态

一、概念 我们需要知道进程的不同状态。一个进程可以有几个状态&#xff08;在Linux内核里&#xff0c;进程有时候也叫做任务&#xff09; 在操作系统原理中&#xff1a;运行状态分为以下三种&#xff1a;运行状态&#xff08;执行&#xff09;、阻塞状态、就绪状态 1. 运行状…...

深入理解计算机系统阅读笔记-第十二章

第12章 网络编程 12.1 客户端-服务器编程模型 每个网络应用都是基于客户端-服务器模型的。根据这个模型&#xff0c;一个应用时由一个服务器进程和一个或者多个客户端进程组成。服务器管理某种资源&#xff0c;并且通过操作这种资源来为它的客户端提供某种服务。例如&#xf…...

《C++11》nullptr介绍:从NULL说起

在C11之前&#xff0c;我们通常使用NULL来表示空指针。然而&#xff0c;NULL在C中有一些问题和限制&#xff0c;这就是C11引入nullptr的原因。本文将详细介绍nullptr的定义、用法和优点。 1. NULL的问题 在C中&#xff0c;NULL实际上是一个整数0&#xff0c;而不是一个真正的…...

Realsense相机驱动安装及其ROS通讯配置——机器人抓取系统系列文章(四)

文章目录 概要1 Realsense相机驱动安装Method1: 使用Intel服务器预编译包Method2: 使用ROS服务器预编译包Method3: 使用SDK源代码方法对比总结 2 Realsense-ROS通讯配置与使用2.1 Realsense-ROS包安装2.2 ROS节点启动 小结Reference 概要 本文首先阐述了Realsense相机驱动安装…...

conntrack iptables 安全组

centos 安装yum install conntrack-tools 1. conntrack状态 NEW: 新建连接&#xff08;第一次包&#xff09;。 ESTABLISHED: 已建立连接&#xff0c;正在传输数据。 RELATED: 与已有连接相关的连接&#xff0c;如 FTP 数据连接。 INVALID: 无效连接&#xff0c;无法识别或不…...

基于springboot+vue的 嗨玩-旅游网站

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…...

SQL多表联查、自定义函数(字符串分割split)、xml格式输出

记录一个报表的统计&#xff0c;大概内容如下&#xff1a; 多表联查涉及的报表有&#xff1a;房间表、买家表、合同表、交易表、费用表、修改记录表 注意&#xff1a;本项目数据库使用的是sqlserver&#xff08;mssql&#xff09;&#xff0c;非mysql。 难点1:业主信息&#…...

神经网络

“损失函数 王木头学科学-哔哩哔哩_bilibili 一、transformer和注意力机制的本质&#xff0c;以及编码和解码的结构及其与注意力机制的关系&#xff0c;帮助理解transformer的优势和应用场景。 00:01 - 介绍transformer是人工智能主流技术&#xff0c;大语言模型GBT是在其基础…...

C16.【C++ Cont】string类字符串的关系运算和与string有关的函数

目录 1.知识回顾:strcmp函数 2.六个关系运算符 示例代码1 运行结果 示例代码2 运行结果 3.stoi和stol函数 1.stoi函数 函数声明 使用说明 示例代码3 运行结果 示例代码4 运行结果 示例代码5 运行结果 示例代码6 运行结果 2.stol函数 4.stod和stof函数 1.s…...

深入剖析 Wireshark:网络协议分析的得力工具

在网络技术的广阔领域中&#xff0c;网络协议分析是保障网络正常运行、优化网络性能以及进行网络安全防护的关键环节。而 Wireshark 作为一款开源且功能强大的网络协议分析工具&#xff0c;在网络工程师、安全专家以及网络技术爱好者中广受欢迎。本文将深入介绍 Wireshark 的功…...

七大排序算法(Java,便于理解)

时间换空间排序算法 一.冒泡排序 package SortDemo;import java.util.Arrays; import java.util.Comparator; import java.util.PriorityQueue;public class BubbleSort {//冒泡排序,相邻两个数两两比较private int[]a;public void swap(int i,int j){int tempa[i];a[i]a[j];…...

洛谷 P1873 [COCI 2011/2012 #5] EKO / 砍树 c语言

题目&#xff1a; P1873 [COCI 2011/2012 #5] EKO / 砍树 - 洛谷 | 计算机科学教育新生态 题目描述 伐木工人 Mirko 需要砍 M 米长的木材。对 Mirko 来说这是很简单的工作&#xff0c;因为他有一个漂亮的新伐木机&#xff0c;可以如野火一般砍伐森林。不过&#xff0c;Mirko…...

MySQL8.0新特性

第十八章_MySQL8.0新特性 1.新特性概述 1. 数据库管理和存储 1.1 数据字典 特性: MySQL 8.0 使用统一的数据字典存储元数据&#xff08;如表、列、索引等&#xff09;&#xff0c;并将其存储在 InnoDB 表中。 优点 : 提升性能&#xff1a;减少对文件系统的依赖。 提高一致…...

Browser-Use Web UI:浏览器自动化与AI的完美结合

Browser-Use Web UI:浏览器自动化与AI的完美结合 前言简介一、克隆项目二、安装与环境配置1. Python版本要求2. 安装依赖3. 安装 Playwright4. 配置环境变量(非必要步骤)三、启动 WebUI四、配置1. Agent设置2. 大模型设置3. 浏览器相关设置4. 运行 Agent结语前言 Web UI是在…...

006-excel数据输出insert语句

一、在空白列插入&#xff0c;选择需要的列 "INSERT INTO tab_name1 (code, name) VALUES ("&A1&", "&B1&");"二、 拖动填充块&#xff0c;或者双击填充块&#xff08;可以快速填充整列&#xff09; 三、直接把生成的 insert 语…...

AI大模型如何赋能电商行业并引领变革?

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于AI大模型如何赋能电商行业并引领变革的相…...

食堂采购系统源码:基于PHP的校园食堂供应链管理平台开发全解析

传统的食堂采购管理普遍存在信息不透明、流程繁琐、效率低下等问题&#xff0c;这使得开发一款高效、智能的食堂采购系统变得尤为重要。本篇文章&#xff0c;笔者将详细解析基于PHP开发的校园食堂供应链管理平台&#xff0c;从功能设计、系统架构到技术实现&#xff0c;全方位剖…...

【2024华为OD-E卷-100分-字符串分割】(题目+思路+JavaC++Python解析)

题目 字符串分割 给定一个字符串 s 和一个整数 k&#xff0c;你需要将字符串 s 分割成恰好 k 个非空子字符串&#xff0c;使得这些子字符串中字典序最大的子字符串尽可能小。 输入&#xff1a; 第一行输入一个字符串 s&#xff08;只包含小写字母&#xff09;。第二行输入一…...

MCP Server开发的入门教程(python和pip)

使用python技术栈开发的简单mcp server 需要安装 MCP server的需要使用python-sdk,python需要 3.10,安装如下 pip install mcpPS: MCP官方使用的是uv包管理工具,我平时使用pip比较多,所以文中以pip为主。因为mcp的一些依赖包版本并不是最新的,所以最好弄一个干净的环境…...

我的年度总结

这一年的人生起伏&#xff1a;从曙光到低谷再到新的曙光 其实本来没打算做年度总结的&#xff0c;无聊打开了帅帅的视频&#xff0c;结合自己最近经历的&#xff0c;打算简单聊下。因为原本打算做的内容会是一篇比较丧、低能量者的呻吟。 实习生与创业公司的零到一 第一段工…...

48_Lua错误处理

在编写Lua应用时,都可能会遇到不可预见的错误,而错误处理是确保程序稳定性和健壮性的关键环节。有效的错误处理不仅能防止程序崩溃,还能提供有用的反馈信息给开发者或最终用户,从而提高应用程序的质量。本文将详细介绍Lua中的错误处理机制。 1.错误类型 Lua中的错误类型主…...

掌握 React 关键:理解 super () 和 super (props) 的不同应用

在 React 中&#xff0c;super() 和 super(props) 都与 React 类组件的构造函数&#xff08;constructor&#xff09;以及继承有关。为了理解它们之间的区别&#xff0c;我们需要了解 JavaScript 类继承机制以及 React 类组件的工作原理。 1. super() 与 super(props) 的区别 …...

type 属性的用途和实现方式(图标,表单,数据可视化,自定义组件)

1.图标类型 <uni-icon>组件中&#xff0c;type可以用来指定图标的不同样式。 <uni-icons type"circle" size"30" color"#007aff"></uni-icons> //表示圆形 <uni-icons type"square" size"30" co…...

scala基础学习_方法函数

文章目录 方法与函数函数&#xff08;又称函数值/匿名函数&#xff09;定义方法注意 单参数函数多参数函数函数作为参数传递 方法将方法转换为函数方法的返回值总结 方法与函数 函数&#xff08;又称函数值/匿名函数&#xff09; 定义在任何地方&#xff1a;函数可以定义在类…...

linux: 文本编辑器vim

文本编辑器 vi的工作模式 (vim和vi一致) 进入vim的方法 方法一:输入 vim 文件名 此时左下角有 "文件名" 文件行数,字符数量 方法一: 输入 vim 新文件名 此时新建了一个文件并进入vim,左下角有 "文件名"[New File] 灰色的长方形就是光标,输入文字,左下…...

《深入理解Mybatis原理》Mybatis中的缓存实现原理

一级缓存实现 什么是一级缓存&#xff1f; 为什么使用一级缓存&#xff1f; 每当我们使用MyBatis开启一次和数据库的会话&#xff0c;MyBatis会创建出一个SqlSession对象表示一次数据库会话。 在对数据库的一次会话中&#xff0c;我们有可能会反复地执行完全相同的查询语句&…...

【Debug】django.db.utils.OperationalError: (1040, ‘Too many connections‘)

报错&#xff1a; django.db.utils.OperationalError: (1040, ‘Too many connections‘) 排查 可能是Mysql的连接数量超过了允许的最大连接数量&#xff1b; 查看Mysql允许最大连接数量&#xff1a; -- 查看允许连接的最大数量 SHOW VARIABLES LIKE %max_connections%;-- 查…...

常用教程备份

1.Ubuntu 系统软件安装教程 https://blog.csdn.net/weixin_51591021/article/details/134363237 2.Docker 教程 https://blog.csdn.net/weixin_51591021/article/details/134363849 3.Makefile 教程 https://blog.csdn.net/weixin_51591021/article/details/134363638 4.…...

什么是视频孪生智慧能源?视频孪生智慧能源的应用案例

‌视频孪生智慧能源是集三维地理信息系统、视频虚实融合、数字孪生、人工智能等多技术于一体的综合应用&#xff0c;旨在实现对能源系统的实时、动态、全方位监控和管理‌。 具体来说&#xff0c;视频孪生智慧能源通过以下方式实现其功能&#xff1a; ‌技术融合‌&#xff1a;…...

Kubernetes1.28 编译 kubeadm修改证书有效期到 100年.并更新k8s集群证书

文章目录 前言一、资源准备1. 下载对应源码2.安装编译工具3.安装并设置golang 二、修改证书有效期1.修改证书有效期2.修改 CA 证书有效期 三、编译kubeadm四、使用新kubeadm方式1.当部署新集群时,使用该kubeadm进行初始化2.替换现有集群kubeadm操作 前言 kubeadm 默认证书为一…...

时序数据库的订阅对比:TDengine vs InfluxDB 谁更强?

目录 1. 架构&#xff1a;内置 vs 依赖外部 TDengine: InfluxDB: 2. 灵活性&#xff1a;动态订阅 vs 静态订阅 TDengine: InfluxDB: 3. 消费机制、API 兼容性与易用性对比 4. 结语 在时序数据应用场景中&#xff0c;数据实时消费和处理能力成为衡量数据库性能和可用性的…...

OpenCV实现多尺度细节提升算法

1、算法原理 多尺度细节提升算法来源于论文*《DARK IMAGE ENHANCEMENT BASED ON PAIRWISE TARGET CONTRAST AND MULTI-SCALE DETAIL BOOSTING》*&#xff0c;算法主要是解决细节增强算法中噪声和细节的平衡问题。 常规的非锐化掩蔽&#xff08;USM&#xff09;算法在提升细节…...

按键精灵ios越狱脚本教程:多选框联动的ui界面

以下是一个简单的 iOS 代码示例&#xff0c;使用 Swift 语言来创建一个包含多选框&#xff08;复选框&#xff09;的 UI 界面&#xff0c;并实现联动效果。 import UIKitclass ViewController: UIViewController {let checkbox1 UIButton(type:.system)let checkbox2 UIButt…...

YOLOv10-1.1部分代码阅读笔记-patches.py

patches.py ultralytics\utils\patches.py 目录 patches.py 1.所需的库和模块 2.def imread(filename: str, flags: int cv2.IMREAD_COLOR): 3.def imwrite(filename: str, img: np.ndarray, paramsNone): 4.def imshow(winname: str, mat: np.ndarray): 5.def tor…...

撤回最近的 git commit

在 Git 中&#xff0c;如果你想撤回最近的 git commit&#xff0c;可以根据不同的需求选择不同的操作。以下是几种常见的撤回方式&#xff1a; 1. 撤回最后一次 commit&#xff0c;但保留修改&#xff08;soft reset&#xff09; 如果你想撤销 git commit&#xff0c;但保留修…...

基于DFT与IIR-FIR滤波器的音频分析与噪声处理

基于DFT与IIR-FIR滤波器的音频分析与噪声处理 【完整源码文档报告】 【需要可随时联系博主&#xff0c;常在线能秒回!】 系统功能与实现介绍 功能与实现 音频处理系统界面搭建&#xff1a;利用MATLAB的GUI工具&#xff0c;构建了音频分析界面&#xff0c;包括文件导入、录…...

MySQL主从部署(保姆版)

一、mysql 同步复制有关概述 一般数据库都是读取压力大于写数据压力&#xff0c;主从复制即为了实现数据库的负载均衡和读写分离。通过将Mysql的某一台主机的数据复制到其它主机&#xff08;slaves&#xff09;上&#xff0c;主服务器只负责写&#xff0c;而从服务器只负责读。…...

Golang笔记——协程同步

大家好&#xff0c;这里是Good Note&#xff0c;关注 公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍Golang的协程同步的实现和应用场景。 文章目录 协程同步是什么&#xff1f;为什么需要协程同步&#xff1f;常见的协程同步机制互斥锁&#xff0…...

1.14学习

misc buuctf-大白 由提示可以知道这个应该是修改图片的宽高了&#xff0c;下载附件后得到了图片用随波逐流直接修改图片的宽高输出即可 buuctf-乌镇峰会种图 点击下载&#xff0c;出现了一个网页为图片将图片另存为&#xff0c;用随波逐流得到的信息解不了&#xff0c;再试…...

2025 年 JavaScript 入门教程

2025 年 JavaScript 入门教程 在当今数字化时代&#xff0c;JavaScript 作为一门广泛应用于 Web 开发的编程语言&#xff0c;其重要性不言而喻。无论是前端页面的交互实现&#xff0c;还是后端服务器的逻辑处理&#xff0c;JavaScript 都发挥着关键作用。本教程旨在帮助初学者…...

paddle——站在巨人肩膀上及背刺二三事

飞桨AI Studio - 人工智能学习与实训社区 飞桨PaddlePaddle-源于产业实践的开源深度学习平台 先抛结论&#xff0c;对于想要快速了解某一领域有哪些比较适合落地的算法的从业人员来说&#xff0c;是一个很好的参考系统。从中可以知道从哪些模型里选型、如何轻量化、如何加…...

nvim , neovim , Lua 语法, text object

说明 &#xff1a; 了解一下 nvim 中的基本的 文本的类型。 基本类型有几种&#xff0c; 1 word , sentence , paragragh 2 (), {}, ,"", 3 就是 html 中的 tag 标签。 然后就是选中的类型。 1 i : 待变 inner 2 a: 代表around &#xff0c; 基本的动作有 &…...

6.2 MySQL时间和日期函数

以前我们就用过now()函数来获得系统时间&#xff0c;用datediff()函数来计算日期相差的天数。我们在计算工龄的时候&#xff0c;让两个日期相减。那么其中的这个now函数返回的就是当前的系统日期和时间。 1. 获取系统时间函数 now()函数&#xff0c;返回的这个日期和时间的格…...

批量识别图片型PDF指定区域内容识别保存表格+PDF批量改名:技术难题与项目实战总结

相关项目实战&#xff1a; 一、引言 在当今数字化办公环境中&#xff0c;批量处理PDF文件中的表格数据并进行改名是一项常见但具有挑战性的任务。无论是从大量的财务报销凭证、学术研究报告还是项目文档中提取表格信息&#xff0c;都可能遇到各种各样的技术难题。 二、批量提…...