DEV Community

Cover image for The Mathematics Behind Database Performance
Derek Mwale
Derek Mwale

Posted on

The Mathematics Behind Database Performance

Databases Don't Become Slow by Accident. There Is Usually an Equation Hiding Somewhere.

There is a moment every backend developer eventually experiences.

The application works.

The API works.

The frontend looks beautiful.

The database has a few thousand rows.

Everything feels ridiculously fast.

Then the application grows.

A few thousand rows become a few million.

A few hundred users become tens of thousands.

The innocent query that used to take:

2 ms
Enter fullscreen mode Exit fullscreen mode

now takes:

2 seconds
Enter fullscreen mode Exit fullscreen mode

Then:

8 seconds
Enter fullscreen mode Exit fullscreen mode

Then someone opens the dashboard and suddenly the entire backend begins behaving like it is reconsidering its life choices.

Developers start doing what developers traditionally do.

They add an index.

Sometimes it works.

Sometimes it doesn't.

Then they add another index.

Then another.

Then someone says:

"Let's increase the database server."

So the CPU goes from 4 cores to 16.

RAM goes from 16 GB to 64 GB.

The database becomes faster.

For a while.

Then the dataset grows again.

And eventually you discover something uncomfortable:

Database performance is not magic.

It is mathematics.

Underneath every database query there are relationships involving:

  • latency
  • throughput
  • probability
  • logarithms
  • memory
  • storage bandwidth
  • queueing
  • cache hit rates
  • selectivity
  • cardinality
  • network round trips
  • concurrency
  • asymptotic complexity

A database is a mathematical machine pretending to be a table.

And understanding the mathematics behind it changes how you design systems.

Because suddenly an index is not just "something that makes queries faster."

It becomes a data structure with a mathematical cost.

A join is not just "combining tables."

It becomes an algorithm whose complexity depends on the sizes and distributions of its inputs.

A cache is not just "fast memory."

It becomes a probability distribution.

A database server is not just a computer.

It becomes a queue.

And a query is not just SQL.

It is a computational problem.

Let's go down the rabbit hole.


1. The First Equation: Time

Let's start with the simplest possible model.

Suppose a query takes:

T = 50 ms
Enter fullscreen mode Exit fullscreen mode

If one request runs at a time, you can theoretically execute:

1000 ms / 50 ms = 20
Enter fullscreen mode Exit fullscreen mode

queries per second.

So:

$$
Throughput \approx \frac{1}{Latency}
$$

If a query takes 100 ms:

$$
\frac{1}{0.1} = 10
$$

queries per second per sequential execution path.

If it takes 1 ms:

$$
\frac{1}{0.001} = 1000
$$

The first important lesson is obvious:

Lower latency generally means higher potential throughput.

But real databases are not single-threaded calculators.

They have concurrency.

They have queues.

They have locks.

They have disks.

They have network connections.

They have CPU contention.

They have cache misses.

So a more useful approximation is:

$$
Throughput \approx \frac{Concurrency}{Latency}
$$

Suppose you have 100 concurrent workers and average request latency is 50 ms:

$$
\frac{100}{0.05} = 2000
$$

requests per second.

But if latency increases to 500 ms:

$$
\frac{100}{0.5} = 200
$$

requests per second.

Same concurrency.

Same application.

Same hardware.

Ten times slower latency.

Potential throughput drops roughly tenfold.

This is why latency is not merely a user-experience metric.

It is an architectural constraint.


2. Little's Law Is Hiding Inside Your Backend

One of the most useful equations in performance engineering comes from queueing theory.

It is known as Little's Law:

$$
L = \lambda W
$$

Where:

  • (L) = average number of items in the system
  • (\lambda) = arrival rate
  • (W) = average time spent in the system

For databases, you can interpret this as:

$$
Concurrency = Throughput \times Latency
$$

Imagine your API receives:

1000 requests/sec
Enter fullscreen mode Exit fullscreen mode

and each request spends:

50 ms
Enter fullscreen mode Exit fullscreen mode

waiting on the database.

Then:

$$
L = 1000 \times 0.05
$$

$$
L = 50
$$

Approximately 50 requests are simultaneously in the database portion of the system.

Now suppose a slow query increases latency to:

500 ms
Enter fullscreen mode Exit fullscreen mode

At the same arrival rate:

$$
L = 1000 \times 0.5
$$

$$
L = 500
$$

Now your system needs to sustain roughly 500 concurrent database operations.

