Windows Server多核心服务器中C# Windows服务LiteDB文件锁异常问题排查及解决方案咨询
Windows服务中LiteDB文件占用异常与锁问题的排查方案
问题背景
我有一个以Windows服务形式运行的C#控制台应用程序,采用NT Service账户运行。服务启动后,会在用户目录C:\Users\XXX\AppData\Local\TimeSeries.db位置创建一个LiteDB(SQLite变种)数据库文件:
- 运行第一天,同一
test.exe进程(同一PID)下有2条该文件的占用记录,数据读写正常; - 运行数天后,出现
TimeSeries.db文件锁问题,读写失败,此时同一PID下的文件占用记录增加到了3条。
我有几个疑问:
- 明明服务是单实例运行,为什么文件占用条目会新增?最终导致锁问题的原因是什么?
- 问题只在部分生产服务器出现,该怎么排查和解决?
- 附上相关代码,我怀疑问题出在
LiteDbResultWrite没实现IDisposable接口,这个判断对吗?
相关代码
public class LiteContext { public LiteDatabase Database { get; } public LiteContext() { Database = new LiteDatabase("TimeSeries.db"); } } public class LiteDbResultWrite { private readonly LiteDatabase _liteDb; public LiteDbResultWrite(LiteContext liteContext) { // 注:原代码构造函数名疑似笔误,修正为类名一致 _liteDb = liteContext.Database; } public Task<Guid> Insert(Result result) { try { var liteCollection = _liteDb.GetCollection<Result>("Result"); return Task.FromResult(liteCollection.Insert(result).AsGuid); } catch (Exception exception) { return Task.FromResult(new Guid()); } } }
问题解答与排查步骤
1. 文件占用条目新增的核心原因
首先明确:LiteDB正常运行时,会为数据库文件、日志文件(.db-journal)或WAL文件(.db-wal)创建多个句柄,但你提到的是同一TimeSeries.db文件的占用记录增加,那大概率是重复创建了LiteDatabase实例,且旧实例没有被正确释放。
你的LiteContext在构造函数里直接实例化LiteDatabase,如果LiteContext被多次创建(比如依赖注入时注册成了瞬时/范围服务,而非单例),每一次创建都会打开一个新的数据库连接,对应一个新的文件句柄。随着服务运行时间变长,旧的实例没被回收,句柄数积累到一定程度,就会触发LiteDB的文件锁机制——因为LiteDB默认是单写多读,多个写实例共存时必然会引发锁冲突。
2. 关于LiteDbResultWrite未实现IDisposable的判断
你的猜测方向是对的,但不够精准:
- 问题的核心不是
LiteDbResultWrite要实现IDisposable,而是**LiteDatabase实例没有被正确释放**。LiteDatabase本身是实现了IDisposable接口的,必须调用Dispose()才能关闭文件句柄。 - 你的
LiteContext持有LiteDatabase实例,但没有实现IDisposable,这意味着如果LiteContext被多次创建,旧的LiteDatabase实例不会被及时销毁,文件句柄会一直被占用,最终导致句柄泄漏和锁问题。
3. 排查与解决方案
排查方向
- 检查依赖注入配置:如果你的应用用了DI容器(比如ASP.NET Core DI),先确认
LiteContext的注册方式。如果是AddTransient或AddScoped,每次请求/操作都会创建新的LiteContext,进而生成新的LiteDatabase实例,这是句柄泄漏的常见原因。 - 用Process Explorer分析句柄:找到服务进程(PID 1736),查看
TimeSeries.db的文件句柄详情,通过句柄的调用栈(Process Explorer里可查看)定位到底是哪里重复创建了数据库连接。 - 完善日志记录:你的代码只捕获了
Insert的异常,但可能存在其他未捕获的异常导致资源没被回收,或者异步操作中的泄漏。建议添加日志,跟踪LiteContext的创建次数、LiteDatabase的使用情况,以及异常细节。
解决方案
- 给
LiteContext实现IDisposable:public class LiteContext : IDisposable { public LiteDatabase Database { get; } private bool _disposed = false; public LiteContext() { Database = new LiteDatabase(@"Filename=C:\Users\XXX\AppData\Local\TimeSeries.db"); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { Database?.Dispose(); // 释放数据库连接,关闭文件句柄 } _disposed = true; } ~LiteContext() { Dispose(false); } } - 确保
LiteContext是单例:在DI容器里把LiteContext注册为单例,确保整个服务生命周期内只有一个LiteDatabase实例:services.AddSingleton<LiteContext>(); - 优化
LiteDbResultWrite的注册:如果LiteDbResultWrite是通过DI注入的,建议注册为单例或范围服务,避免重复创建,同时确保服务停止时能正确释放所有资源。 - 显式配置LiteDB连接选项:可以在连接字符串里指定
Connection=Exclusive,强制独占数据库连接,避免多实例冲突:
这个配置会确保只有一个实例能连接数据库,从根源上避免多实例导致的锁问题,但前提是你的服务确实是单实例运行。Database = new LiteDatabase(@"Filename=C:\Users\XXX\AppData\Local\TimeSeries.db; Connection=Exclusive");
总结
文件占用条目增加的本质是LiteDatabase实例重复创建且未被正确释放,导致文件句柄泄漏。你的猜测方向正确,但需要从LiteContext的资源释放和单例配置入手解决。通过实现IDisposable、确保单例注册、排查DI配置,基本能解决这类问题。
内容的提问来源于stack exchange,提问作者user584018




