I've read most of the posts I could find on optimizing SQLite3 performance, such as:
- How do I improve the performance of SQLite?,
- Is it possible to insert multiple rows at a time in an SQLite database?
- What are the performance characteristics of sqlite with very large database files?
Which are good, but they have not been updated for a few SQLite releases, so I went for "testing before believing". In the "testing" I became suspicious of whether my approach to benchmarking of the modes of using SQLite is correct? The reason for asking is that I get a much lower threshold difference in performance - which is slightly contrary to the posts I've read, where some report orders of magnitude in difference.
for example http://www.sqlite.org/pragma.html#pragma_journal_mode comments:
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.
But I cannot replicate the drop/increase in performance which should be "expected" from performing single transactions in different modes. So I wonder: Has SQLite3 evolved so much that these differences become a legend of the past? - or did I just miss something?
Update: Yes something was missing: The documentation states that:
By default, the sqlite3 module opens transactions implicitly before a Data Modification Language (DML) statement (i.e. INSERT/UPDATE/DELETE/REPLACE), and commits transactions implicitly before a non-DML, non-query statement (i. e. anything other than SELECT or the aforementioned).
So if you are within a transaction and issue a command like CREATE TABLE ..., VACUUM, PRAGMA, the sqlite3 module will commit implicitly before executing that command.
This might not to be the case, as pysqlite appears cycles through the operation without executing a commit between "execute" statements. I don't know how to test for this?
Results summary
Using SQLite 3.6.21 and pysqlite version 2.6.0 and python 2.7.5
Code
Update 22:26 31/05/2013: Generator added: Creates synthetic data for review if file is not available.#!/usr/bin/python27
import sys
import csv
import time
import sqlite3
from datetime import time as Time # used only for generating data if file is absent
from random import randint # used only for generating data if file is absent
def get_the_data():
msg = """
Thanks to http://opendata.toronto.ca/TTC/routes/
OpenData_TTC_Schedules.zip/stop_times.txt
for the data.
"""
print msg
""" Returns a list with this:
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
19985180,6:15:00,6:15:00,14146,1,,0,0,
19985180,6:16:41,6:16:41,3807,2,,0,0,0.5355
19985180,6:17:41,6:17:41,6904,3,,0,0,0.8543
...
(many lines later)
...
20064514,25:41:41,25:41:41,7531,65,,0,0,17.6094
20064514,25:42:00,25:42:00,13118,66,,1,1,17.6794
"""
data=[]
start = time.clock()
try:
with open(r'D:\OpenData_TTC_Schedules\stop_times.txt') as fi:
for row in csv.reader(fi, delimiter=','):
data.append(tuple(row)) # SQLite's "executemany()" needs tuples.
data.pop(0) # get rid of headers.
except IOError:
print "\tfile not found - generating data..."
data=syntheticData()
end = time.clock()
wall_time = end - start
print "\tFetched %d lines of data, or %.3f Mb, in %.3f seconds" %(len(data), sys.getsizeof(data)/float(10**6), wall_time)
print "\t(Reading csv from disk = %d rows per second)." %(len(data)/wall_time)
return data
def syntheticData(length=4*10**5):
alist=[]
for i in range(length):
trip_id = 19985180+i
arrival_time = Time(randint(0,23),randint(0,59),randint(0,59)).isoformat()
departure_time = Time(randint(0,23),randint(0,59),randint(0,59)).isoformat()
stop_id = randint(1,15000)
stop_sequence = randint(1,66)
stop_headsign = ''
pickup_type = randint(0,1)
drop_off_type = randint(0,1)
shape_dist_traveled = round(float(randint(5000,25000)/1000),4)
row=tuple([trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled])
alist.append(row)
return alist
def transaction(data, DB, SQL_create_table, SQL, PRAGMA="ON", JOURNAL="", BATCH=True):
con = sqlite3.connect(DB)
cur = con.cursor()
if PRAGMA=="OFF":
cur.execute("PRAGMA synchronous = OFF")
if JOURNAL!="":
cur.execute("PRAGMA journal_mode = %s" %(JOURNAL))
cur.execute(SQL_create_table) # Create the table.
start = time.clock()
if BATCH==True:
cur.executemany(SQL, data) # Do the insert test
# Save (commit) the changes
con.commit() ###
else:
for row in data[:100000]: # We have patience for 100k rows.
cur.execute(SQL, row)
# Save (commit) the changes
con.commit() ###
if time.clock()-start > 300:
break
end = time.clock()
cur.execute("SELECT IFNULL(MAX(id),1) FROM TTC;")
rows=cur.fetchone()[0]
wall_time = (end-start)
if JOURNAL=="":
JOURNAL="(default)"
if BATCH==True:
MODE="EXECUTEMANY"
else:
MODE="EXECUTE"
print "\t%.2f rows per second with PRAGMA=%s and JOURNAL=%s using %s in %s" %(int(rows/wall_time),PRAGMA, JOURNAL, MODE, DB)
SQL = "DROP TABLE IF EXISTS TTC;" # wipe the junk
cur.execute(SQL)
con.commit()
con.close()
if __name__ == '__main__':
data = get_the_data()
print "\n\tUsing SQLite %s and pysqlite version %s" %(sqlite3.sqlite_version, sqlite3.version)
SQL_create_table = "CREATE TABLE IF NOT EXISTS TTC (id INTEGER PRIMARY KEY, trip_id TEXT, arrival_time TEXT, departure_time TEXT, stop_id INTEGER, stop_sequence INTEGER, stop_headsign TEXT, pickup_type TEXT, drop_off_type TEXT, shape_dist_traveled REAL);"
SQL_data_operation = "INSERT INTO TTC VALUES(Null,?,?,?,?,?,?,?,?,?)" # the Null keyword permits SQLite to autoincrement.
DB = 'sqlite3_speedtest.db'
print "\tSingle Transactions using EXECUTE To DISK"
transaction(data, DB, SQL_create_table, SQL_data_operation, BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "DELETE", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "TRUNCATE", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "MEMORY", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "WAL", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "OFF", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "DELETE", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "TRUNCATE", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "MEMORY", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "WAL", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "OFF", BATCH=False)
print "\tBatched Transactions using EXECUTEMANY To DISK"
transaction(data, DB, SQL_create_table, SQL_data_operation)
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON")
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "DELETE")
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "TRUNCATE")
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "MEMORY")
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "WAL")
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "OFF")
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF")
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "DELETE")
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "TRUNCATE")
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "MEMORY")
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "WAL")
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "OFF")
DB = ':memory:'
print "\tSingle Transactions using EXECUTE To MEMORY"
transaction(data, DB, SQL_create_table, SQL_data_operation, BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "DELETE", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "TRUNCATE", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "MEMORY", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "WAL", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "OFF", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "DELETE", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "TRUNCATE", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "MEMORY", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "WAL", BATCH=False)
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "OFF", BATCH=False)
print "\tBatched Transactions using EXECUTEMANY To MEMORY"
transaction(data, DB, SQL_create_table, SQL_data_operation)
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON")
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "DELETE")
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "TRUNCATE")
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "MEMORY")
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "WAL")
transaction(data, DB, SQL_create_table, SQL_data_operation, "ON", "OFF")
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF")
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "DELETE")
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "TRUNCATE")
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "MEMORY")
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "WAL")
transaction(data, DB, SQL_create_table, SQL_data_operation, "OFF", "OFF")
print "\tTest Completed"
Output
Thanks to http://opendata.toronto.ca/TTC/routes/
OpenData_TTC_Schedules.zip/stop_times.txt
for the data.
Fetched 4624024 lines of data, or 20.108 Mb, in 8.170 seconds
(Reading csv from disk = 565958 rows per second).
Using SQLite 3.6.21 and pysqlite version 2.6.0
Single Transactions using EXECUTE To DISK
289.00 rows per second with PRAGMA=ON and JOURNAL=(default) using EXECUTE in sqlite3_speedtest.db
134.00 rows per second with PRAGMA=ON and JOURNAL=(default) using EXECUTE in sqlite3_speedtest.db
136.00 rows per second with PRAGMA=ON and JOURNAL=DELETE using EXECUTE in sqlite3_speedtest.db
157.00 rows per second with PRAGMA=ON and JOURNAL=TRUNCATE using EXECUTE in sqlite3_speedtest.db
527.00 rows per second with PRAGMA=ON and JOURNAL=MEMORY using EXECUTE in sqlite3_speedtest.db
137.00 rows per second with PRAGMA=ON and JOURNAL=WAL using EXECUTE in sqlite3_speedtest.db
522.00 rows per second with PRAGMA=ON and JOURNAL=OFF using EXECUTE in sqlite3_speedtest.db
564.00 rows per second with PRAGMA=OFF and JOURNAL=(default) using EXECUTE in sqlite3_speedtest.db
560.00 rows per second with PRAGMA=OFF and JOURNAL=DELETE using EXECUTE in sqlite3_speedtest.db
2145.00 rows per second with PRAGMA=OFF and JOURNAL=TRUNCATE using EXECUTE in sqlite3_speedtest.db
5579.00 rows per second with PRAGMA=OFF and JOURNAL=MEMORY using EXECUTE in sqlite3_speedtest.db
563.00 rows per second with PRAGMA=OFF and JOURNAL=WAL using EXECUTE in sqlite3_speedtest.db
11311.00 rows per second with PRAGMA=OFF and JOURNAL=OFF using EXECUTE in sqlite3_speedtest.db
Batched Transactions using EXECUTEMANY To DISK
192030.00 rows per second with PRAGMA=ON and JOURNAL=(default) using EXECUTEMANY in sqlite3_speedtest.db
164718.00 rows per second with PRAGMA=ON and JOURNAL=(default) using EXECUTEMANY in sqlite3_speedtest.db
177257.00 rows per second with PRAGMA=ON and JOURNAL=DELETE using EXECUTEMANY in sqlite3_speedtest.db
167466.00 rows per second with PRAGMA=ON and JOURNAL=TRUNCATE using EXECUTEMANY in sqlite3_speedtest.db
181955.00 rows per second with PRAGMA=ON and JOURNAL=MEMORY using EXECUTEMANY in sqlite3_speedtest.db
166388.00 rows per second with PRAGMA=ON and JOURNAL=WAL using EXECUTEMANY in sqlite3_speedtest.db
212319.00 rows per second with PRAGMA=ON and JOURNAL=OFF using EXECUTEMANY in sqlite3_speedtest.db
246463.00 rows per second with PRAGMA=OFF and JOURNAL=(default) using EXECUTEMANY in sqlite3_speedtest.db
245461.00 rows per second with PRAGMA=OFF and JOURNAL=DELETE using EXECUTEMANY in sqlite3_speedtest.db
243850.00 rows per second with PRAGMA=OFF and JOURNAL=TRUNCATE using EXECUTEMANY in sqlite3_speedtest.db
246533.00 rows per second with PRAGMA=OFF and JOURNAL=MEMORY using EXECUTEMANY in sqlite3_speedtest.db
245036.00 rows per second with PRAGMA=OFF and JOURNAL=WAL using EXECUTEMANY in sqlite3_speedtest.db
245244.00 rows per second with PRAGMA=OFF and JOURNAL=OFF using EXECUTEMANY in sqlite3_speedtest.db
Single Transactions using EXECUTE To MEMORY
85012.00 rows per second with PRAGMA=ON and JOURNAL=(default) using EXECUTE in :memory:
85393.00 rows per second with PRAGMA=ON and JOURNAL=(default) using EXECUTE in :memory:
85461.00 rows per second with PRAGMA=ON and JOURNAL=DELETE using EXECUTE in :memory:
85249.00 rows per second with PRAGMA=ON and JOURNAL=TRUNCATE using EXECUTE in :memory:
85315.00 rows per second with PRAGMA=ON and JOURNAL=MEMORY using EXECUTE in :memory:
84864.00 rows per second with PRAGMA=ON and JOURNAL=WAL using EXECUTE in :memory:
25705.00 rows per second with PRAGMA=ON and JOURNAL=OFF using EXECUTE in :memory:
83249.00 rows per second with PRAGMA=OFF and JOURNAL=(default) using EXECUTE in :memory:
85097.00 rows per second with PRAGMA=OFF and JOURNAL=DELETE using EXECUTE in :memory:
84700.00 rows per second with PRAGMA=OFF and JOURNAL=TRUNCATE using EXECUTE in :memory:
85441.00 rows per second with PRAGMA=OFF and JOURNAL=MEMORY using EXECUTE in :memory:
84724.00 rows per second with PRAGMA=OFF and JOURNAL=WAL using EXECUTE in :memory:
25521.00 rows per second with PRAGMA=OFF and JOURNAL=OFF using EXECUTE in :memory:
Batched Transactions using EXECUTEMANY To MEMORY
284543.00 rows per second with PRAGMA=ON and JOURNAL=(default) using EXECUTEMANY in :memory:
285171.00 rows per second with PRAGMA=ON and JOURNAL=(default) using EXECUTEMANY in :memory:
285505.00 rows per second with PRAGMA=ON and JOURNAL=DELETE using EXECUTEMANY in :memory:
285609.00 rows per second with PRAGMA=ON and JOURNAL=TRUNCATE using EXECUTEMANY in :memory:
285056.00 rows per second with PRAGMA=ON and JOURNAL=MEMORY using EXECUTEMANY in :memory:
285799.00 rows per second with PRAGMA=ON and JOURNAL=WAL using EXECUTEMANY in :memory:
286359.00 rows per second with PRAGMA=ON and JOURNAL=OFF using EXECUTEMANY in :memory:
286316.00 rows per second with PRAGMA=OFF and JOURNAL=(default) using EXECUTEMANY in :memory:
285607.00 rows per second with PRAGMA=OFF and JOURNAL=DELETE using EXECUTEMANY in :memory:
285506.00 rows per second with PRAGMA=OFF and JOURNAL=TRUNCATE using EXECUTEMANY in :memory:
285671.00 rows per second with PRAGMA=OFF and JOURNAL=MEMORY using EXECUTEMANY in :memory:
285624.00 rows per second with PRAGMA=OFF and JOURNAL=WAL using EXECUTEMANY in :memory:
285707.00 rows per second with PRAGMA=OFF and JOURNAL=OFF using EXECUTEMANY in :memory:
Test Completed