That means connection pools grow.

Queues grow.

Memory usage grows.

Lock contention grows.

Context switching grows.

The original query being "only 10x slower" can create much more than a 10x operational problem.

This is one reason latency spikes can become cascading failures.


3. Why Indexes Work: Logarithms

Now we reach the most famous mathematical idea behind database indexing.

Suppose your table contains:

1,000,000 rows
Enter fullscreen mode Exit fullscreen mode

You need to find one row.

A naive scan might inspect:

$$
O(n)
$$

rows.

In the worst case:

1,000,000 comparisons
Enter fullscreen mode Exit fullscreen mode

That is linear search.

But a balanced tree index changes the problem.

Instead of asking:

"Which row is this?"

you repeatedly divide the search space.

This gives approximately:

$$
O(\log n)
$$

complexity.

For one million entries:

$$
\log_2(1,000,000) \approx 20
$$

So instead of potentially examining a million entries, a tree may need around twenty levels of decisions.

That is an absurd difference.

Consider:

Linear:
1,000,000

Binary/tree-like:
~20
Enter fullscreen mode Exit fullscreen mode

This is why indexes feel magical.

But they are not magic.

They are exploiting logarithmic mathematics.


4. B-Trees and Why Databases Love Them

Most traditional relational databases rely heavily on B-tree-family indexes.

Why?

Because databases do not live entirely in RAM.

Storage access matters.

Suppose each index node can contain hundreds of keys.

Then the branching factor can be enormous.

Imagine a simplified tree where each node has:

100 children
Enter fullscreen mode Exit fullscreen mode

Then:

$$
100^1 = 100
$$

$$
100^2 = 10,000
$$

$$
100^3 = 1,000,000
$$

$$
100^4 = 100,000,000
$$

Four levels can theoretically cover an enormous number of records.

This is one of the beautiful things about high branching factors.

The database does not need a 20-level tree.

It might need only a handful of page accesses.

And that matters because database performance is often dominated not by CPU instructions but by memory and storage behavior.


5. The Real Enemy: I/O

Modern CPUs are ridiculously fast.

Storage is much slower.

Memory sits somewhere in between.

This creates a hierarchy:

CPU registers
      ↓
CPU cache
      ↓
RAM
      ↓
SSD
      ↓
Network storage
Enter fullscreen mode Exit fullscreen mode

Each layer has different latency characteristics.

This means:

Database performance is frequently a problem of moving bytes.

Not calculating numbers.

Moving bytes.

Suppose a query can be answered from memory.

Excellent.

Suppose the database must read thousands of disk pages.

Now things become interesting.

Imagine:

1 page = 8 KB
Enter fullscreen mode Exit fullscreen mode

and the query needs:

10,000 pages
Enter fullscreen mode Exit fullscreen mode

Then:

$$
10,000 \times 8KB = 80MB
$$

The database potentially needs to move 80 MB of data.

Even if the CPU can process it quickly, the storage subsystem and memory hierarchy still have to move it.

This is why reducing the amount of data touched often matters more than optimizing the number of CPU instructions.


6. Selectivity: The Mathematics of "How Much?"

Suppose you have:

10,000,000 users
Enter fullscreen mode Exit fullscreen mode

and run:

SELECT *
FROM users
WHERE country = 'Zambia';
Enter fullscreen mode Exit fullscreen mode

If 1% of users are in Zambia:

$$
Selectivity = 0.01
$$

Expected rows:

$$
10,000,000 \times 0.01 = 100,000
$$

That's still a lot.

Now imagine:

WHERE user_id = 123456
Enter fullscreen mode Exit fullscreen mode

If user_id is unique:

$$
Selectivity \approx \frac{1}{10,000,000}
$$

Expected result:

1 row
Enter fullscreen mode Exit fullscreen mode

This distinction matters because an index is useful when it significantly reduces the amount of data that must be examined.

The database optimizer is constantly asking a question that sounds almost philosophical:

"How much of the table am I going to have to touch?"

If the answer is:

0.00001%
Enter fullscreen mode Exit fullscreen mode

an index is extremely attractive.

If the answer is:

70%
Enter fullscreen mode Exit fullscreen mode

the optimizer might decide scanning the table is actually cheaper.

And this is why blindly adding indexes is not database optimization.

The optimizer is solving a cost problem.


7. Why Sometimes a Full Table Scan Wins

This confuses many developers.

You create an index.

You expect the database to use it.

But the query planner chooses:

