You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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条。

我有几个疑问:

  1. 明明服务是单实例运行,为什么文件占用条目会新增?最终导致锁问题的原因是什么?
  2. 问题只在部分生产服务器出现,该怎么排查和解决?
  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的注册方式。如果是AddTransientAddScoped,每次请求/操作都会创建新的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

火山引擎 最新活动