โ† back to journal

SQLite's JSONB reads 3 to 15 times faster, until you ask for the text back

2026-10-14 ยท Kit Wickham

Since 3.45, SQLite can store JSON in a binary form, JSONB, and every json_* function accepts it. I stored the same documents both ways in SQLite 3.46.1 and timed json_extract on a single key. JSONB was 3x faster on small documents and up to 15x faster on big ones. Its speed also depended on where the key sits in the document, which the text form's speed did not. Getting a whole document back out as text costs as much as parsing it in the first place, so JSONB only pays when you query inside documents more than you return them whole.

The test

Flat objects with 10, 100 or 1,000 keys, two thirds integers and one third short strings, about 2 million keys per table in total. Everything ran in an in-memory database, so the numbers measure CPU only, not I/O. Each query summed one key over every row, best of 5. I ran it 3 times; the ratio ranges below cover all three runs. Times are nanoseconds per row, from one run.

keys per dockey readtextJSONBratio (3 runs)
10first5931072.2โ€“5.5x
10last5341663.2โ€“3.6x
100first3,4384935.2โ€“7.0x
100middle3,3839623.5โ€“4.7x
100last3,9111,0873.2โ€“3.6x
1,000first29,3442,06210โ€“14x
1,000middle28,7265,2025.5โ€“6.5x
1,000last34,4246,4944.5โ€“5.5x

On the text column the key's position barely matters, because SQLite parses the whole document before it looks anything up. JSONB skips that parse, but finding a key is still a walk past the keys that come before it, since there's no index inside an object. In the 1,000-key documents the last key costs about 3x what the first key does. If one or two fields are what you filter on, put them first when you build the document.

Updates gain more than reads. jsonb_set on one key was 5x faster than json_set at 10 keys and 13โ€“16x faster at 1,000 keys, because the text version parses the document and then writes out the whole thing as text again.

The catch

Your application almost certainly wants text. Turning a JSONB value back into text with json() took 298 ns per row at 10 keys and 21 ยตs at 1,000 keys. That's about what jsonb() takes to parse the text in the first place (506 ns and 26 ยตs). If most of your queries look like SELECT doc FROM t WHERE id = ?, a JSONB column makes every read pay that conversion, and the parse it saves happens nowhere, because a plain text column is returned as it is stored.

JSONB also took up 23% less space here (141 vs 183 bytes at 10 keys, 15.2 vs 19.5 KB at 1,000). The saving comes from numbers and punctuation; long strings would shrink it.

So what

If you filter, aggregate or patch inside documents in SQL, store JSONB: it's several times faster and a bit smaller. If you mostly store documents and hand them back whole, keep text. Converting is cheap to try: UPDATE t SET doc = jsonb(doc), and the same queries keep working.

Method: one machine, a 40-line Python script, SQLite 3.46.1 in a container, 2 CPUs per run. Documents are synthetic and flat; nested paths would add a walk per level.