Seq Scan
Enter fullscreen mode Exit fullscreen mode

You think:

"The database is stupid."

Usually, it isn't.

Suppose a table has:

1,000,000 rows
Enter fullscreen mode Exit fullscreen mode

and your query wants:

700,000 rows
Enter fullscreen mode Exit fullscreen mode

Using an index might involve:

  1. Walking the index.
  2. Finding hundreds of thousands of references.
  3. Fetching rows from the table.
  4. Performing many random accesses.

A sequential scan may simply read the table efficiently from beginning to end.

If storage can stream data efficiently, the sequential approach can win.

Mathematically, you can think of the optimizer as comparing:

$$
Cost_{index}
$$

against:

$$
Cost_{scan}
$$

and selecting approximately:

$$
min(Cost_{index}, Cost_{scan})
$$

That is database optimization in miniature.


8. Composite Indexes Are Geometry

Now consider:

SELECT *
FROM orders
WHERE customer_id = 42
AND created_at > '2026-01-01';
Enter fullscreen mode Exit fullscreen mode

You might create:

CREATE INDEX idx_orders_customer_date
ON orders(customer_id, created_at);
Enter fullscreen mode Exit fullscreen mode

Why this order?

Because a composite index creates an ordering over multiple dimensions.

Conceptually:

(customer_id, created_at)
Enter fullscreen mode Exit fullscreen mode

means the index is primarily ordered by:

customer_id
Enter fullscreen mode Exit fullscreen mode

and then within each customer:

created_at
Enter fullscreen mode Exit fullscreen mode

Think of it as sorting a spreadsheet first by column A and then by column B.

This means:

(customer_id, created_at)
Enter fullscreen mode Exit fullscreen mode

is not equivalent to:

(created_at, customer_id)
Enter fullscreen mode Exit fullscreen mode

The mathematics of ordering matters.

If the query primarily narrows by customer_id, putting it first can allow the database to jump into a much smaller region of the index.

This is why index design is not:

"Index all the columns."

It is:

"Design an ordering that makes common queries cheap."


9. The Leftmost Prefix Idea

Suppose we have:

INDEX(a, b, c)
Enter fullscreen mode Exit fullscreen mode

The index naturally supports access patterns beginning with:

a
Enter fullscreen mode Exit fullscreen mode

or:

a, b
Enter fullscreen mode Exit fullscreen mode

or:

a, b, c
Enter fullscreen mode Exit fullscreen mode

But a query using only:

b
Enter fullscreen mode Exit fullscreen mode

does not necessarily get the same benefit.

Why?

Because the index is ordered like:

a → b → c
Enter fullscreen mode Exit fullscreen mode

not:

b → a → c
Enter fullscreen mode Exit fullscreen mode

This is another example of mathematics hiding behind SQL.

An index is an ordering.

An ordering determines which search problems can be solved efficiently.

Database engineers are basically designing multidimensional search structures while pretending they are just writing SQL.


10. Join Algorithms Are Algorithms

Consider two tables:

users
orders
Enter fullscreen mode Exit fullscreen mode

Suppose:

users = 1,000,000 rows
orders = 10,000,000 rows
Enter fullscreen mode Exit fullscreen mode

A join is not just a SQL concept.

It is an algorithmic problem.

The database has several strategies.

One is nested loop join.

Conceptually:

for each user:
    find matching orders
Enter fullscreen mode Exit fullscreen mode

In naive form:

$$
O(nm)
$$

If:

$$
n = 1,000,000
$$

and:

$$
m = 10,000,000
$$

then:

$$
n \times m = 10^{13}
$$

That is:

10,000,000,000,000
Enter fullscreen mode Exit fullscreen mode

potential comparisons.

Absolutely terrible.

But an index changes the equation.

For each user, the database can search orders through an index in roughly:

$$
O(\log m)
$$

So the conceptual cost becomes:

$$
O(n\log m)
$$

which is dramatically smaller.

This is why indexes on join keys can be transformative.


11. Hash Joins: Trading Memory for Speed

Another approach is a hash join.

Suppose you build a hash table for one relation.

Conceptually:

Build hash table
      ↓
Scan second table
      ↓
Lookup matching keys
Enter fullscreen mode Exit fullscreen mode

Hash lookup is approximately:

$$
O(1)
$$

on average.

So instead of repeatedly searching the entire relation, you perform constant-time-ish lookups.

The overall cost can approach:

$$
O(n+m)
$$

