[食譜好菜] LiteDB 查詢怎麼愈來愈慢?Index 建了嗎?

上一篇介紹 LiteDB 基本的 CRUD,但是我相信這還是有點不太夠的,隨著寫入的資料愈來愈多,做非主鍵條件查詢肯定是會愈來愈慢的,LiteDB 在沒有建 Index 的情況之下,如果查詢條件不是主鍵,它是對整個 DB 做 Full Document Scan,意謂著 LiteDB 必須將每一筆資料反序化出來之後一筆一筆去比對,不僅慢又浪費記憶體。

EnsureIndex()

在 LiteDB 建 Index 的方式非常簡單,選定欄位、呼叫 EnsureIndex() 就完成了,而且只要呼叫一次就有作用,即使重覆呼叫,已經建立的 Index 也不會重建。

using (var db = new LiteDatabase(ConnectionString))
{
    var collection = db.GetCollection<Sample>();

    collection.EnsureIndex(x => x.Name);
}

原本 15 萬筆的資料在沒有建 Index 的情況下,查詢 Name 欄位等於某個特定值的時間要 11 秒,建完 Index 只要 3 毫秒,有沒有 Index 真的差很多。

使用 Index 加入查詢條件

舉個例子來說,我們現在要找出最晚出生的人是誰?但是,程式碼千萬不要這樣寫:

using (var db = new LiteDatabase(ConnectionString))
{
    var collection = db.GetCollection<Sample>();

    var firstBorn = collection.FindAll().OrderByDescending(x => x.Birthday).First();
}

這樣寫意謂著把資料都序列化後才對 Birthday 降冪排序取第一筆,慢又消耗記憶體。

我們改用 Query Method 利用已經建好的 Index 來做查詢:

using (var db = new LiteDatabase(ConnectionString))
{
    var collection = db.GetCollection<Sample>();

    var firstBorn = collection.Find(Query.All(nameof(Sample.Birthday), Query.OrderByDescending), limit: 1).Single();
}

消耗的資源立馬就降下來了

再加個條件,要找出 2000 年以前最晚出生的是誰?經過剛剛的例子我們知道不應該這樣寫:

using (var db = new LiteDatabase(ConnectionString))
{
    var collection = db.GetCollection<Sample>();

    var firstBorn = collection.Find(x => x.Birthday < new DateTime(2000, 1, 1))
        .OrderByDescending(x => x.Birthday)
        .First();
}

應該改這樣:

using (var db = new LiteDatabase(ConnectionString))
{
    var collection = db.GetCollection<Sample>();

    var firstBorn = collection.Find(
            Query.Where(nameof(Sample.Birthday), value => value.AsDateTime < new DateTime(2000, 1, 1), Query.Descending),
            limit: 1)
        .Single();
}

限制

LiteDB 的 Index 有兩個限制:

  1. 拿來建立 Index 的欄位值必須小於 512 bytes。
  2. 每個 Collection 最多只能建 16 個 Index(包含 PrimaryKey)。

參考資料

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學