Post

Speeding up SQLite3 Query Times on a GoDaddy Shared Hosting Server

Nearly identical SQLite3 queries on a GoDaddy shared hosting server had significantly different run times. I solved the problem with the SQLite 3 'pragma temp_store=2;' command.


I have been creating a website, Crawler Activity, that presents the data from monitoring the activity of web crawlers that are indexing and scraping my websites at conradhalling.com and sphaerula.com. The website is hosted on a GoDaddy shared hosting server on which I installed SQLite 3.50.0 and Python 3.13.3. (For more information, see my post Installing SQLite 3.50.0 and Python 3.13.3 on a GoDaddy Shared Hosting Server.)

I experienced a performance issue that I describe here in detail. I had tested two database queries that were identical except for the field that they selected from the same table. The first query selected tbl_domain.id whereas the second query selected tbl_domain.name. I expected these queries to take the same amount of time, but the run times were significantly different. I experienced a similar run time difference with many other queries.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
$ sqlite3 data/crawlers.sqlite3
SQLite version 3.50.0 2025-05-29 14:26:00
Enter ".help" for usage hints.
sqlite> .mode columns
sqlite> .timer on
sqlite> SELECT
    tbl_domain.id,
    COUNT(tbl_domain.id)
FROM
    tbl_request
    INNER JOIN tbl_ip_address
        ON tbl_request.ip_address_id = tbl_ip_address.id
    INNER JOIN tbl_domain
        ON tbl_request.domain_id = tbl_domain.id
WHERE
    tbl_ip_address.is_site_owner = 0
    AND tbl_request.iso_date <> (
        SELECT MAX(tbl_request.iso_date) FROM tbl_request
    )
GROUP BY
    tbl_domain.id
;
id  COUNT(tbl_domain.id)
--  --------------------
1   72542
2   48347
Run Time: real 0.226 user 0.152617 sys 0.063156


sqlite> SELECT
    tbl_domain.name,
    COUNT(tbl_domain.name)
FROM
    tbl_request
    INNER JOIN tbl_ip_address
        ON tbl_request.ip_address_id = tbl_ip_address.id
    INNER JOIN tbl_domain
        ON tbl_request.domain_id = tbl_domain.id
WHERE
    tbl_ip_address.is_site_owner = 0
    AND tbl_request.iso_date <> (
        SELECT MAX(tbl_request.iso_date) FROM tbl_request
    )
GROUP BY
    tbl_domain.name
;
name               COUNT(tbl_domain.name)
-----------------  ----------------------
conradhalling.com  72542
sphaerula.com      48347
Run Time: real 1.255 user 0.174469 sys 0.068221

This table extracts the significant results for easy comparison.

Select Run Time (real s)
tbl_domain.id 0.226
tbl_domain.name 1.255

The tbl_domain table was a small table with three columns and two rows. Why would selecting the name instead of the id field from the table cause the query to run more than five times slower?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sqlite> .schema tbl_domain
CREATE TABLE tbl_domain
        (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL UNIQUE,
            access_log_file_glob_pattern TEXT NOT NULL
        ) STRICT
    ;
sqlite> .mode columns
sqlite> select * from tbl_domain;
id  name               access_log_file_glob_pattern
--  -----------------  ----------------------------
1   conradhalling.com  access.log.*.gz
2   sphaerula.com      sphaerula.com-*.gz
Run Time: real 0.000 user 0.000000 sys 0.000239

After I studied the SQLite3 documentation for several hours, I found the description of PRAGMA temp_store. The default setting for this pragma turned out to be 0, causing the storage used for TEMP tables and indices to be file storage. Setting the value of PRAGMA temp_store to 2 would change the storage to memory, which should be much faster.

In fact, when I set PRAGMA temp_store=2, as shown below, the run times for both queries where essentially equally fast.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
sqlite> PRAGMA temp_store;
0
sqlite> PRAGMA temp_store=2;
sqlite> pragma temp_store;
2


sqlite> SELECT
    tbl_domain.id,
    COUNT(tbl_domain.id)
FROM
    tbl_request
    INNER JOIN tbl_ip_address
        ON tbl_request.ip_address_id = tbl_ip_address.id
    INNER JOIN tbl_domain
        ON tbl_request.domain_id = tbl_domain.id
WHERE
    tbl_ip_address.is_site_owner = 0
    AND tbl_request.iso_date <> (
        SELECT MAX(tbl_request.iso_date) FROM tbl_request
    )