assuming reasonable hashing behavior and enough memory.

This is beautiful because it demonstrates one of the central ideas in computer science:

Spend more memory to save time.

A hash table stores extra information so that future searches become cheap.

Indexes do something similar.

Caches do something similar.

Materialized views do something similar.

Denormalization does something similar.

A huge percentage of performance engineering is really:

"Can I spend space now to avoid work later?"


12. Sorting Is More Expensive Than People Think

Suppose a query asks:

ORDER BY created_at
Enter fullscreen mode Exit fullscreen mode

Sorting (n) records generally costs approximately:

$$
O(n\log n)
$$

For:

1,000,000 rows
Enter fullscreen mode Exit fullscreen mode

you are dealing with roughly:

$$
1,000,000 \times \log_2(1,000,000)
$$

which is approximately:

$$
20,000,000
$$

units of comparison-scale work.

But if you already have an index ordered by created_at, the database may be able to retrieve records in sorted order without performing a full sort.

This is another reason indexes are more than lookup accelerators.

They can encode ordering.

And ordering can eliminate computation.


13. Pagination Is a Complexity Problem

Everyone starts with:

SELECT *
FROM posts
ORDER BY id
LIMIT 20
OFFSET 1000000;
Enter fullscreen mode Exit fullscreen mode

It looks harmless.

It is not always harmless.

As the offset increases, the database may need to walk past a large number of records before returning the requested page.

Conceptually:

$$
Cost \approx O(offset + limit)
$$

So:

OFFSET 20
Enter fullscreen mode Exit fullscreen mode

is cheap.

But:

OFFSET 1,000,000
Enter fullscreen mode Exit fullscreen mode

can be expensive.

This is why cursor-based pagination is so powerful.

Instead of saying:

"Skip one million records."

you say:

"Start after this known key."

For example:

SELECT *
FROM posts
WHERE id > 1000000
ORDER BY id
LIMIT 20;
Enter fullscreen mode Exit fullscreen mode

With an appropriate index, the database can jump directly into the relevant region.

The cost becomes closer to:

$$
O(\log n + k)
$$

where (k) is the number of returned records.

This is a beautiful example of algorithmic thinking improving API design.

Pagination is not just a frontend concern.

It is a database complexity concern.


14. The N+1 Query Problem Is Multiplication

Suppose your API returns:

100 posts
Enter fullscreen mode Exit fullscreen mode

Then for each post, it fetches its author.

You now have:

1 query for posts
+
100 queries for authors
Enter fullscreen mode Exit fullscreen mode

Total:

$$
101
$$

queries.

If there are:

10,000 requests
Enter fullscreen mode Exit fullscreen mode

per minute:

$$
101 \times 10,000
$$

equals:

$$
1,010,000
$$

database queries per minute.

That is:

$$
16,833
$$

queries per second.

And suddenly the problem becomes obvious.

The database was not necessarily slow.

The application multiplied work.

The N+1 problem is essentially a multiplication problem hiding inside application logic.


15. Caching Is Probability

Now let's talk about caching.

Suppose a database query normally costs:

20 ms
Enter fullscreen mode Exit fullscreen mode

But a cache lookup costs:

0.5 ms
Enter fullscreen mode Exit fullscreen mode

If the cache hit rate is:

95%
Enter fullscreen mode Exit fullscreen mode

then:

$$
P(hit)=0.95
$$

and:

$$
P(miss)=0.05
$$

Expected latency is approximately:

$$
E[T] = P(hit)T_{hit} + P(miss)T_{miss}
$$

Therefore:

$$
E[T] = 0.95(0.5) + 0.05(20)
$$

$$
E[T] = 0.475 + 1
$$

$$
E[T] = 1.475ms
$$

Your average latency dropped from:

20 ms
Enter fullscreen mode Exit fullscreen mode

to approximately:

1.475 ms
Enter fullscreen mode Exit fullscreen mode

because of probability.

But there is another lesson.

A cache hit rate of:

95%
Enter fullscreen mode Exit fullscreen mode

sounds excellent.

But if the cache miss path is extremely expensive, those 5% misses can dominate tail latency.

This is why average latency is not enough.


16. The Mathematics of Tail Latency

Suppose:

99% of requests = 10 ms
1% of requests = 2 seconds
Enter fullscreen mode Exit fullscreen mode

Average latency:

$$
0.99(10) + 0.01(2000)
$$

$$
= 9.9 + 20
$$

