Skip to main content
GRDB provides two classes for accessing SQLite databases: DatabaseQueue and DatabasePool.

Opening Connections

import GRDB

// Open a database file
let dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite")

// Or create an in-memory database
let dbQueue = try DatabaseQueue()

DatabaseQueue vs DatabasePool

Choose the right connection type for your needs:
FeatureDatabaseQueueDatabasePool
Concurrent reads❌ No✅ Yes
WAL modeOptionalAlways (unless read-only)
In-memory databases✅ Yes❌ No
Thread safety✅ Yes✅ Yes
Memory usageLowerHigher
If you are not sure, choose DatabaseQueue. You can always switch to DatabasePool later when you need concurrent read access.

DatabaseQueue

DatabaseQueue provides a single serialized database connection:
public final class DatabaseQueue {
    public init(path: String, configuration: Configuration = Configuration()) throws
    public init(named name: String? = nil, configuration: Configuration = Configuration()) throws
}

Reading from DatabaseQueue

try dbQueue.read { db in
    let count = try Player.fetchCount(db)
    let players = try Player.fetchAll(db)
    return players
}

Writing to DatabaseQueue

try dbQueue.write { db in
    try Player(name: "Arthur", score: 100).insert(db)
    try Player(name: "Barbara", score: 1000).insert(db)
}

In-Memory Databases

DatabaseQueue supports in-memory databases:
// Each connection gets its own independent database
let dbQueue = try DatabaseQueue()

DatabasePool

DatabasePool manages a pool of database connections for concurrent reads:
public final class DatabasePool {
    public init(path: String, configuration: Configuration = Configuration()) throws
}

Key Features

  • Concurrent Reads: Multiple threads can read simultaneously
  • WAL Mode: Automatically enables Write-Ahead Logging
  • Single Writer: Writes are serialized
  • Snapshot Isolation: Readers see a consistent snapshot

Reading from DatabasePool

// Concurrent reads are isolated from writes
try dbPool.read { db in
    let players = try Player.fetchAll(db)
    return players
}

Writing to DatabasePool

Writes are serialized through a single writer connection:
try dbPool.write { db in
    try Player(name: "Arthur", score: 100).insert(db)
}

Async Concurrent Reads

DatabasePool provides snapshot isolation for concurrent reads:
try dbPool.writeWithoutTransaction { db in
    try Player.deleteAll(db)
    
    // Read concurrently with guaranteed snapshot isolation
    dbPool.asyncConcurrentRead { dbResult in
        do {
            let db = try dbResult.get()
            // Guaranteed to see zero players
            let count = try Player.fetchCount(db)
        } catch {
            // Handle error
        }
    }
    
    try Player(name: "Arthur", score: 100).insert(db)
}

Configuration Options

Both connection types accept a Configuration object:
var config = Configuration()
config.readonly = true
config.foreignKeysEnabled = true
config.label = "MyDatabase"

let dbQueue = try DatabaseQueue(path: dbPath, configuration: config)

Common Configuration Options

var config = Configuration()
config.readonly = true
let dbQueue = try DatabaseQueue(path: dbPath, configuration: config)

Memory Management

Both connection types provide memory management methods:
// Release as much memory as possible (blocks current thread)
dbQueue.releaseMemory()
dbPool.releaseMemory()

// DatabasePool also provides an async version
dbPool.releaseMemoryEventually()
On iOS, GRDB automatically releases memory when the app receives memory warnings or enters the background.

Closing Connections

Connections are automatically closed when deallocated, but you can explicitly close them:
try dbQueue.close()
try dbPool.close()
After closing, the connection cannot be reopened. Any database access will throw an error.

Thread Safety

Both DatabaseQueue and DatabasePool are thread-safe:
  • DatabaseQueue: Serializes all database access on a single dispatch queue
  • DatabasePool: Allows concurrent reads on multiple connections, writes on a single connection
// Safe to call from any thread
DispatchQueue.global().async {
    try? dbQueue.read { db in
        // Read from database
    }
}

DispatchQueue.global().async {
    try? dbQueue.write { db in
        // Write to database
    }
}

Best Practices

Start with DatabaseQueue for simplicity, then upgrade to DatabasePool if you need concurrent reads.

Next Steps

Build docs developers (and LLMs) love