GROUP BY
    tbl_domain.id
;
id  COUNT(tbl_domain.id)
--  --------------------
1   72542
2   48347
Run Time: real 0.240 user 0.167511 sys 0.063543


sqlite> SELECT
    tbl_domain.name,
    COUNT(tbl_domain.name)
FROM
    tbl_request
    INNER JOIN tbl_ip_address
        ON tbl_request.ip_address_id = tbl_ip_address.id
    INNER JOIN tbl_domain
        ON tbl_request.domain_id = tbl_domain.id
WHERE
    tbl_ip_address.is_site_owner = 0
    AND tbl_request.iso_date <> (
        SELECT MAX(tbl_request.iso_date) FROM tbl_request
    )
GROUP BY
    tbl_domain.name
;
name               COUNT(tbl_domain.name)
-----------------  ----------------------
conradhalling.com  72542
sphaerula.com      48347
Run Time: real 0.236 user 0.176746 sys 0.058760

This table extracts the significant values for easy comparison.

Select Pragma temp_store Run Time (real s)
tbl_domain.id 0 0.226
tbl_domain.id 2 0.240
tbl_domain.name 0 1.255
tbl_domain.name 2 0.236

I speculate that the query selecting tbl_domain.name required temporary storage and that disk storage was much slower than memory storage on the shared hosting server.

I modified my Python code for connecting to a SQLite3 database to set PRAGMA temp_store=2, as shown below. This code snippet includes code that enforces foreign key constraints.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import sqlite3

# conn is a singleton containing the connection to the SQLite database file.
conn = None


def connect(db_file):
    """
    Connect to the database file.

    Enforce foreign key constraints.

    Set temp_store to 2 (memory); this greatly reduces query time.
    """
    global conn
    conn = sqlite3.connect(database=db_file, isolation_level=None)
    set_temp_store_in_memory()
    enforce_foreign_key_constraints()


def set_temp_store_in_memory():
    """
    Set PRAGMA temp_store = 2 and check its value.

    On sphaerula.com, setting this pragma greatly speeds up query
    performance.
    """
    global conn
    # The API doesn't allow binding a value to a pragma using '?'.
    in_memory = 2
    # Set the pragma.
    cur = conn.execute(f"PRAGMA temp_store={in_memory}")
    # Get the pragma value and check it.
    cur.execute("PRAGMA temp_store")
    row = cur.fetchone()
    cur.close()
    pragma_temp_store_value = None
    if row is not None:
        pragma_temp_store_value = row[0]
    if pragma_temp_store_value != in_memory:
        raise sqlite3.IntegrityError(f"PRAGMA temp_store is not {in_memory}")


def enforce_foreign_key_constraints():
    """
    Set PRAGMA foreign_keys = ON and check its value.
    """
    global conn
    cur = conn.cursor()
    # Set the pragma.
    if conn.in_transaction == True:
        # This is needed when conn.autocommit is False because a transaction
        # is already open, and PRAGMA foreign_keys = ON has no effect in
        # an open transaction.
        cur.executescript("COMMIT; PRAGMA foreign_keys = ON; BEGIN;")
    else:
        cur.execute("PRAGMA foreign_keys = ON")
    cur.close()
    verify_foreign_key_constraints()


def verify_foreign_key_constraints():
    """
    Verify that the PRAGMA foreign_keys value is 1.
    Raise a sqlite3.IntegrityError exception if the value is not 1.
    """
    # Get the pragma. It must be 1.
    global conn
    cur = conn.cursor()
    cur.execute("PRAGMA foreign_keys")
    rows = cur.fetchall()
    cur.close()
    pragma_foreign_keys_value = None
    if len(rows) != 0:
        pragma_foreign_keys_value = rows[0][0]
    logger.debug(f"pragma_foreign_keys_value: {pragma_foreign_keys_value}")
    if pragma_foreign_keys_value != 1:
        raise sqlite3.IntegrityError("PRAGMA foreign_keys is not ON")

A note on performance of modern laptop computers. I am developing this code on my M4 Pro MacBook Pro, which has 1 TB of SSD storage and 48 GB of RAM, using SQLite 3.50.0 and Python 3.13.3 installed using homebrew.

With pragma temp_store=0, the run times for the same queries were:

Select Run Time (real s)
tbl_domain.id 0.051
tbl_domain.name 0.061

My MacBook Pro laptop executes these queries about four times faster than the shared hosting server.

This post is licensed under CC BY 4.0 by the author.