$$
= 29.9ms
$$

You might report:

"Our average latency is about 30 ms."

Sounds excellent.

Until 1% of your users experience 2 seconds.

At millions of requests, that 1% is not a corner case.

If you process:

10,000,000 requests
Enter fullscreen mode Exit fullscreen mode

then:

$$
10,000,000 \times 0.01 = 100,000
$$

requests experience the bad latency.

That is why serious systems care about:

p50
p90
p95
p99
p99.9
Enter fullscreen mode Exit fullscreen mode

The tail tells you what happens when the system is under pressure.

And database performance often lives in the tail.


17. Connection Pools Are Queues

Your application probably has a database connection pool.

Suppose you configure:

20 connections
Enter fullscreen mode Exit fullscreen mode

and receive:

100 concurrent database operations
Enter fullscreen mode Exit fullscreen mode

Only 20 can actively use connections at once.

The remaining 80 wait.

You now have a queue.

Increasing the connection pool can help.

But there is a trap.

If your database can efficiently process only 50 concurrent operations, increasing the pool to:

500
Enter fullscreen mode Exit fullscreen mode

does not magically create 500 units of database capacity.

It may simply create:

more contention
more memory usage
more context switching
more lock competition
more queueing
Enter fullscreen mode Exit fullscreen mode

This is another lesson from queueing theory:

More concurrency is not automatically more throughput.

At some point, you hit the service capacity of the database.


18. Amdahl's Law: Why One Slow Part Dominates

Suppose a request spends:

80% database
20% application logic
Enter fullscreen mode Exit fullscreen mode

You optimize the application logic and make it 10x faster.

What happens?

The application portion goes from:

20 units → 2 units
Enter fullscreen mode Exit fullscreen mode

The database remains:

80 units
Enter fullscreen mode Exit fullscreen mode

Total:

82 units
Enter fullscreen mode Exit fullscreen mode

Original:

100 units
Enter fullscreen mode Exit fullscreen mode

Speedup:

$$
\frac{100}{82} \approx 1.22
$$

You made one component 10x faster.

The entire system improved only about:

22%
Enter fullscreen mode Exit fullscreen mode

This is Amdahl's Law.

The basic equation is:

$$
Speedup = \frac{1}{(1-p)+\frac{p}{s}}
$$

Where:

  • (p) = fraction of work improved
  • (s) = speedup of that component

This is why database optimization matters so much when the database dominates request latency.

And it also explains why optimizing irrelevant code is often a waste of time.


19. Database Performance Is Usually About Reducing Work

There is a recurring theme across everything we've discussed.

Indexes reduce search work.

Query predicates reduce rows.

Pagination reduces returned work.

Caching reduces database work.

Batching reduces round trips.

Prepared statements reduce repeated parsing work.

Materialized views reduce repeated computation.

Denormalization can reduce join work.

Partitioning reduces the amount of data considered.

The common principle is:

$$
Performance \approx \frac{Useful\ Work}{Total\ Work}
$$

Or more practically:

The fastest query is often the query that does not have to do something.

This is why performance engineers ask:

Why are we reading this row?
Why are we joining this table?
Why are we sorting this?
Why are we fetching this column?
Why are we making this request?
Why are we doing this query 100 times?
Enter fullscreen mode Exit fullscreen mode

Optimization begins with subtraction.


20. The Mathematics of Data Growth

Suppose your database starts with:

100,000 rows
Enter fullscreen mode Exit fullscreen mode

and grows by:

1,000,000 rows/month
Enter fullscreen mode Exit fullscreen mode

After one year:

$$
100,000 + 12(1,000,000)
$$

$$
= 12,100,000
$$

Your query might work perfectly at 100,000 rows.

But if its cost is:

$$
O(n)
$$

then the amount of work scales directly with the data.

If the cost is:

$$
O(n\log n)
$$

growth becomes even more interesting.

If the cost is:

$$
O(n^2)
$$

you are eventually going to have a very bad day.

This is why complexity analysis matters even in SQL.

A query that looks harmless against a development database may be algorithmically disastrous against production data.

Your database size today is not the only thing that matters.

You need to ask:

What happens when the data is 100x larger?


21. Partitioning: Divide the Problem

Suppose a database contains:

10 billion events
Enter fullscreen mode Exit fullscreen mode

but your application usually queries only the current month.

Why search through 10 billion rows?

Partitioning can divide the dataset:

2026-01
2026-02
2026-03
...
2026-08
Enter fullscreen mode Exit fullscreen mode

