Collation & Type Precedence · 10M rows
Implicit conversion: 3 reads vs. 27,469 reads
When .NET/EF sends a Unicode parameter (@p nvarchar) against a varchar column, type precedence forces a row-by-row scan under default SQL collation:
nvarchar (Scan)
27,469 reads · 446 ms
Matched (Seek)
3 reads · 2.7 ms
Under SQL collations, SQL Server has to convert every single row (plan warning: CONVERT_IMPLICIT, 3,011 ms CPU time). 3 page reads turn into a massive scan. View lab benchmarks & collation matrix →
Passing Sets & Cache · 10M rows
Long IN lists (2,000 IDs) & ad-hoc floods
How do you pass hundreds or thousands of IDs from C# to SQL Server? Four methods benchmarked at 10 to 2,000 IDs and with 50,000 single calls:
Literal list
6,376 KB cache · 589 ms
TVP (2,000 IDs)
200 KB cache · 222 ms
Individual parameters inflate the plan cache; 50,000 ad-hoc queries occupy 781 MB memory vs. 0.1 MB parameterised. Table-valued parameters (TVP) maintain a single reusable plan. View lab benchmarks & 4-method comparison →
Data Model & Storage · 1M rows
Composite text key vs. surrogate key (int)
A three-part business key (Mandant + Bereich + Artikelnummer, ~56 bytes) repeats in every child table and foreign key index:
Text key
287 MB · 19,075 reads
Surrogate int
65 MB · 3,919 reads
The text key inflates storage 4.4-fold and makes child joins 2.5 times slower (1,142 ms vs. 471 ms). The business key is preserved as a UNIQUE constraint. View lab benchmarks & metadata script →
SARGability · 10M rows
Leading wildcard (LIKE '%berg')
A suffix search such as WHERE Firma LIKE '%berg' is not SARGable – even with an index, SQL Server scans all 49,022 pages:
Standard LIKE
49,022 reads · 3,890 ms
REVERSE + Idx
43 reads · 6.3 ms (×617)
With a computed column FirmaRev AS REVERSE(Firma) and an index on it, the search becomes a prefix seek. Reads drop by ×1,140, and runtime drops from 4 seconds to 6.3 ms. View lab benchmarks & computed column →
Server Boundaries · 1M rows
Linked Server vs. OPENQUERY & Collation Trap
Queries across server and database boundaries produce radically different execution plans depending on syntax and collation:
OPENQUERY
167,982 reads · 1,224 ms
Linked 4-part
746 ms (stable plan)
For large joins, OPENQUERY forces thousands of nested-loop accesses. If target collation differs, implicit conversion kills the seek (factor ×119 I/O). View lab benchmarks & remote join tests →
Normalisation · 1M rows
Plain-text columns vs. lookup tables (3NF)
When attributes like Stadt and Zahlungsart are stored as plain text instead of compact foreign keys:
Plain-text scan
4,074 reads · 1,518 ms
3NF + Join
1,608 reads · 33 ms (×46.4)
Pattern matching on the compact lookup table costs almost nothing before joining via lean keys – 46.4 times faster. Contradiction checks drop from 21,584 to 32 reads. View lab benchmarks & schema modelling →
Procedure vs. view · 10M customers
Compute everything, filter later: factor ×2,600
In the measured case (synthetic data, cold cache) the same question – revenue of the customers in one city – once through a view and once through a procedure without parameters whose result is filtered afterwards:
Procedure, filtered outside
134,172 ms
The view lets the filter reach the index; the procedure computes all 10 million customers. A multi-statement function sits in between at 53,095 ms. With a parameter the procedure is as fast as the view (54 ms). View lab benchmarks →
Partitioning · 100M bookings
Removing one month: 14,082 ms vs. 8.5 ms
One month (833,333 rows) is removed from 100 million bookings – with DELETE or by truncating the month's partition:
DELETE
14,082 ms with date index (without: 67,298 ms)
Truncate partition
8.5 ms (×1,657)
DELETE logs every row and maintains every index; truncating a partition only deallocates its pages. Prerequisite: the month lies in exactly one partition and all indexes are aligned. View lab benchmarks →
Partitioning · 100M bookings
Partitioning does not make queries faster by itself
The same bookings, once with a plain index by date, once partitioned by month:
Account, partitioned
285 ms
Account, not partitioned
45 ms
The account query does not know the month and searches all 120 partitions. The monthly total was not faster when partitioned either: 244 ms vs. 173 ms with a plain date index. Partitioning serves data maintenance; for queries it does not replace the right index. View lab benchmarks →
Join · 100M customers, 400M line items
Three-table join: factor ×632
We look for the 400 line items of the customers in one city – without and with indexes on city and foreign keys:
Primary keys only
6,843,764 reads · 41,982 ms
With indexes
3,144 reads · 66 ms
With three subqueries per customer instead of the join: 50,737 ms vs. 232 ms (×219). View lab benchmarks →