What does an index actually cost on an INSERT or an UPDATE?
Three things. Storage, work, and unpredictability.
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
amount DECIMAL(10, 2) NOT NULL
);
INSERT INTO orders (customer_id, amount) VALUES
(1, 120.00), (1, 340.50), (2, 75.25);
SELECT 'table only' AS stage,
(SELECT COUNT(*) FROM orders) AS rows_stored,
(SELECT * FROM pragma_page_count) AS pages_used;
CREATE INDEX idx_orders_customer ON orders (customer_id);
SELECT 'with index' AS stage,
(SELECT COUNT(*) FROM orders) AS rows_stored,
(SELECT * FROM pragma_page_count) AS pages_used;
-- The index is maintained on every write, so it is correct immediately.
SELECT COUNT(*) AS rows_for_customer_1
FROM orders
WHERE customer_id = 1;The page counts show the storage cost directly: the table occupies some pages on its own, and the index adds pages on top of it. The row count stays at three — an index stores no rows, it stores entries pointing at them — and the final query returns 2, confirming the index is correct straight after the write.
The work is the second cost. Every INSERT has to add an entry to every index on the table, and every UPDATE that changes an indexed column has to update or delete the old entry and add a new one. That is a B-tree insert per index, and it has to be durable, because the index has to be consistent with the table after a crash. On a write-heavy table, five indexes can turn a fast insert into a slow one.
The third cost is unpredictability. A random primary key means the index entries are written in random order, so every insert touches a random page and may have to split one, leaving half-empty pages behind. The same insert against a sequential key appends to the right-hand edge, touches the same hot pages and never splits. This is why the choice of primary key affects write performance so much, and why the page count of a badly-indexed table can be far larger than the data warrants.