Now a query such as:

WHERE created_at >= '2026-08-01'
Enter fullscreen mode Exit fullscreen mode

can potentially eliminate partitions that cannot contain relevant data.

This is called partition pruning.

Mathematically, instead of searching:

$$
N
$$

rows, you may search approximately:

$$
N_k
$$

where:

$$
N_k \ll N
$$

This is the same fundamental strategy used everywhere in computer science:

Reduce the search space.

Binary search does it.

Indexes do it.

Hashing does it.

Partitioning does it.

Caching does it.

Distributed databases do it.

Good algorithms spend less time looking in places where the answer cannot possibly exist.


22. Normalization Has a Mathematical Cost

Database normalization is fantastic.

It reduces duplication.

It improves consistency.

But normalization can increase the number of joins required to reconstruct information.

Suppose a customer profile is distributed across:

customers
addresses
preferences
subscriptions
orders
Enter fullscreen mode Exit fullscreen mode

A dashboard may need several joins.

If the application repeatedly needs the same combined representation, you might consider:

  • denormalization
  • materialized views
  • caching
  • precomputed aggregates

This is not an argument against normalization.

It is an argument for understanding trade-offs.

You are exchanging:

write complexity / storage duplication
Enter fullscreen mode Exit fullscreen mode

for:

read complexity / join cost
Enter fullscreen mode Exit fullscreen mode

Database architecture is full of these equations.

There is rarely a universally optimal design.

There is usually an optimization problem.


23. The Hidden Cost of Network Round Trips

Suppose your application and database are separated by:

1 ms
Enter fullscreen mode Exit fullscreen mode

of network latency.

One query:

1 ms
Enter fullscreen mode Exit fullscreen mode

Not terrible.

But now your application performs:

100 queries
Enter fullscreen mode Exit fullscreen mode

sequentially.

Ignoring database execution time:

$$
100 \times 1ms = 100ms
$$

If network latency is:

5 ms
Enter fullscreen mode Exit fullscreen mode

then:

$$
100 \times 5ms = 500ms
$$

The database might be incredibly fast.

Your architecture can still be slow.

This is why batching is powerful.

Instead of:

100 round trips
Enter fullscreen mode Exit fullscreen mode

you might perform:

1 round trip
Enter fullscreen mode Exit fullscreen mode

and process the batch server-side.

This is another case where multiplication destroys performance.


24. Why "SELECT *" Is More Than a Style Problem

Consider:

SELECT *
FROM users
WHERE id = 123;
Enter fullscreen mode Exit fullscreen mode

It works.

But suppose the table contains:

id
name
email
profile
preferences
avatar
biography
settings
large_json_blob
Enter fullscreen mode Exit fullscreen mode

You only need:

id
name
email
Enter fullscreen mode Exit fullscreen mode

Yet the database may have to read and transfer more information than necessary.

The cost of a query is influenced by:

$$
Rows \times Columns \times RowWidth
$$

If you reduce row width, you reduce:

  • I/O
  • memory bandwidth
  • network transfer
  • serialization
  • deserialization

This is why data size matters.

The database is not moving abstract objects.

It is moving bytes.


25. Write Performance Has Its Own Mathematics

Everyone loves talking about read performance.

Then production gets busy.

Writes become the problem.

Suppose one insert modifies:

1 table
Enter fullscreen mode Exit fullscreen mode

with:

```text 5 indexes




The database does not merely insert the row.

It must maintain those indexes.

Conceptually:



```text
Insert row
   ↓
Update index 1
   ↓
Update index 2
   ↓
Update index 3
   ↓
Update index 4
   ↓
Update index 5
Enter fullscreen mode Exit fullscreen mode

More indexes can make reads faster while making writes more expensive.

So indexing is not:

free speed
Enter fullscreen mode Exit fullscreen mode

It is a trade-off.

You are effectively paying:

write cost + storage + maintenance
Enter fullscreen mode Exit fullscreen mode

to obtain:

read performance
Enter fullscreen mode Exit fullscreen mode

This is why every index should have a reason to exist.


26. Transactions and Concurrency

Now we reach one of the hardest parts.

Concurrency.

Imagine two transactions both attempt:

balance = balance - 100
Enter fullscreen mode Exit fullscreen mode

at the same time.

Without proper isolation, you can get race conditions.

Databases therefore implement mechanisms involving:

  • locks
  • MVCC
  • snapshots
  • isolation levels
  • conflict detection

