SQLite: database disk image is malformed (tested)

sqlite3.DatabaseError: database disk image is malformed means SQLite's own bookkeeping inside the file no longer matches its actual contents — most often from a crash or interrupted copy mid-write. We corrupted a real file ourselves and measured precisely what triggers it.

1. What did NOT trigger it: scattered byte corruption

We first overwrote 40 bytes in the middle of a populated database file with 0xFF and ran PRAGMA integrity_check:

[('ok',)]

No error. SQLite doesn't checksum page contents by default, so overwriting bytes that happen to land in unused space inside a page goes completely unnoticed. Scattered corruption is not a reliable way to reproduce this error.

2. What DID trigger it: truncating off whole pages the header still expects

Every SQLite file's first 100 bytes are a header containing the page size and the total page count. We read them directly with struct:

page_size = struct.unpack(">H", hdr[16:18])[0]        # 4096
page_count_hdr = struct.unpack(">I", hdr[28:32])[0]    # 4

We truncated the file from 16384 bytes (4 full pages) down to 14884 bytes — removing 1500 bytes, less than one full page. Result: no error, because the missing bytes happened to fall inside a page SQLite never needed to read. We then truncated further to exactly 12288 bytes (3 full pages):

actual file size: 12288   # 3 pages of 4096 bytes
header page_count: 4      # header still says 4
con.execute("SELECT * FROM t").fetchall()
sqlite3.DatabaseError: database disk image is malformed

The exact trigger, measured: the header's own page-count field (4) no longer matches the file's actual size in pages (3). The error fires the moment SQLite tries to read the page the header promised but the file doesn't have — not from the missing bytes themselves. PRAGMA integrity_check fails identically, and con.iterdump() fails immediately too (0 rows recovered) since it has to read the schema first.

3. The fix: there isn't one on the file itself — restore from a backup

This error means the file's internal structure is inconsistent, not that a value is wrong. We confirmed there's no in-place repair: the same query and PRAGMA integrity_check fail every time against the truncated file, and iterdump() can't get past the first read either. The only real fix is restoring the database from a known-good backup or copy made before the truncation/crash happened. To prevent it: avoid copying a live SQLite file with a plain file copy while a connection might be writing to it (use the online backup API instead, per Python's sqlite3 docs — not measured in this pass), and make sure processes that write to the file are given a chance to close cleanly rather than being killed mid-write.

Frequently asked questions

What exactly causes 'database disk image is malformed'?

We measured it directly: the file's own header declares a page count that no longer matches the file's actual size. A query fails the instant SQLite tries to read a page the header promised but the file doesn't actually contain.

Does any byte corruption trigger this error?

No — we tested overwriting 40 bytes in the middle of a file with 0xFF first, and PRAGMA integrity_check still reported 'ok'. SQLite doesn't checksum page contents by default, so corruption landing in unused space goes undetected. The reliable trigger we measured was a page-count mismatch from truncation.

Can I repair the file in place?

We found no in-place fix — the same query and PRAGMA integrity_check fail consistently against the corrupted file, and con.iterdump() fails immediately too since it must read the schema first. Restoring from a backup made before the corruption is the only fix we could confirm.

What usually causes this in real apps, not a deliberate test?

A crash or kill -9 mid-write, or copying the .db file with a plain file-copy tool while another process still has it open for writing — both can leave the file's header out of sync with its actual contents, the same mismatch we reproduced here deliberately.

Does PRAGMA integrity_check catch problems that a normal query wouldn't?

Not in our test — both failed with the identical 'database disk image is malformed' error on the same truncated file. integrity_check is still useful as a deliberate, explicit check you can run on a schedule rather than discovering corruption only when a real query happens to hit the bad page.

Written with AI assistance. Measured on 2026-10-03 with Python 3.13.5's built-in sqlite3 module and struct, by truncating a real .db file to deliberately cut off whole pages the file's own header still claims exist. Every error string and byte offset below is copy-pasted or directly computed output, not paraphrased. PlainHeaders is our extension; Python's sqlite3 and struct are built in.