Denormalization

Denormalization and When It Pays

Denormalization stores a derived or copied value on purpose to make a measured, hot read cheap. The classic case is an order total, which shop computes from order_items. At 200,000 orders:

The ten largest orders: aggregating the lines versus a stored, indexed columnSQL
SET SESSION cte_max_recursion_depth = 200000;
CREATE TABLE big_orders (PRIMARY KEY (id)) AS WITH RECURSIVE n (id) AS
  (SELECT 1 UNION ALL SELECT id + 1 FROM n WHERE id < 200000) SELECT id FROM n;
CREATE TABLE big_items (PRIMARY KEY (order_id, product_id)) AS
  SELECT o.id AS order_id, p.id AS product_id, (o.id * p.id) % 4 + 1 AS quantity,
         5 + (o.id * 7 + p.id) % 45 AS unit_price
  FROM big_orders AS o JOIN (VALUES ROW(1), ROW(4), ROW(7)) AS p (id);
SET @t = NOW(6);
SELECT GROUP_CONCAT(order_id) INTO @a FROM (SELECT order_id, SUM(quantity * unit_price) AS t
  FROM big_items GROUP BY order_id ORDER BY t DESC, order_id DESC LIMIT 10) AS x;
SET @agg = TIMESTAMPDIFF(MICROSECOND, @t, NOW(6)) / 1e6;
ALTER TABLE big_orders ADD total DECIMAL(10,2) NOT NULL DEFAULT 0, ADD INDEX (total);
UPDATE big_orders AS o JOIN (SELECT order_id, SUM(quantity * unit_price) AS s FROM big_items
  GROUP BY order_id) AS t ON t.order_id = o.id SET o.total = t.s;
SET @t = NOW(6);
SELECT GROUP_CONCAT(id) INTO @b FROM
  (SELECT id FROM big_orders ORDER BY total DESC, id DESC LIMIT 10) AS x;
SELECT @agg AS aggregate_s, TIMESTAMPDIFF(MICROSECOND, @t, NOW(6)) / 1e6 AS stored_s,
       @a = @b AS same_top_ten;
Output
+-------------+----------+--------------+
| aggregate_s | stored_s | same_top_ten |
+-------------+----------+--------------+
|    0.319663 | 0.000616 |            1 |
+-------------+----------+--------------+

A reverse scan of the index answers hundreds of times faster. Keep sort directions equal: with ORDER BY total DESC, id the index was useless and the query took 0.80 s. The price is upkeep. An AFTER INSERT trigger adding NEW.quantity * NEW.unit_price kept total correct in a test, but needs UPDATE and DELETE twins (Triggers and Their Limits); or write the total in the same PHP transaction as the lines. order_items.unit_price is no copy: it is the price at the time of sale. MySQL 9.7 524 accepts CREATE MATERIALIZED VIEW but makes an ordinary view; only HeatWave 207 materializes. Denormalize when reads dominate, the cost is measured, and one mechanism owns the copy.