These mechanisms have performance costs.

Higher isolation can reduce certain anomalies but may increase contention.

More locking can mean more waiting.

More waiting means greater latency.

And again:

$$
Concurrency \rightarrow Queueing \rightarrow Latency
$$

Everything connects.

Database performance cannot be separated from correctness.

A database that is extremely fast but produces incorrect balances is not performant.

It is broken.


27. The Database Optimizer Is a Statistician

Modern query optimizers do not blindly execute SQL in the written order.

They estimate.

They ask:

How many rows will this predicate return?

How selective is this condition?

How expensive is this join?

Which index is useful?

Should I hash?

Should I sort?

Should I scan?
Enter fullscreen mode Exit fullscreen mode

These decisions depend heavily on statistics.

Imagine:

10 million rows
Enter fullscreen mode Exit fullscreen mode

but only:

10 rows
Enter fullscreen mode Exit fullscreen mode

match a predicate.

An index is likely attractive.

Now imagine:

9 million rows
Enter fullscreen mode Exit fullscreen mode

match.

A sequential scan may be cheaper.

The optimizer is essentially trying to estimate:

$$
Expected\ Cost
$$

for different execution plans.

This is why stale statistics can sometimes lead to terrible query plans.

The optimizer is solving a problem using information.

Bad information produces bad decisions.


28. Performance Is an Optimization Function

You can think of database design as optimizing something like:

$$
Cost =
C_{CPU}
+
C_{IO}
+
C_{Memory}
+
C_{Network}
+
C_{Contention}
$$

The exact formulas differ between database engines.

But the conceptual model is useful.

A query can be CPU-heavy.

Or I/O-heavy.

Or network-heavy.

Or memory-heavy.

Or blocked by locks.

That means "optimize the query" is not a complete engineering instruction.

You need to ask:

Which resource is actually limiting us?

If the query is CPU-bound, adding RAM may do almost nothing.

If it is I/O-bound, optimizing a JavaScript loop is irrelevant.

If it is network-bound, rewriting the SQL may not help.

Performance engineering begins with measurement.


29. Why EXPLAIN Is Basically a Mathematical Window

When you run:

EXPLAIN ANALYZE
Enter fullscreen mode Exit fullscreen mode

you are asking the database:

"Show me the computational strategy you chose."

You might see:

Seq Scan
Index Scan
Nested Loop
Hash Join
Sort
Aggregate
Enter fullscreen mode Exit fullscreen mode

These are algorithms.

And their costs depend on data size.

For example:

Seq Scan
Rows Removed by Filter: 9,900,000
Rows Returned: 100,000
Enter fullscreen mode Exit fullscreen mode

That tells a story.

The database examined a huge amount of data to produce a smaller result.

You can then ask:

Can I make the database avoid touching those 9.9 million rows?

That is the mathematical question hiding behind the query plan.


30. The Most Dangerous Complexity Is the Complexity You Cannot See

A database query can look like:

SELECT *
FROM orders
WHERE customer_id = ?
Enter fullscreen mode Exit fullscreen mode

One line.

But internally it may involve:

parse
↓
plan
↓
index lookup
↓
page reads
↓
buffer cache
↓
visibility checks
↓
row reconstruction
↓
serialization
↓
network transfer
Enter fullscreen mode Exit fullscreen mode

The SQL is only the tip of the iceberg.

This is why database engineering is so fascinating.

A declarative language allows you to describe what you want.

The database decides how to obtain it.

That "how" is where algorithms and mathematics live.


31. Database Performance Is Applied Computer Science

Once you understand the mathematics, database optimization stops feeling like folklore.

You begin seeing familiar concepts everywhere.

Indexes

$$
O(\log n)
$$

Hash joins

Approximately:

$$
O(n+m)
$$

Sorting

$$
O(n\log n)
$$

Naive nested loops

$$
O(nm)
$$

Pagination

Offset:

$$
O(offset+k)
$$

Cursor-based:

$$
O(\log n+k)
$$

Caching

Expected latency:

$$
E[T] =
P(hit)T_{hit}
+
P(miss)T_{miss}
$$

Concurrency

Little's Law:

$$
L=\lambda W
$$

Optimization

Amdahl's Law:

$$
S=\frac{1}{(1-p)+p/s}
$$

These aren't just academic equations.

They show up in production.

They show up when your API suddenly gets slow.

They show up when your dashboard times out.

They show up when your database CPU reaches 100%.

