Savepoints offer the same protection as transactions. The difference is that savepoints can be nested, when transactions can't. When you want to make sure some statements are protected by a transaction, but don't know if a transaction is already active or not, savepoints are perfect.
Most methods that write and accept a Database should use a savepoint:
func doStuff(_ db: Database) throws {
try db.inSavepoint { ... }
}
// 3 × Fine
try dbQueue.write(doStuff) // implicit transaction
try dbQueue.writeWithoutTransaction(doStuff) // no transaction
try dbQueue.inTransaction { db in // explicit transaction
try doStuff(db)
return .commit
}
GRDB documentation fosters the simple write, which opens an implicit transaction and brings the most safety.
Other writing methods allow manual transaction handling, and target advanced use cases.
If you run a single process, and this process uses a single instance of DatabaseQueue or DatabasePool, then concurrent writes are serialized. Practically speaking, all threads can call a writing method, but there is never two threads that write in the database at the same time. Some threads will just wait until other writes are completed. There is no parallel write (SQLite does not support this).
If you run multiple processes that write in the database, then you should carefully read the Sharing a Database guide.
If you run a single process, and this process creates several instances of DatabaseQueue or DatabasePool on the same database file, then this is most probably a misuse. Refactor your app so that you share a single instance of DatabaseQueue or DatabasePool. See the Concurrency Guide for more information.
In our (insert + fetch) group, the risk is THERE:
func insertReturningPlayer(_ db: Database) throws -> Player {
try insert(db)
// <- THERE
return try Player
.filter(Column.rowID == db.lastInsertedRowID)
.fetchOne(db)!
}
THERE, a concurrent writer (another process, or another instance of DatabaseQueue or DatabasePool) could delete the freshly inserted player. A transaction brings this risk to zero.