Index tuning on a heavy reportBeverage distributor, anonymized × red9CS-0106
A sales-margin report cut its disk reads 381-fold, from 297,091 to 777
The problem. A beverage distributor ran a sales-margin report about 71 times a day, joining order lines, orders, items, and customers. Each run took roughly 592 ms and drove 297,091 disk reads, with the daily total reaching 21,093,461. The join filtered on a sales-history date the orders table had no index for.
What we did. We created one nonclustered index on that date column, carrying the six columns the report returns as included columns so nothing goes back to the base table, and captured the same Query Store measurements again.
Red9 · Performance Impact
Disk reads per run
381x
297,091 down to 777.
Report duration
592 → 95
Milliseconds, at about 71 runs a day.
Daily reads
21.1M → 55,167
From a report that only runs a few dozen times a day.
Duration
591.59 ms → 94.65 ms
6.3x shorter
CPU
205.72 ms → 45.3 ms
4.5x less
Disk reads
297,091 → 777
381x less
How to read these. The 381x is the figure the report gives for disk reads falling from 297,091 to 777. Duration and CPU are 591.59 over 94.65 and 205.72 over 45.3. The daily totals came down from 21,093,461 reads to 55,167. Low frequency, high weight: this is the shape of query that a per-execution ranking would have missed entirely.
The result. The margin report returns in 95 ms reading 777 pages, and the storage subsystem sees 21 million fewer reads a day from it. The distributor's reporting stopped interfering with the operational workload around it.
The technical detail
What the review found. The report filtered orders on a sales-history date with no index, so the join drove a scan of the orders table and pulled the other three tables along with it.
What we changed (identifiers generalized for privacy):
-- Sales-margin report: ~71 runs/day, 591.59 ms each, 297,091 disk reads, 21.1M/day.
CREATE NONCLUSTERED INDEX IX_red9_orders_sales_history_date
ON dbo.[orders] (salesHistoryDate)
INCLUDE (isToDistributor, ticketNumber, isVoided, shippedDate, customerId, waitState);
-- before/after captured with Query Store; DROP INDEX rollback provided
A wide include list is worth its storage when it removes 296,000 reads from every single run of a report.