Example: Turbo Boost Mode
Some developers are desperate to wring every last bit of performance out of their database, even to the point of risking data loss or corruption. Some PRAGMA
statements tie into performance this way.
For example, normally, many times when SQLite writes data to disk, it will use fsync()
or the equivalent to block until all of the bytes are confirmed to be written. This is important in operating systems with write-caching filesystems, as otherwise the data that you think that you wrote might actually just be in a buffer waiting to be written in the future. Android, when using the ext4
filesystem, is one such OS. However, PRAGMA synchronous = OFF
tells SQLite to skip those fsync()
calls. This speeds up I/O, with increased risk of the database becoming corrupted if there is a major system problem while that I/O is going on. This is a transient PRAGMA
, only affecting the current connection.
Even riskier is PRAGMA journal_mode = MEMORY
. In effect, this says to keep the transaction log of the database in memory, rather than writing it to disk. As the documentation states, “if the application using SQLite crashes in the middle of a transaction when the MEMORY journaling mode is set, then the database file will very likely go corrupt”. But, some people would consider performance gains as being a valid trade-off here. This is a persistent setting, and so it only needs to be applied once.
The approach for both of these cases is to use a RoomDatabase.Callback
, as seen in the PragmaTest
sample module from the book’s primary sample project. RoomDatabase.Callback
was introduced in the chapter on the support database API.
This is a tests-only module, one that tests importing a bunch of city population data from a JSON file into a Room-powered database called CityDatabase
.
The newInstance()
method that we use to create an instance of our CityDatabase
uses a RoomDatabase.Builder
as normal. However, based on a boolean
parameter, it may also use addCallback()
to add a RoomDatabase.Callback
to the builder:
package com.commonsware.room.pragmatest
import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
@Database(entities = [City::class], version = 1)
abstract class CityDatabase : RoomDatabase() {
abstract fun cityStore(): City.Store
companion object {
@VisibleForTesting
const val DB_NAME = "un.db"
fun newInstance(ctxt: Context, applyPragmas: Boolean): CityDatabase {
val builder = Room.databaseBuilder(
ctxt,
CityDatabase::class.java,
DB_NAME
)
if (applyPragmas) {
builder.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
db.query("PRAGMA journal_mode = MEMORY")
}
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
db.query("PRAGMA synchronous = OFF")
}
})
}
return builder.build()
}
}
}
There are two methods that you can supply on a Callback
implementation: onCreate()
and onOpen()
. As the names suggest, they are called when the database is created and opened, respectively. In each, you are handed a SupportSQLiteDatabase
instance, which has an API reminiscent of the framework’s SQLiteDatabase
. It has a query()
method that works like rawQuery()
, taking a simple SQL statement (that might return a result set) and executing it. Since PRAGMA
might return a result set, we have to use query()
instead of execSQL()
. Here, we invoke our PRAGMA
statements at the appropriate times.
And, in truth, there does seem to be a performance gain:
Scenario | Use the PRAGMA s? |
Time (milliseconds) |
---|---|---|
Inserting 1,063 cities via individual insert() calls |
No | 1,135 |
Inserting 1,063 cities via individual insert() calls |
Yes | 960 |
Inserting 1,063 cities in a single insert() call |
No | 393 |
Inserting 1,063 cities in a single insert() call |
Yes | 252 |
(tests conducted on a Google Pixel 4)
Proper use of transactions — such as doing all of the inserts at once rather than one at a time — has a much bigger impact, though. Using these two PRAGMA
statements is a bit like using a holodeck with the safeties off: you may have some casualties.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.