Skip to content

Performance

github-actions[bot] edited this page Apr 1, 2026 · 5 revisions

Performance

Benchmark: Bulk Insert

Inserting 1000 rows with a transaction takes a fraction of the time compared to inserting them one by one without one. SQLite commits each write to disk by default, so individual inserts are slow.

var entities = Enumerable.Range(0, 1000)
    .Select(i => new Book { Title = $"Book {i}", AuthorId = 1, Price = 9.99m })
    .ToList();

// Fast: all inserts in a single transaction (default behavior)
await db.Table<Book>().AddRangeAsync(entities);

// Slow: one transaction per insert
await db.Table<Book>().AddRangeAsync(entities, runInTransaction: false);

On a typical device, inserting 1000 rows with a transaction completes in under 0.1 seconds. Without a transaction it can take 5 to 10 seconds or more (depending on the device).

Tips

Use AddRange for bulk inserts

AddRangeAsync wraps everything in a single transaction by default. Do not call AddAsync in a loop.

// Good
await db.Table<Book>().AddRangeAsync(books);

// Slow
foreach (var book in books)
    await db.Table<Book>().AddAsync(book);

Use ExecuteDelete and ExecuteUpdate for bulk changes

These run a single SQL statement. Loading rows into memory to update or delete them one by one is much slower.

// Good: one SQL statement
await db.Table<Book>()
    .Where(b => b.InStock == false)
    .ExecuteDeleteAsync();

// Slow: loads all rows, then deletes each one
var stale = await db.Table<Book>().Where(b => b.InStock == false).ToListAsync();
await db.Table<Book>().RemoveRangeAsync(stale);

Select only the columns you need

Projecting to a smaller type reduces the data SQLite has to read and the objects .NET has to allocate.

// Only fetches Title and Price columns
var summaries = await db.Table<Book>()
    .Select(b => new { b.Title, b.Price })
    .ToListAsync();

Add indexes for columns you filter or sort on

Without an index, SQLite scans the whole table for every query. Add [Indexed] to columns that appear in Where, OrderBy, or Join conditions.

public class Book
{
    [Key]
    [AutoIncrement]
    public int Id { get; set; }

    [Indexed(Name = "IX_Book_AuthorId")]
    public int AuthorId { get; set; }

    [Indexed(Name = "IX_Book_Price")]
    public decimal Price { get; set; }
}

Wrap related operations in a transaction

If you are inserting or updating rows across multiple tables as part of one logical operation, put them in a transaction. This is both safer (all or nothing) and faster (one disk commit instead of many).

await using SQLiteTransaction tx = await db.BeginTransactionAsync();

await db.Table<Author>().AddRangeAsync(authors, runInTransaction: false);
await db.Table<Book>().AddRangeAsync(books, runInTransaction: false);

transaction.Commit();

Reuse the SQLiteDatabase instance

The connection is lazy and opens once. Creating a new SQLiteDatabase for every operation is wasteful. Create it once and keep it alive for the lifetime of your app or service.

Use WITHOUT ROWID for lookup tables

If a table is mostly looked up by primary key and rarely scanned in order, [WithoutRowId] can reduce the number of B-tree lookups. See Defining Models for usage.

Clone this wiki locally