Evaluating SQLite Performance by Testing All Parameters
First, some information about the parameters.
Open mode – No-mutex or Full-mutex
If the SQLITE_OPEN_NOMUTEX flag is set, then the database connection opens in the multi-thread threading mode as long as the single-thread mode has not been set at compile-time or start-time. If the SQLITE_OPEN_FULLMUTEX flag is set then the database connection opens in the serialized threading mode unless single-thread was previously selected at compile-time or start-time. (Open mode)
Locking mode – Exclusive or Normal
In NORMAL locking-mode (the default unless overridden at compile-time using SQLITE_DEFAULT_LOCKING_MODE), a database connection unlocks the database file at the conclusion of each read or write transaction. When the locking-mode is set to EXCLUSIVE, the database connection never releases file-locks. The first time the database is read in EXCLUSIVE mode, a shared lock is obtained and held. The first time the database is written, an exclusive lock is obtained and held. (Locking mode)
Transaction mode – Deferred, Immediate, or Exclusive
After a BEGIN IMMEDIATE, no other database connection will be able to write to the database or do a BEGIN IMMEDIATE or BEGIN EXCLUSIVE. Other processes can continue to read from the database, however. … After a BEGIN EXCLUSIVE, no other database connection except for read_uncommitted connections will be able to read the database and no other connection without exception will be able to write the database until the transaction is complete. (Transaction mode)
Synchronous mode – Full, Normal, or Off
When synchronous is FULL (2), the SQLite database engine will use the xSync method of the VFS to ensure that all content is safely written to the disk surface prior to continuing. This ensures that an operating system crash or power failure will not corrupt the database. FULL synchronous is very safe, but it is also slower. FULL is the most commonly used synchronous setting when not in WAL mode.
and
WAL mode is safe from corruption with synchronous=NORMAL, and probably DELETE mode is safe too on modern filesystems. WAL mode is always consistent with synchronous=NORMAL, but WAL mode does lose durability. A transaction committed in WAL mode with synchronous=NORMAL might roll back following a power loss or system crash. Transactions are durable across application crashes regardless of the synchronous setting or journal mode. The synchronous=NORMAL setting is a good choice for most applications running in WAL mode. (Synchronous mode)
Journal Mode – WAL, Truncate, or Memory
The TRUNCATE journaling mode commits transactions by truncating the rollback journal to zero-length instead of deleting it. On many systems, truncating a file is much faster than deleting the file since the containing directory does not need to be changed. … The MEMORY journaling mode stores the rollback journal in volatile RAM. This saves disk I/O but at the expense of database safety and integrity. 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. … The WAL journaling mode uses a write-ahead log instead of a rollback journal to implement transactions. The WAL journaling mode is persistent; after being set it stays in effect across multiple database connections and after closing and reopening the database. A database in WAL journaling mode can only be accessed by SQLite version 3.7.0 (2010-07-21) or later. (Journal mode)
To help me decide which parameters are optimal for my use case I’ve set up a test running permutations of available parameters when setting up a SQLite connection. I perform a series of operations then sort the timings below on both an SSD and an HDD. Here is the generator of permuted parameters to test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // Permutations of parameters: 1152 List<List<Serializable>> permutations = Lists.cartesianProduct( List.of( // Open mode List.of( SQLiteOpenMode.NOMUTEX, SQLiteOpenMode.FULLMUTEX ), // Cache size List.of( 1000, 2000, 4000, 8000 ), // Page size List.of( 1024, 2048, 4096, 8192 ), // Locking mode List.of( LockingMode.NORMAL, LockingMode.EXCLUSIVE ), // Transaction mode List.of( TransactionMode.IMMEDIATE, TransactionMode.EXCLUSIVE ), // Journal size limit List.of( -1 ), // Synchronous mode List.of( SynchronousMode.FULL, SynchronousMode.NORMAL, SynchronousMode.OFF ), // Journal mode List.of( JournalMode.WAL, JournalMode.TRUNCATE, JournalMode.MEMORY ) ) ); |
Here is the test table schema and composite index:
Note: All the inserts are in batch mode with auto-commit false and deliberate committing of the inserts at the end of the batch.
Insert 50,000 rows (SSD)
Preliminarily, it looks like NOMUTEX
open mode with synchronization OFF
, and either MEMORY
or TRUNCATE
with a large page size allow for the fastest inserts. A large page size (8192 KB
) seems to be in common with faster inserts.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | Insert 50,000 took 113.7 ms ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, OFF, TRUNCATE]) Insert 50,000 took 114.3 ms ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, OFF, MEMORY]) Insert 50,000 took 114.3 ms ([NOMUTEX, 8000, 8192, EXCLUSIVE, IMMEDIATE, OFF, MEMORY]) Insert 50,000 took 114.8 ms ([NOMUTEX, 4000, 8192, EXCLUSIVE, IMMEDIATE, OFF, TRUNCATE]) Insert 50,000 took 115.0 ms ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, OFF, TRUNCATE]) Insert 50,000 took 115.1 ms ([NOMUTEX, 2000, 8192, EXCLUSIVE, IMMEDIATE, OFF, TRUNCATE]) Insert 50,000 took 115.5 ms ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, OFF, MEMORY]) Insert 50,000 took 116.0 ms ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, OFF, TRUNCATE]) Insert 50,000 took 116.0 ms ([NOMUTEX, 4000, 8192, NORMAL, EXCLUSIVE, OFF, MEMORY]) Insert 50,000 took 116.2 ms ([NOMUTEX, 4000, 8192, EXCLUSIVE, IMMEDIATE, OFF, MEMORY]) ...[snip]... Insert 50,000 took 307.0 ms ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, NORMAL, MEMORY]) Insert 50,000 took 316.3 ms ([FULLMUTEX, 1000, 1024, EXCLUSIVE, EXCLUSIVE, NORMAL, MEMORY]) Insert 50,000 took 335.3 ms ([FULLMUTEX, 2000, 4096, NORMAL, EXCLUSIVE, NORMAL, WAL]) Insert 50,000 took 348.9 ms ([NOMUTEX, 2000, 4096, NORMAL, IMMEDIATE, OFF, MEMORY]) Insert 50,000 took 351.4 ms ([NOMUTEX, 1000, 1024, NORMAL, IMMEDIATE, FULL, TRUNCATE]) Insert 50,000 took 355.1 ms ([FULLMUTEX, 2000, 1024, NORMAL, IMMEDIATE, NORMAL, TRUNCATE]) Insert 50,000 took 368.1 ms ([FULLMUTEX, 1000, 1024, NORMAL, EXCLUSIVE, FULL, MEMORY]) Insert 50,000 took 368.1 ms ([FULLMUTEX, 2000, 4096, NORMAL, EXCLUSIVE, NORMAL, MEMORY]) Insert 50,000 took 436.4 ms ([NOMUTEX, 1000, 1024, NORMAL, IMMEDIATE, FULL, WAL]) Insert 50,000 took 541.6 ms ([NOMUTEX, 1000, 4096, EXCLUSIVE, IMMEDIATE, FULL, MEMORY]) |
Insert 50,000 rows twice (SSD)
I need a non-memory journal, so I try this again with WAL
and TRUNCATE
journalling modes. I insert all the records twice to stress the index and journal. Again the TRUNCATE
journal mode outperformed WAL
on an SSD when purely inserting. Also, having a large page size improves the speed with synchronization OFF
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | Insert 50,000 twice took 199.2 ms ([NOMUTEX, 8000, 8192, EXCLUSIVE, IMMEDIATE, OFF, TRUNCATE]) Insert 50,000 twice took 203.2 ms ([NOMUTEX, 8000, 8192, NORMAL, IMMEDIATE, OFF, TRUNCATE]) Insert 50,000 twice took 203.3 ms ([NOMUTEX, 4000, 8192, NORMAL, IMMEDIATE, OFF, TRUNCATE]) Insert 50,000 twice took 203.6 ms ([NOMUTEX, 8000, 8192, NORMAL, EXCLUSIVE, OFF, TRUNCATE]) Insert 50,000 twice took 204.5 ms ([NOMUTEX, 8000, 8192, NORMAL, EXCLUSIVE, FULL, TRUNCATE]) Insert 50,000 twice took 204.7 ms ([NOMUTEX, 8000, 4096, NORMAL, EXCLUSIVE, OFF, TRUNCATE]) Insert 50,000 twice took 204.9 ms ([NOMUTEX, 8000, 4096, EXCLUSIVE, EXCLUSIVE, OFF, TRUNCATE]) Insert 50,000 twice took 205.0 ms ([NOMUTEX, 8000, 8192, EXCLUSIVE, EXCLUSIVE, FULL, TRUNCATE]) Insert 50,000 twice took 205.4 ms ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, OFF, TRUNCATE]) Insert 50,000 twice took 205.5 ms ([NOMUTEX, 8000, 4096, EXCLUSIVE, IMMEDIATE, OFF, TRUNCATE]) ...[snip]... Insert 50,000 twice took 418.3 ms ([NOMUTEX, 8000, 1024, NORMAL, EXCLUSIVE, OFF, WAL]) Insert 50,000 twice took 421.3 ms ([FULLMUTEX, 4000, 1024, NORMAL, EXCLUSIVE, OFF, WAL]) Insert 50,000 twice took 422.8 ms ([FULLMUTEX, 2000, 8192, EXCLUSIVE, EXCLUSIVE, NORMAL, TRUNCATE]) Insert 50,000 twice took 425.1 ms ([FULLMUTEX, 1000, 1024, EXCLUSIVE, IMMEDIATE, FULL, TRUNCATE]) Insert 50,000 twice took 429.6 ms ([FULLMUTEX, 8000, 8192, EXCLUSIVE, IMMEDIATE, OFF, WAL]) Insert 50,000 twice took 431.2 ms ([FULLMUTEX, 8000, 1024, NORMAL, EXCLUSIVE, FULL, TRUNCATE]) Insert 50,000 twice took 433.5 ms ([FULLMUTEX, 4000, 4096, NORMAL, IMMEDIATE, FULL, TRUNCATE]) Insert 50,000 twice took 446.1 ms ([NOMUTEX, 4000, 2048, NORMAL, EXCLUSIVE, OFF, WAL]) Insert 50,000 twice took 484.8 ms ([FULLMUTEX, 2000, 4096, EXCLUSIVE, EXCLUSIVE, NORMAL, TRUNCATE]) Insert 50,000 twice took 844.6 ms ([NOMUTEX, 1000, 1024, NORMAL, IMMEDIATE, FULL, WAL]) |
Insert 500,000 rows twice (SSD)
NOMUTEX
is consistently faster than FULLMUTEX
, so I use that only from now on. Next, I insert 500,000 rows twice. The takeaway is that TRUNCATE
journal mode beats WAL
on an SSD when purely inserting. Again, a large page size improves speed. Synchronization mode has no noticeable impact on speed here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | Insert 500,000 twice took 2.402 s ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, OFF, TRUNCATE]) Insert 500,000 twice took 2.418 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, FULL, TRUNCATE]) Insert 500,000 twice took 2.434 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, FULL, TRUNCATE]) Insert 500,000 twice took 2.453 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, NORMAL, TRUNCATE]) Insert 500,000 twice took 2.476 s ([NOMUTEX, 2000, 8192, NORMAL, EXCLUSIVE, FULL, TRUNCATE]) Insert 500,000 twice took 2.476 s ([NOMUTEX, 4000, 8192, EXCLUSIVE, EXCLUSIVE, NORMAL, TRUNCATE]) Insert 500,000 twice took 2.479 s ([NOMUTEX, 1000, 4096, EXCLUSIVE, IMMEDIATE, FULL, TRUNCATE]) Insert 500,000 twice took 2.486 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, OFF, TRUNCATE]) Insert 500,000 twice took 2.492 s ([NOMUTEX, 1000, 4096, EXCLUSIVE, EXCLUSIVE, FULL, TRUNCATE]) Insert 500,000 twice took 2.500 s ([NOMUTEX, 4000, 8192, NORMAL, EXCLUSIVE, NORMAL, TRUNCATE]) ...[snip]... Insert 500,000 twice took 3.718 s ([NOMUTEX, 2000, 8192, EXCLUSIVE, IMMEDIATE, FULL, TRUNCATE]) Insert 500,000 twice took 3.775 s ([NOMUTEX, 8000, 1024, NORMAL, EXCLUSIVE, NORMAL, TRUNCATE]) Insert 500,000 twice took 3.786 s ([NOMUTEX, 4000, 1024, EXCLUSIVE, IMMEDIATE, OFF, TRUNCATE]) Insert 500,000 twice took 3.804 s ([NOMUTEX, 2000, 2048, EXCLUSIVE, EXCLUSIVE, OFF, WAL]) Insert 500,000 twice took 4.026 s ([NOMUTEX, 8000, 1024, NORMAL, EXCLUSIVE, OFF, TRUNCATE]) Insert 500,000 twice took 4.205 s ([NOMUTEX, 1000, 1024, NORMAL, IMMEDIATE, NORMAL, WAL]) Insert 500,000 twice took 4.277 s ([NOMUTEX, 8000, 1024, EXCLUSIVE, EXCLUSIVE, OFF, TRUNCATE]) Insert 500,000 twice took 4.595 s ([NOMUTEX, 1000, 1024, NORMAL, IMMEDIATE, FULL, WAL]) Insert 500,000 twice took 4.641 s ([NOMUTEX, 1000, 1024, NORMAL, IMMEDIATE, NORMAL, TRUNCATE]) Insert 500,000 twice took 4.895 s ([NOMUTEX, 1000, 1024, NORMAL, IMMEDIATE, FULL, TRUNCATE]) |
Insert 50,000 rows twice, fewer degrees of freedom (SSD)
I again set NOMUTEX
, exclude OFF
synchronization (because power failure is a possibility), and use a page size of 8192 KB
with 1000
pages of cache since they performed best above. I test the rest of the parameters below. Of the 16 remaining permutations, the differences between them were negligible. I run each permutation several times to magnify the differences in the next section.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Insert 50,000 twice took 210.8 ms ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, FULL, TRUNCATE]) Insert 50,000 twice took 218.6 ms ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, FULL, TRUNCATE]) Insert 50,000 twice took 219.3 ms ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, NORMAL, TRUNCATE]) Insert 50,000 twice took 219.6 ms ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, NORMAL, TRUNCATE]) Insert 50,000 twice took 224.6 ms ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, NORMAL, WAL]) Insert 50,000 twice took 228.9 ms ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, FULL, WAL]) Insert 50,000 twice took 238.4 ms ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, NORMAL, WAL]) Insert 50,000 twice took 240.3 ms ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, FULL, TRUNCATE]) Insert 50,000 twice took 241.0 ms ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, NORMAL, WAL]) Insert 50,000 twice took 248.3 ms ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, NORMAL, TRUNCATE]) Insert 50,000 twice took 250.2 ms ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, FULL, WAL]) Insert 50,000 twice took 266.4 ms ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, NORMAL, WAL]) Insert 50,000 twice took 286.2 ms ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, NORMAL, TRUNCATE]) Insert 50,000 twice took 290.0 ms ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, FULL, TRUNCATE]) Insert 50,000 twice took 334.0 ms ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, FULL, WAL]) Insert 50,000 twice took 767.4 ms ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, FULL, WAL]) |
Insert 50,000 rows 10x, fewer degrees of freedom (SSD)
Repeating the above test but with 10 rounds of inserts each (to magnify parameter differences), the variations between the permutations of parameters remain negligible except for the very last permutation. WAL
actually performs slightly better than the traditional journal mode.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Insert 50,000 10x took 971.0 ms ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, FULL, WAL]) Insert 50,000 10x took 1.045 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, FULL, TRUNCATE]) Insert 50,000 10x took 1.088 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, NORMAL, WAL]) Insert 50,000 10x took 1.097 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, NORMAL, WAL]) Insert 50,000 10x took 1.111 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, NORMAL, TRUNCATE]) Insert 50,000 10x took 1.112 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, FULL, WAL]) Insert 50,000 10x took 1.128 s ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, FULL, TRUNCATE]) Insert 50,000 10x took 1.130 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, FULL, TRUNCATE]) Insert 50,000 10x took 1.133 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, NORMAL, TRUNCATE]) Insert 50,000 10x took 1.137 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, NORMAL, TRUNCATE]) Insert 50,000 10x took 1.144 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, NORMAL, WAL]) Insert 50,000 10x took 1.146 s ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, NORMAL, TRUNCATE]) Insert 50,000 10x took 1.146 s ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, FULL, WAL]) Insert 50,000 10x took 1.158 s ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, NORMAL, WAL]) Insert 50,000 10x took 1.185 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, FULL, TRUNCATE]) Insert 50,000 10x took 1.921 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, FULL, WAL]) |
Insert, copy 50,000 rows 10x, fewer degrees of freedom (SSD)
Below I insert 50,000 records, then I copy the database using the ATTACH
method ten times. Specifically,
1 2 3 4 | ATTACH DATABASE /tmp/empty.db AS copy; DROP TABLE IF EXISTS copy.test; CREATE TABLE copy.test AS SELECT * FROM test; DETACH DATABASE copy; |
Journal mode WAL
edged out TRUNCATE
for this scenario. All parameters performed reasonably well. Consistently the slowest performance results from the settings NORMAL
, IMMEDIATE
, FULL
, and WALL
together.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Insert, copy 50,000 10x took 1.554 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, NORMAL, WAL]) Insert, copy 50,000 10x took 1.586 s ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, NORMAL, WAL]) Insert, copy 50,000 10x took 1.608 s ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, FULL, WAL]) Insert, copy 50,000 10x took 1.648 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, NORMAL, WAL]) Insert, copy 50,000 10x took 1.702 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, FULL, TRUNCATE]) Insert, copy 50,000 10x took 1.714 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, FULL, TRUNCATE]) Insert, copy 50,000 10x took 1.723 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, FULL, WAL]) Insert, copy 50,000 10x took 1.726 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, NORMAL, WAL]) Insert, copy 50,000 10x took 1.811 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, NORMAL, TRUNCATE]) Insert, copy 50,000 10x took 1.825 s ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, FULL, TRUNCATE]) Insert, copy 50,000 10x took 1.846 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, FULL, WAL]) Insert, copy 50,000 10x took 1.875 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, FULL, TRUNCATE]) Insert, copy 50,000 10x took 1.876 s ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, NORMAL, TRUNCATE]) Insert, copy 50,000 10x took 1.879 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, NORMAL, TRUNCATE]) Insert, copy 50,000 10x took 1.928 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, NORMAL, TRUNCATE]) Insert, copy 50,000 10x took 3.130 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, FULL, WAL]) |
Insert, copy 50,000 rows 10x, fewer degrees of freedom (HDD)
I repeat the above scenario on an HDD. Aside from running four times slower, the results parallel the results on an SSD.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Insert, copy 50,000 10x took 6.024 s ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, NORMAL, WAL]) Insert, copy 50,000 10x took 6.069 s ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, FULL, WAL]) Insert, copy 50,000 10x took 6.266 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, FULL, WAL]) Insert, copy 50,000 10x took 6.289 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, FULL, WAL]) Insert, copy 50,000 10x took 6.320 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, NORMAL, TRUNCATE]) Insert, copy 50,000 10x took 6.321 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, NORMAL, WAL]) Insert, copy 50,000 10x took 6.340 s ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, FULL, TRUNCATE]) Insert, copy 50,000 10x took 6.356 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, NORMAL, WAL]) Insert, copy 50,000 10x took 6.429 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, IMMEDIATE, FULL, TRUNCATE]) Insert, copy 50,000 10x took 6.459 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, NORMAL, TRUNCATE]) Insert, copy 50,000 10x took 6.578 s ([NOMUTEX, 1000, 8192, NORMAL, EXCLUSIVE, NORMAL, TRUNCATE]) Insert, copy 50,000 10x took 6.786 s ([NOMUTEX, 1000, 8192, EXCLUSIVE, EXCLUSIVE, FULL, TRUNCATE]) Insert, copy 50,000 10x took 6.866 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, NORMAL, WAL]) Insert, copy 50,000 10x took 6.984 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, NORMAL, TRUNCATE]) Insert, copy 50,000 10x took 7.349 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, FULL, TRUNCATE]) Insert, copy 50,000 10x took 11.40 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, FULL, WAL]) |
Insert, copy 500,000 rows 10x: Full vs. Normal Sync (SSD)
To really compare the two synchronous modes (except OFF
), I perform two stressful tests with the parameters below. NORMAL
synchronous mode is much faster.
1 2 | Insert, copy 500,000 10x took 27.53 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, NORMAL, WAL]) Insert, copy 500,000 10x took 35.65 s ([NOMUTEX, 1000, 8192, NORMAL, IMMEDIATE, FULL, WAL]) |
Results
My use case is such that by using individual SQLite databases for each security I’m recording time-series data for – around 55,000 – I have a single thread per SQLite database. While I may have eight or more threads with network activity running simultaneously, I can avoid locking issues with this design, but I do need read-access for ETL and graphing in worker threads.
Open mode
Because I control the synchronization of the reads and writes in my own code, and my model is one thread per database, I can use the NOMUTEX
open mode confidently which lends itself to a noticeable speedup.
Locking mode
Locking mode had no noticeable impact on performance due to the single-thread-per-database model, so I opt for NORMAL
.
Transaction mode
I will use IMMEDIATE
transaction mode so I can perform some read-only ETL as well as graphing while a given database is being written to. There is no performance difference between this and an exclusive transaction here.
Journal mode
WAL
journal mode is theoretically faster than the classic journal mode, and the above tests leaned toward that. A page size of 8KB worked well. The cache size didn’t affect performance so I capped it at 1000 pages.
Synchronous mode
The man pages for SQLite state the NORMAL
synchronous mode works well with WAL
. I found that FULL
+WAL
was much slower as confirmed by the docs. Just to highlight this point, I ran the final test in the previous section and NORMAL
was clearly faster (27.5s vs. 35.7s).
Settings
These are the settings that are most performant for my use case which is similar to the above tests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | SQLiteConfig config = new SQLiteConfig(); final int cacheSize = 1000; final int pageSize = 8192; config.setReadOnly( readOnly ); config.setTempStore( TempStore.MEMORY ); // Hold indices in memory config.setCacheSize( cacheSize ); config.setPageSize( pageSize ); config.setJounalSizeLimit( cacheSize * pageSize ); config.setOpenMode( SQLiteOpenMode.NOMUTEX ); config.setLockingMode( LockingMode.NORMAL ); config.setTransactionMode( TransactionMode.IMMEDIATE ); config.setSynchronous( SynchronousMode.NORMAL ); // If read-only, then use the existing journal, if any if ( ! readOnly ) { config.setJournalMode( JournalMode.WAL ); } |
WAL
works better with the NORMAL
synchronous mode. My applications are already running faster.