Transactions and Concurrency - negue/LiteDB GitHub Wiki

LiteDB implements ACID transactions across multiple documents like a RDBMS database.

using(var db = new LiteDatabase("mydata.db"))
{
    var col = db.GetCollection<Customer>("customer");

    db.BeginTrans();

    col.Insert(new Customer { Name = "John Doe" });
    col.Insert(new Customer { Name = "Joanna Doe" });
    col.Insert(new Customer { Name = "Foo Bar" });

    db.Commit(); // all or none!
}

How transactions works

LiteDB use CacheService to track pages that are read from disk. When a page change, cache keeps in memory this changes until an Commit() is called. Until commit, no change occurs on data file, just on memory. If a Rollback() are called, cache service discard all changed pages.

Transaction are required to LiteDB works. If omitted in write operations, like Insert(), Update() and Delete(), LiteDB will create an auto transaction for each operation. This is a slow solution if you compare to use BeginTrans() at start and Commit() on end, because auto transaction writes on disk after each document change.

Concurrency

LiteDB supports multiple connections on same data file. To use this, you must use multiple instances of LiteDatabase. Do not reuse same instance across mulitple threads/processes. LiteDatabase implements IDisposable, so you always need use and using statement or call Dispose method to guarantee that data file will be closed.

Concurrency is guaranteed by locking your data file using FileStream.Lock() method. LiteDB implement 3 modes of locking:

  • UNLOCKED - No locks on data file - any client can read or write data.
  • SHARED - When the first client calls BeginTrans() data file locks to shared mode. In shared mode, any other can read but can't open new transactions. BeginTrans() for others will be hold until first transaction finish.
  • EXCLUSIVE - When Commit() all called, LiteDB start writing pages on disk. While this writing operation, data file are in exclusive lock. In exclusive lock, no others can even read data from disk. When finish write on disk, data file backs to unlocked state.

In StringConnection you can define timeout for this hold locking. Default is 1 minute.

Nested transactions

LiteDB supports nested transactions. You can have nested BeginTrans() and Commit() inside a main transaction. All nested transaction just are treated like a simple stack counter. Only main transaction Commit() will write all changes in disk.

But, if a Rollback() occurs during any transaction (main or nested) rollback command will be executed and transaction stack are cleared.

LiteDB throws exceptions and rollback transaction when has any error occurs during an write operation, like insert duplicate key.

using(var db = new LiteDatabase("mydata.db"))
{
    var col = db.GetCollection("customer");

    db.BeginTrans();
    col.Insert(new Customer { Id = 1, Name = "John" });

    db.BeginTrans(); // Stack +1 
    col.Insert(new Customer { Id = 2, Name = "Joana" });
    db.Commit();     // Stack -1 

    col.Insert(new Customer { Id = 1, Name = "Foo Bar" }); // Causes index duplicate key

    // An exception will be throw and a rollback will revert all data
    // no data will be saved! 

    db.Commit();
}
⚠️ **GitHub.com Fallback** ⚠️