Tag: Indexing

  • SQL Server Index Syntax: Covering Indexes, INCLUDE Columns, and Filtered Indexes

    SQL Server Index Syntax: Covering Indexes, INCLUDE Columns, and Filtered Indexes

    Beyond a basic index, two techniques do most of the real performance work: covering indexes, which eliminate the key lookup from the previous lesson entirely, and filtered indexes, which shrink an index down to just the rows that actually matter.

    Covering & filtered indexes(what actually lives in the index)one index row:KEY:customer_idsets sort order+INCLUDE:order_status, order_totaljust riding along, no sortcovers the whole queryno Key Lookup!Key Lookupfiltered index = only the rows that match WHERE:shippedshippedpendingcompletedpendingshippedIX_OrderLog_PendingOnlytiny index — only ‘pending’ rowsoptimizer only picksthis index when WHEREclause PROVES a match —otherwise: full scan 📌

    Covering Index with INCLUDE

    CREATE NONCLUSTERED INDEX IX_OrderLog_Customer_Covering
    ON dbo.OrderLog (customer_id)
    INCLUDE (order_status, order_total);
    
    -- Fully satisfied by the index — no key lookup needed
    SELECT customer_id, order_status, order_total
    FROM dbo.OrderLog
    WHERE customer_id = 42;

    “Covering” means every column the query needs — for filtering, sorting, or just selecting — exists somewhere in the index itself, so the engine never has to jump back to the clustered index at all. This directly eliminates the exact key-lookup cost the previous lesson demonstrated.

    Key Columns vs INCLUDE Columns

    Key columns Determine sort order Usable for seeking/filtering INCLUDE columns Just ride along at the leaf Avoid a lookup, can’t be used to seek

    Put columns you filter/sort on in the key; put columns you only ever SELECT in INCLUDE — this keeps the index narrower and cheaper to maintain than making everything a key column. Key columns also enforce sort order (relevant to ORDER BY), while INCLUDE columns carry no ordering guarantee at all — they’re purely along for the ride.

    Common mistake: Putting every SELECTed column into the key list “to be safe.” Wider key columns mean a physically larger B-tree, more page splits on insert, and more expensive maintenance on every write — INCLUDE exists specifically to avoid that cost for columns that only need to be read, never searched or sorted on.

    Filtered Index: Indexing Just a Subset

    CREATE NONCLUSTERED INDEX IX_OrderLog_PendingOnly
    ON dbo.OrderLog (order_date)
    WHERE order_status = 'pending';

    Ideal when queries consistently target a small, well-defined subset of a large table — the index is smaller, faster to scan, and cheaper to maintain since it only updates when a matching row changes (a row with order_status = 'completed' never touches this index at all, on insert or update).

    -- The optimizer only uses a filtered index when the query's WHERE clause
    -- provably matches (or is a subset of) the index's filter condition:
    SELECT * FROM dbo.OrderLog WHERE order_status = 'pending' AND order_date > '2026-01-01';
    -- Uses IX_OrderLog_PendingOnly — the query's filter is compatible with the index's
    
    SELECT * FROM dbo.OrderLog WHERE order_date > '2026-01-01';
    -- Does NOT use it — this query has no order_status filter, so the index can't
    -- guarantee it covers every matching row
    Practice tip: Build both indexes above, then compare sys.dm_db_index_physical_stats page counts between the filtered index and an unfiltered equivalent covering the same key column, on a table where ‘pending’ is a small fraction of total rows. The size difference makes the benefit concrete rather than theoretical.

    Enjoyed this?

    Subscribe to get every new SQL Server lesson as soon as it’s published, and share it with a developer who’d find it useful.

    📡 Subscribe via RSS  | 
    Share on X  | 
    Share on LinkedIn  | 
    Share on Facebook

    Want the full structured course with quizzes, projects, and 10+ exercises per chapter? Check out SQL Server for Developers & DBAs, coming soon on this site.

  • SQL Server Index Strategy: Deciding What to Index Based on Real Query Patterns

    SQL Server Index Strategy: Deciding What to Index Based on Real Query Patterns

    Knowing index syntax isn’t the hard part — deciding what deserves an index is. This is strategy, not mechanics.

    Index Strategy, sketched out(what actually deserves an index)1. Missing Index DMVlogs a wish, not an order— verify before creating2. Weigh the Traderead benefit vs write costweighted by real frequency3. Decidecreate, keep, or dropRead Benefit ↑faster seeks, not scansfewer logical reads per run× how often the query runsvsWrite Cost ↓every INSERT/UPDATE/DELETEmust maintain this index× the table’s write frequencyZero reads + nonzero writes =pure cost. Drop it — unless it’senforcing a PK/UNIQUE. 📌

    Let SQL Server Tell You What It’s Missing

    SELECT
        d.statement AS table_name,
        d.equality_columns, d.inequality_columns, d.included_columns,
        s.user_seeks, s.avg_total_user_cost, s.avg_user_impact
    FROM sys.dm_db_missing_index_details d
    JOIN sys.dm_db_missing_index_groups g ON d.index_handle = g.index_handle
    JOIN sys.dm_db_missing_index_group_stats s ON g.index_group_handle = s.group_handle
    ORDER BY s.avg_user_impact * s.user_seeks DESC;

    This DMV logs every time the optimizer wished an index existed. It’s a starting hypothesis, not an automatic answer — verify against the Evidence-First workflow before creating anything.

    The Cost-Benefit That Actually Matters

    This is a direct application of Ch.8’s covering-index lesson turned into a decision framework, not just a syntax choice: every key/INCLUDE column you add helps reads but also widens what every write has to maintain.

    Read Benefit Faster seeks instead of scans Fewer logical reads per query Weighted by how OFTEN the query runs Write Cost Every INSERT/UPDATE/DELETE maintains every index touched Weighted by table’s write frequency

    An index that helps a report run once a week but slows down a table receiving thousands of writes per second is very likely a bad trade — measure both sides, not just the read win.

    Finding Indexes That Aren’t Earning Their Keep

    SELECT OBJECT_NAME(s.object_id) AS table_name, i.name AS index_name,
        s.user_seeks + s.user_scans + s.user_lookups AS total_reads, s.user_updates AS total_writes
    FROM sys.dm_db_index_usage_stats s
    JOIN sys.indexes i ON i.object_id = s.object_id AND i.index_id = s.index_id
    WHERE s.database_id = DB_ID() AND s.user_updates > 0
    ORDER BY (s.user_seeks + s.user_scans + s.user_lookups) ASC;

    Sort ascending by reads with nonzero writes — the indexes at the top are pure cost, no benefit. Strong drop candidates, pending one more check: confirm they’re not enforcing a UNIQUE constraint or primary key first.

    Key Takeaways

    • Missing index DMVs are hypotheses to verify, not automatic instructions
    • Every index decision is a trade: read benefit vs. write cost, weighted by actual frequency
    • Regularly audit for unused indexes — they cost on every write with zero return

    Enjoyed this?

    Subscribe to get every new lesson as soon as it’s published, and share it with a developer who’d find it useful.

    📡 Subscribe via RSS  | 
    Share on X  | 
    Share on LinkedIn  | 
    Share on Facebook

    Want the full structured course with quizzes and hands-on labs? Check out SQL Server Performance Tuning, coming soon on this site.

  • Index Fragmentation and Statistics Maintenance in SQL Server: REORGANIZE vs REBUILD

    Index Fragmentation and Statistics Maintenance in SQL Server: REORGANIZE vs REBUILD

    “Always rebuild indexes weekly” is one of the most common DBA myths in this entire field. Here’s what fragmentation and statistics actually require.

    Fragmentation & Stats, sketched out(measure — don’t schedule blindly)Healthy Index1234pages in physical order— one smooth scanFragmented Index1324pages scattered on disk— extra random I/O jumps< 5%5–30%> 30%do nothingREORGANIZE (online)REBUILDA tiny, always-seeked table at60% fragmentation? Doesn’t matter.Only large range-scanned tables care. 📌

    Measuring Fragmentation First

    This is the direct extension of Ch.8’s index-usage DMV lesson — same evidence-first instinct, now pointed at internal index health instead of usage frequency.

    SELECT OBJECT_NAME(ips.object_id) AS table_name, i.name AS index_name,
        ips.avg_fragmentation_in_percent, ips.page_count
    FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ips
    JOIN sys.indexes i ON i.object_id = ips.object_id AND i.index_id = ips.index_id
    WHERE ips.avg_fragmentation_in_percent > 5
    ORDER BY ips.avg_fragmentation_in_percent DESC;

    The Standard Thresholds

    Fragmentation Action
    < 5% Do nothing
    5–30% ALTER INDEX ... REORGANIZE — online, lighter-weight
    > 30% ALTER INDEX ... REBUILD — heavier, can be ONLINE = ON in Enterprise

    The Myth, Directly Addressed

    “Always rebuild weekly” wastes CPU/IO on indexes that were never fragmented — measure first, every time

    A small, rarely-scanned table can sit at 60% fragmentation and never matter, because it’s tiny and always read via seeks. Fragmentation only meaningfully affects large tables read via range scans. Evidence-first applies here too: measure, don’t schedule blindly.

    Statistics: Often the Real Culprit

    -- Check when statistics were last updated and how many rows have changed since
    SELECT OBJECT_NAME(s.object_id) AS table_name, s.name AS stats_name,
        sp.last_updated, sp.rows, sp.modification_counter
    FROM sys.stats s
    CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) sp
    WHERE OBJECT_NAME(s.object_id) = 'YourTableName';
    
    -- Manually refresh if auto-update hasn't triggered recently
    UPDATE STATISTICS dbo.YourTableName WITH FULLSCAN;

    By default, auto-update statistics fires after roughly 20% of rows change (with some newer, more granular thresholds on recent SQL Server versions for large tables). A query that suddenly gets a bad plan after a large bulk load is very often stale statistics, not fragmentation — check this DMV before reaching for a rebuild.


    Enjoyed this?

    Subscribe to get every new lesson as soon as it’s published, and share it with a developer who’d find it useful.

    📡 Subscribe via RSS  | 
    Share on X  | 
    Share on LinkedIn  | 
    Share on Facebook

    Want the full structured course with quizzes and hands-on labs? Check out SQL Server Performance Tuning, coming soon on this site.

  • Columnstore Indexes in SQL Server: When to Use Them for Analytical Workloads

    Columnstore Indexes in SQL Server: When to Use Them for Analytical Workloads

    Everything covered so far is a rowstore B-tree. Columnstore indexes are a fundamentally different storage model — and a fundamentally different use case.

    Rowstore vs Columnstore, sketched out(same data, two different layouts)Rowstorereads a full ROW at onceColumnstorereads a full COLUMN at onceOLTP: “fetch order #4471”one row → rowstore winsOLAP: “SUM(amount) by month”millions of rows → columnstore winsFrequent single-row updates on acolumnstore table? Bad fit — reserveit for fact & reporting tables. 📌

    Row Storage vs Column Storage

    Rowstore Stores a full row together Great for: fetching one/few rows (OLTP: “get this order”) Columnstore Stores each column together, heavily compressed Great for: scanning/aggregating millions of rows (OLAP: “sum revenue by month”)

    Creating One

    CREATE NONCLUSTERED COLUMNSTORE INDEX IX_Sales_Columnstore
    ON dbo.SalesFact (product_id, region_id, sale_date, amount);
    
    -- Or make the whole table columnstore-organized (common for pure fact tables)
    CREATE CLUSTERED COLUMNSTORE INDEX CCI_SalesFact ON dbo.SalesFact;

    Why It’s Fast: Compression and Batch Mode

    Columnar storage compresses extremely well (repeated values in a single column compress far better than mixed row data), and queries against columnstore indexes execute in batch mode — processing ~900 rows at a time per operator call instead of one row at a time, dramatically cutting CPU overhead for large aggregations.

    When NOT to Use Columnstore

    This is the practical, real-world caveat behind why rowstore vs. columnstore isn’t a strict upgrade — it’s a workload match, exactly like choosing an iTVF vs. mTVF in the Developers & DBAs course came down to matching the tool to the shape of the problem.

    Columnstore is a poor fit for OLTP-style point lookups and frequent single-row updates — it’s optimized for bulk scan/aggregate patterns, not “fetch order #4471.” Using it as your primary OLTP table index is a common, costly mistake. Reserve it for fact tables, reporting tables, and genuinely analytical workloads.


    Enjoyed this?

    Subscribe to get every new lesson as soon as it’s published, and share it with a developer who’d find it useful.

    📡 Subscribe via RSS  | 
    Share on X  | 
    Share on LinkedIn  | 
    Share on Facebook

    Want the full structured course with quizzes and hands-on labs? Check out SQL Server Performance Tuning, coming soon on this site.

  • Build a Production-Style SQL Server Backend: A Capstone Ticket System Project

    Build a Production-Style SQL Server Backend: A Capstone Ticket System Project

    This combines every chapter of the advanced track into one realistic deliverable: the backend for TicketDesk, a small support-ticket system, built the way a real backend actually gets built — ambiguous edges, several defensible designs, and a requirement to justify your choices, not just produce code that runs.

    TicketDesk: everything, one schema(each requirement maps to a chapter)Customercreates a ticketusp_Ticket_Create (Ch.1, Ch.4)Ticketstatus, priorityusp_Ticket_Assign (Ch.4)Agentmust be activeTicketAuditAFTER UPDATE trigger,set-based (Ch.7)TicketCommentauthor_type, bodyvw_AgentWorkloadmind the JOIN type — 0-ticket agents must still show (Ch.6)one schema, everychapter of the course ✓the app’s service account:least-privilege, neverdb_owner (Ch.10) 📌

    Schema Requirements

    • Agent: agent_id, name, email (unique), is_active
    • Customer: customer_id, name, email (unique)
    • Ticket: ticket_id, customer_id (FK), assigned_agent_id (FK, nullable — unassigned tickets are a valid state), status, priority, created_at, resolved_at
    • TicketComment: comment_id, ticket_id (FK), author_type, body, created_at
    • TicketAudit: populated automatically by a trigger — old_status, new_status, changed_at

    The Architecture, Visualized

    Customer Ticket Agent TicketAudit TicketComment

    Business Logic Requirements — Mapped to Where You Learned Each One

    Requirement Chapter it draws on
    fn_GetOpenTicketCount(@agentId) — scalar or inline TVF, with a justification comment for which type you chose and why Ch.2
    usp_Ticket_Create — TRY/CATCH + transaction, OUTPUT parameter for the new ticket_id Ch.1, Ch.4
    usp_Ticket_Assign — THROWs if the agent is not active Ch.4
    usp_Ticket_Resolve — THROWs if the ticket is already closed Ch.4, Ch.5
    AFTER UPDATE trigger on Ticket — logs every status change to TicketAudit, correctly set-based for multi-row updates Ch.7
    vw_AgentWorkload — one row per active agent, including agents with zero open tickets (mind the JOIN type) Ch.6
    -- A skeleton for one requirement, deliberately incomplete — you decide the JOIN type
    CREATE VIEW dbo.vw_AgentWorkload AS
    SELECT a.agent_id, a.name, COUNT(t.ticket_id) AS open_ticket_count
    FROM dbo.Agent a
    -- ??? JOIN dbo.Ticket t ON t.assigned_agent_id = a.agent_id AND t.status IN ('open','in_progress')
    WHERE a.is_active = 1
    GROUP BY a.agent_id, a.name;

    The blank above is deliberate: pick the wrong JOIN type here and agents with zero open tickets silently vanish from the report — the exact LEFT JOIN + WHERE-vs-ON distinction from the Fundamentals course, now applied inside a view that a real dashboard would depend on.

    Performance & Security Requirements

    • Populate Ticket with 5,000+ rows and design a covering/filtered index for “open tickets by agent, ordered by priority” — prove it with before/after STATISTICS IO (Ch.8)
    • Create a least-privilege service account for the application — not db_owner, with explicit GRANTs you can justify one by one (Ch.10)

    Self-Check Before You Consider It Done

    Check Why it matters
    Run a multi-row UPDATE against Ticket’s status column and confirm every changed row appears in TicketAudit Catches the single-row-assumption trigger bug from Ch.7
    Call usp_Ticket_Assign against an inactive agent Confirms your THROW logic actually fires, not just compiles
    Query vw_AgentWorkload and confirm an agent with zero tickets still appears, with count 0 Confirms the correct JOIN type from the skeleton above
    Log in as your least-privilege service account and confirm it genuinely cannot do more than granted The only real proof least-privilege was actually applied, not just declared

    Why This Is the Right Capstone

    Every chapter of this track shows up here: functions, procedures with proper error handling, a correctly set-based trigger, a view with the right JOIN type, indexing backed by real measurement, and least-privilege security. It mirrors how a real backend ticket actually gets built — ambiguous edges, multiple valid designs, and a requirement to justify your decisions, not just produce working code.

    What comes next: Combined with the Fundamentals capstone, you now have two complete, defensible schemas behind you — a good portfolio starting point. The Performance Tuning course picks up exactly where this leaves off: given a schema like TicketDesk under real load, how do you diagnose and fix what’s actually slow, using evidence rather than guesswork.

    Enjoyed this?

    Subscribe to get every new SQL Server lesson as soon as it’s published, and share it with a developer who’d find it useful.

    📡 Subscribe via RSS  | 
    Share on X  | 
    Share on LinkedIn  | 
    Share on Facebook

    You’ve completed the full curriculum! Check out SQL Server Fundamentals and SQL Server for Developers & DBAs, coming soon as structured courses on this site.

  • Clustered vs Nonclustered Indexes in SQL Server: The B-Tree Explained

    Clustered vs Nonclustered Indexes in SQL Server: The B-Tree Explained

    Every index recommendation you’ve absorbed passively up to this point (“add an index here”) gets its real mechanical foundation in this chapter. Every SQL Server index is a B-tree: a root page, branch pages, and leaf pages. The difference between clustered and nonclustered is what lives at the leaf level — and that single difference explains almost everything else in this chapter.

    Clustered = The Table Itself(nonclustered is a separate, narrow lookup)CLUSTERED INDEXleaf level = the data itselfid 101 — data rowid 102 — data rowid 103 — data rowid 104 — data rowid 105 — data rowphysically stored in this exact orderat most ONE per tableNONCLUSTERED INDEXleaf level = key + pointer onlystatus=’shipped’ → id 101status=’cancelled’ → id 104status=’shipped’ → id 103MANY nonclustered allowed per tablekey lookup →Gotcha: many key lookups can cost more than one full scan —that’s exactly when the optimizer abandons the index. 📌

    The B-Tree, Visualized

    Root: 1-50000 Branch: 1-16666 Branch: 16667-33333 Branch: 33334-50000 Leaf pages — actual data rows, in key order

    A seek walks root → branch → leaf, typically just 3-4 page reads even against a table with millions of rows — this is the entire reason indexes matter: it turns “read every row” into “read a handful of pages,” a logarithmic rather than linear cost as the table grows.

    Clustered vs Nonclustered

    Clustered Nonclustered
    Leaf level contains The actual data rows Key + pointer back to clustered key
    Per table At most one Many allowed
    Created by default via PRIMARY KEY (Fundamentals Ch.5) Nothing — explicit CREATE INDEX

    This is worth internalizing precisely: a clustered index doesn’t sit “alongside” the table — for a clustered table, the table is the index. There’s no separate copy of the data; the rows are physically stored in clustered-key order. A nonclustered index, by contrast, is a genuinely separate structure, small and narrow, that only stores its key columns plus a pointer back.

    The Key Lookup Problem

    A query that filters on a nonclustered index’s column but selects other columns not in that index requires a key lookup — jumping from the nonclustered leaf back to the clustered index to fetch the rest. For a handful of rows this is cheap; for a large result set, SQL Server often abandons the index entirely and scans the whole table, because thousands of individual lookups cost more than one sequential scan.

    -- Confirm this tipping-point behavior yourself
    CREATE NONCLUSTERED INDEX IX_OrderLog_Status ON dbo.OrderLog (order_status);
    
    -- Selective (few matching rows): optimizer uses the index + key lookups
    SELECT * FROM dbo.OrderLog WHERE order_status = 'cancelled'; -- rare status, few rows
    
    -- Unselective (most rows match): optimizer likely abandons the index for a scan
    SELECT * FROM dbo.OrderLog WHERE order_status = 'completed'; -- common status, most rows
    -- Compare the two actual execution plans (Ctrl+M) to see the optimizer's choice change
    Practice tip: Run both queries above with the actual execution plan visible, and hover over each Seek/Scan operator to read its estimated row count and cost percentage. Seeing the optimizer switch strategies based purely on how selective the filter is — not on anything about the index itself — is the single most useful intuition this lesson can give you before Chapter 3’s execution plan reading lesson goes deeper.

    Enjoyed this?

    Subscribe to get every new SQL Server lesson as soon as it’s published, and share it with a developer who’d find it useful.

    📡 Subscribe via RSS  | 
    Share on X  | 
    Share on LinkedIn  | 
    Share on Facebook

    Want the full structured course with quizzes, projects, and 10+ exercises per chapter? Check out SQL Server for Developers & DBAs, coming soon on this site.

  • Module 2 Exercises: SQL Server Indexing Strategy Labs (10 Hands-On Exercises)

    Module 2 Exercises: SQL Server Indexing Strategy Labs

    5 guided labs, 3 challenge scenarios, and 2 break-it labs.

    Guided Labs

    Guided1. Query sys.dm_db_missing_index_details and rank results by avg_user_impact × user_seeks.

    SELECT d.statement, d.equality_columns, d.included_columns, s.avg_user_impact, s.user_seeks
    FROM sys.dm_db_missing_index_details d
    JOIN sys.dm_db_missing_index_groups g ON d.index_handle = g.index_handle
    JOIN sys.dm_db_missing_index_group_stats s ON g.index_group_handle = s.group_handle
    ORDER BY s.avg_user_impact * s.user_seeks DESC;
    Guided2. Find every index in a test database with zero reads but nonzero writes.

    SELECT OBJECT_NAME(s.object_id) AS table_name, i.name
    FROM sys.dm_db_index_usage_stats s JOIN sys.indexes i ON i.object_id=s.object_id AND i.index_id=s.index_id
    WHERE s.user_seeks=0 AND s.user_scans=0 AND s.user_lookups=0 AND s.user_updates>0;
    Guided3. Measure fragmentation on all indexes in a test database and sort descending.

    SELECT OBJECT_NAME(ips.object_id) AS tbl, i.name, ips.avg_fragmentation_in_percent
    FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ips
    JOIN sys.indexes i ON i.object_id=ips.object_id AND i.index_id=ips.index_id
    ORDER BY ips.avg_fragmentation_in_percent DESC;
    Guided4. Check statistics staleness on a table you’ve recently bulk-loaded into.

    SELECT s.name, sp.last_updated, sp.modification_counter
    FROM sys.stats s CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) sp
    WHERE s.object_id = OBJECT_ID('YourTableName');
    Guided5. Create a clustered columnstore index on a wide, append-heavy “fact-style” test table and compare SELECT SUM(…) query duration before and after.

    Challenge Scenarios

    Challenge6. A table has 12 nonclustered indexes and insert performance has degraded badly over a year. Using this module’s DMVs, design a plan to identify which indexes are safe to drop.
    Challenge7. A weekly maintenance job rebuilds every index regardless of fragmentation, taking 6 hours and causing blocking. Redesign the job using the evidence-first thresholds from this module.
    Challenge8. A reporting query aggregating 50 million rows for a monthly dashboard takes 40 minutes on a rowstore table. Propose a columnstore-based redesign and explain why it would help, referencing batch mode.

    Break-It Labs

    Break-It9. Deliberately create heavy fragmentation: insert 50,000 rows in random GUID order into a table with a GUID clustered key, measure fragmentation, then fix it with REBUILD and re-measure.
    Break-It10. Deliberately induce a bad plan from stale statistics: disable auto-update statistics on a test table, bulk-load 10x its original row count, run a previously-fast query and observe the degraded plan, then manually UPDATE STATISTICS and confirm the plan improves.

    Enjoyed this?

    Subscribe to get every new lesson as soon as it’s published, and share it with a developer who’d find it useful.

    📡 Subscribe via RSS  | 
    Share on X  | 
    Share on LinkedIn  | 
    Share on Facebook

    Want the full structured course with quizzes and hands-on labs? Check out SQL Server Performance Tuning, coming soon on this site.

  • Module 2 Quiz: SQL Server Indexing Strategy (10 Questions)

    Module 2 Quiz: SQL Server Indexing Strategy

    10 questions covering index decision-making, fragmentation, statistics, and columnstore — including the classic “always rebuild” misconception.

    1. What does sys.dm_db_missing_index_details actually represent?

    A) Indexes that were deleted
    B) Indexes the optimizer logged wishing existed for past queries
    C) Corrupted indexes
    D) A list of all indexes on the server

    Show Answer

    Answer: B

    It’s a hypothesis generator based on what the optimizer wanted, not a guaranteed-correct instruction — always verify against real query patterns first.

    2. Why might an index with high user_seeks still be a bad index to keep?

    A) This is never the case
    B) If its write-maintenance cost on a heavily-written table outweighs the read benefit
    C) Seeks are always bad
    D) High seeks always mean the index is perfect

    Show Answer

    Answer: B

    Every index decision is a trade-off — reads must be weighed against write cost on the same table, not evaluated in isolation.

    3. At what fragmentation level does REORGANIZE typically become the recommended action?

    A) 0-5%
    B) 5-30%
    C) Always, regardless of level
    D) Only above 90%

    Show Answer

    Answer: B

    Below 5%, do nothing. 5-30%, REORGANIZE (lighter, always online). Above 30%, REBUILD is the standard threshold.

    4. “Gotcha”: Is “always rebuild indexes weekly regardless of fragmentation” a sound practice?

    A) Yes, it’s always correct
    B) No — it wastes CPU/IO on indexes that were never fragmented enough to matter
    C) Rebuilding has no cost
    D) Fragmentation is irrelevant to performance

    Show Answer

    Answer: B

    This is one of the most common DBA misconceptions — measure fragmentation first (sys.dm_db_index_physical_stats), don’t schedule blind maintenance.

    5. A small, rarely-scanned table shows 60% fragmentation. How much does this typically matter?

    A) Critical, fix immediately
    B) Usually very little — fragmentation mainly affects large tables read via range scans
    C) It always causes corruption
    D) It doubles storage cost

    Show Answer

    Answer: B

    A tiny table accessed via seeks is largely unaffected by fragmentation — context (table size, access pattern) always matters more than the raw percentage.

    6. What typically triggers SQL Server’s automatic statistics update, by default?

    A) Every single INSERT
    B) Roughly 20% of rows changing (with newer granular thresholds for large tables)
    C) Never automatically
    D) Only on server restart

    Show Answer

    Answer: B

    The classic threshold is ~20% row modification; recent SQL Server versions added more granular auto-update behavior for very large tables.

    7. A query suddenly gets a bad plan right after a huge bulk load. What’s the most likely first suspect?

    A) Stale statistics not yet reflecting the new data volume
    B) Corrupted transaction log
    C) A hardware failure
    D) Buffer pool being too large

    Show Answer

    Answer: A

    Bulk loads change row counts dramatically; if auto-update hasn’t caught up, the optimizer estimates against outdated statistics — check sys.dm_db_stats_properties.

    8. What’s the core structural difference between rowstore and columnstore indexes?

    A) No real difference
    B) Rowstore stores full rows together; columnstore stores each column together, compressed
    C) Columnstore is just a faster rowstore
    D) Rowstore is only for backups

    Show Answer

    Answer: B

    This structural difference is exactly why columnstore compresses better and scans faster for analytical aggregation, but is worse for single-row OLTP lookups.

    9. What execution mode do columnstore index queries typically use that boosts performance on large scans?

    A) Row mode
    B) Batch mode — processing ~900 rows per operator call
    C) Single-threaded mode only
    D) There’s no special execution mode

    Show Answer

    Answer: B

    Batch mode dramatically reduces per-row CPU overhead for large aggregations compared to traditional row-by-row execution.

    10. Why is a columnstore index usually a poor primary index choice for an OLTP order-lookup table?

    A) Columnstore is always slower than rowstore
    B) It’s optimized for bulk scan/aggregate patterns, not frequent single-row point lookups/updates
    C) Columnstore can’t store integers
    D) It’s deprecated

    Show Answer

    Answer: B

    “Fetch order #4471” is exactly the point-lookup pattern columnstore is not designed for — reserve it for fact tables and reporting/analytical workloads.


    Enjoyed this?

    Subscribe to get every new lesson as soon as it’s published, and share it with a developer who’d find it useful.

    📡 Subscribe via RSS  | 
    Share on X  | 
    Share on LinkedIn  | 
    Share on Facebook

    Want the full structured course with quizzes and hands-on labs? Check out SQL Server Performance Tuning, coming soon on this site.