Lab / Open methodology

Database optimization: queries and indexes

200,000 orders summarized: 1.13 s → 7.19 ms. Synthetic lab; median of 10 runs. Open code and raw data.

CompletedSMBEnterprisePartners

Unindexed querytoSame query + composite index

SQLite 3.46 LAB MEASUREMENT

Find the rows you need without scanning the whole table.

Open order totals by customer. The same query before and after indexing, on the same SQLite engine.

Business effect

Reports and list screens do less CPU work. Fixing the query plan can reduce the need to scale hardware.

BEFOREUnindexed query
MODERNIZEDSame query + composite index
Order records200,000
1,000Recorded loads only200,000
Actual time scale
SAME TASK · SAME DATA10 runs · median
01

Unindexed query

Scan the orders table for each customer total.

1.129 s
CPU time
1.129 s
02

Same query + composite index

Use a covering index on customer, status and amount.

7.188 ms
CPU time
7.187 ms
03

Index + grouped-query control

An alternative grouped query was also measured for the same output.

10.158 ms
CPU time
10.16 ms

Bars start at zero and share the same time scale across implementations.

Completion time reduced99.4%Time difference: 1.122 sOriginal → modernized implementation
What changed?

Add a composite covering index for the customer and status filters. Keep the grouped-query alternative as a separate control.

All load points and measurements
1,000 · Order records
ImplementationMedian durationCPU time
Unindexed query5.995 ms5.996 ms
Same query + composite index0.146 ms0.147 ms
Index + grouped-query control0.213 ms0.215 ms
3,000 · Order records
ImplementationMedian durationCPU time
Unindexed query16.785 ms16.786 ms
Same query + composite index0.227 ms0.228 ms
Index + grouped-query control0.292 ms0.293 ms
10,000 · Order records
ImplementationMedian durationCPU time
Unindexed query55.749 ms55.743 ms
Same query + composite index0.536 ms0.538 ms
Index + grouped-query control0.694 ms0.695 ms
30,000 · Order records
ImplementationMedian durationCPU time
Unindexed query175.175 ms175.158 ms
Same query + composite index1.23 ms1.231 ms
Index + grouped-query control1.695 ms1.696 ms
60,000 · Order records
ImplementationMedian durationCPU time
Unindexed query345.649 ms345.617 ms
Same query + composite index2.292 ms2.298 ms
Index + grouped-query control3.264 ms3.265 ms
100,000 · Order records
ImplementationMedian durationCPU time
Unindexed query565.92 ms565.9 ms
Same query + composite index3.691 ms3.693 ms
Index + grouped-query control5.213 ms5.214 ms
200,000 · Order records
ImplementationMedian durationCPU time
Unindexed query1.129 s1.129 s
Same query + composite index7.188 ms7.187 ms
Index + grouped-query control10.158 ms10.16 ms
CPU cost at your workload

Compare the recorded CPU time at your own monthly volume.

One batch run = 200,000 Order records.

CPU time reduced / month3.1162 hours

Processing cost difference / month—Calculated after you enter a unit price.

Lab CPU time × your volume and unit price. This is not a hosting bill forecast; idle capacity, networking, storage, licences and migration are excluded.

Try it yourself

Replay the recorded lab results side by side.

As the workload grows

Duration by workload

  • Before
  • After

Duration by workload (ms)

02004006008001,0001,2001,00060,000100,000200,0001,129.16 ms7.19 ms

Select a data point

02004006008001,0001,2001,00060,000200,0007.19 ms

Select a data point

Order recordsMarkers: measured points
Show as a table
Duration by workload
Order recordsBeforeAfter
1,0005.99 ms0.15 ms
3,00016.78 ms0.23 ms
10,00055.75 ms0.54 ms
30,000175.17 ms1.23 ms
60,000345.65 ms2.29 ms
100,000565.92 ms3.69 ms
200,0001,129.16 ms7.19 ms

How we measured

Environment, protocol and technical analysis

Environment and data

Environment
Apple M3; Docker Linux arm64, 2-CPU quota, 1 GiB memory. PHP 8.4.26 / SQLite 3.46.1.
Dataset
200 customers; 1,000–200,000 deterministic orders. All result rows and cent totals match across implementations.
Tools
PHP 8.4.26 · SQLite 3.46.1 · hrtime / getrusage · OPcache / JIT off · native arm64 Docker
Repetitions
7 loads × 5 implementations × 10 repeats, plus 70 warmup observations. Every sample retained; rotating arm order.
Open raw measurement data ↗

Find the rows you need without scanning the whole table.

Open order totals by customer. The same query before and after indexing, on the same SQLite engine.

Identical PHP/SQLite versions, synthetic records and 200 customers. Two warmups then 10 runs per arm/size; every complete output matches an independent oracle. PHP lookup totals rebuilt each run. SQL timings are warm in-memory reads excluding loading/index creation. Index bytes and the single construction observation are separate. Primary 200,000-record point selected before measurement.

Inspect the measurement script ↗

What changes for the business?

Reports and list screens do less CPU work. Fixing the query plan can reduce the need to scale hardware.

Change and scope

Add a composite covering index for the customer and status filters. Keep the grouped-query alternative as a separate control.

SQLite 3.46.1, warm in-memory data, 200 customers. Query timings exclude index construction and writes; the evidence includes index size and construction time. These are not results for other engines.

Index cost and the alternative query

At 200,000 records the index added 3,899,392 bytes; the single construction observation was 77.56 ms. Write performance is outside this read experiment. The indexed grouped-query alternative is retained in the table. Query plans show the change from scans to a covering-index search.

Where to start in your system

Identify the slow operation, data volume and peak workload. Establish its baseline, change the code and access pattern, then compare the same result.

What we did

  1. Business rule and data

    AIPrepared the synthetic workflow, tests and measurement automation.

    VerificationPublished code and raw outputs are available for review; this is not a client project.

  2. Implementation change

    AIAdd a composite covering index for the customer and status filters. Keep the grouped-query alternative as a separate control.

    VerificationOutput equivalence was independently checked; every repetition is retained.