They show up when adding an index makes writes slower.

They show up when pagination collapses at page 10,000.

They show up when a cache miss causes a latency spike.


32. The Ultimate Database Question

Whenever you encounter a slow query, resist the temptation to immediately change something.

Instead ask:

What is the database actually doing?

Then ask:

How much data is it touching?

Then:

How many rows survive each filtering step?

Then:

What algorithm is being used?

Then:

Where is the latency coming from?

Then:

What happens when the dataset grows 10x?

Then:

What happens under 100x concurrency?

These questions lead you toward the real problem.

Not:

"Should I add an index?"

But:

"What computational problem am I asking the database to solve?"

That is a much better question.


33. The Database Is a Machine for Avoiding Work

This might be the deepest idea in the entire article.

Good database systems are fundamentally machines for avoiding unnecessary work.

An index says:

Don't scan everything.

A cache says:

Don't calculate this again.

A materialized view says:

Don't perform this expensive join every time.

Partitioning says:

Don't search irrelevant data.

A query predicate says:

Don't return unnecessary rows.

Projection says:

Don't move unnecessary columns.

Batching says:

Don't pay network latency repeatedly.

A good schema says:

Don't create ambiguity that forces expensive operations.

The entire field of database optimization can almost be summarized as:

Do less work, move fewer bytes, wait less, and make the remaining work predictable.

That is the mathematics.


Final Thoughts

The next time somebody tells you:

"The database is slow."

Do not immediately believe them.

The database may be slow.

But something else may also be happening.

Maybe the application is executing 101 queries instead of 2.

Maybe an index has terrible selectivity.

Maybe pagination is scanning a million rows.

Maybe a query is transferring megabytes to retrieve three fields.

Maybe a connection pool is creating massive contention.

Maybe the optimizer has bad statistics.

Maybe the system is suffering from tail latency.

Maybe the database is waiting on storage.

Maybe the database is waiting on locks.

Maybe the database is actually fine.

The interesting part is that almost all of these problems can be expressed mathematically.

That is what makes database engineering so beautiful.

Behind:

SELECT
Enter fullscreen mode Exit fullscreen mode

there is complexity.

Behind:

JOIN
Enter fullscreen mode Exit fullscreen mode

there is an algorithm.

Behind:

WHERE
Enter fullscreen mode Exit fullscreen mode

there is selectivity.

Behind:

ORDER BY
Enter fullscreen mode Exit fullscreen mode

there is sorting or ordering.

Behind:

LIMIT
Enter fullscreen mode Exit fullscreen mode

there is a question about how much data must actually be produced.

Behind:

INDEX
Enter fullscreen mode Exit fullscreen mode

there is logarithmic search.

Behind:

CACHE
Enter fullscreen mode Exit fullscreen mode

there is probability.

Behind:

CONNECTION POOL
Enter fullscreen mode Exit fullscreen mode

there is queueing theory.

Behind:

TRANSACTION
Enter fullscreen mode Exit fullscreen mode

there is concurrency theory.

And behind all of it is one simple engineering principle:

Performance is what happens when computation meets scale.

At small scale, almost every design looks intelligent.

At large scale, mathematics starts asking questions.

How many rows?

How many requests?

How many bytes?

How many joins?

How many round trips?

How many concurrent users?

How many milliseconds?

How many times are we doing the same work?

And eventually the database stops being a mysterious black box.

You start seeing it for what it really is.

A collection of algorithms operating over enormous mathematical structures, constrained by physical hardware, probabilities, queues, memory hierarchies, and the laws of computation.

The SQL is merely the language you use to ask the question.

The database's job is to find the cheapest way to answer it.

And your job as an engineer is to understand why that answer costs what it costs.

Because once you understand the mathematics behind database performance, you stop optimizing by superstition.

You stop adding indexes because someone on Stack Overflow said so.

You stop throwing RAM at every problem.

You stop blaming the database for application-level inefficiencies.

You start measuring.

You start modeling.

You start thinking in:

$$
O(n)
$$

$$
O(\log n)
$$

$$
O(n\log n)
$$

$$
O(nm)
$$

You start thinking about:

$$
Latency
$$

$$
Throughput
$$

$$
Selectivity
$$

$$
Probability
$$

$$
Contention
$$

$$
Bandwidth
$$

$$
Queueing
$$

And suddenly database performance becomes much less mysterious.

Because the database was never magic.

It was mathematics the entire time.

Top comments (0)