B-tree and composite indexes
B-tree and Composite Indexes
Learn how B-tree indexes organize searchable keys, how composite indexes support multi-column filters and ordering, why column order matters, how indexes affect reads and writes, and how to design indexes from real query patterns.
Introduction
As a database table grows, scanning every row for each query becomes increasingly expensive. An index provides an additional data structure that helps the database locate qualifying rows without examining the complete table.
B-tree indexes are widely used for equality searches, range conditions, ordered retrieval, prefix matching, uniqueness enforcement, and join operations.
A composite index contains more than one indexed column. It is useful when important queries filter, join, or order by a specific combination of columns.
Core idea: An index should be designed from the query workload, not from the table definition alone. Choose indexed columns and their order according to filters, joins, ranges, ordering, uniqueness, selectivity, and expected write cost.
In your System Design curriculum, B-tree and Composite Indexes is Topic 5.7 under Relational Data Modeling & SQL. It follows isolation and precedes query plans and connection pools.
Prerequisites
| # | Prerequisite | Why It Is Needed |
|---|---|---|
| 1 | Relational tables | Indexes provide access paths to rows stored in relational tables. |
| 2 | Keys and constraints | Primary and unique constraints are commonly supported by unique indexes. |
| 3 | SQL filtering | Indexes are selected according to predicates in WHERE and JOIN clauses. |
| 4 | Sorting and pagination | Suitable indexes can support ORDER BY and cursor-pagination queries. |
| 5 | Transactions and isolation | Index maintenance participates in writes, locking, versioning, and transactions. |
| 6 | Basic performance measurement | Index decisions should be validated using query plans and representative workloads. |
What Is a Database Index?
A database index is a separate physical data structure containing selected key values and references that help the database locate table rows.
Without a suitable index:
Query
|
v
Examine many or all table rows
|
v
Evaluate the filter
|
v
Return matching rows
With a suitable index:
Query
|
v
Navigate indexed keys
|
v
Locate qualifying entries
|
v
Retrieve matching rows
An index does not replace the table. It provides another route through which the database can access data.
What Is a B-tree?
A B-tree is a balanced, ordered tree structure used by many database systems for indexes.
Rather than storing one key and one child per node, a B-tree node can contain several ordered keys and references. This keeps the tree relatively shallow and reduces the number of storage pages required during navigation.
[40 | 80]
/ | \
/ | \
v v v
[10 | 20 | 30] [50 | 70] [90 | 100 | 120]
The diagram is simplified. Real database indexes are stored in fixed-size pages and contain database-specific metadata, key values, and references.
B-tree Index Structure
A B-tree index generally contains:
- A root page
- Zero or more intermediate levels
- Leaf pages
- Ordered key values
- References to child pages or table rows
Root page
|
+-- Intermediate page
| |
| +-- Leaf page
| +-- Leaf page
|
+-- Intermediate page
|
+-- Leaf page
+-- Leaf page
Root Page
The root is the starting point for index navigation.
Intermediate Pages
Intermediate pages guide navigation toward the leaf page containing the required key range.
Leaf Pages
Leaf pages contain ordered index entries. The exact leaf representation depends on whether the index is clustered, nonclustered, covering, or implemented through another database-specific design.
Why Balanced Trees Matter
A balanced tree keeps leaf pages at a consistent navigation depth. This avoids the progressively longer search paths that can occur in an unbalanced search tree.
Balanced tree:
Root
|
+-- Branch
| |
| +-- Leaf
|
+-- Branch
|
+-- Leaf
Comparable navigation depth
for indexed keys
B-tree height depends on page size, key width, entry overhead, row count, fill characteristics, and the database implementation.
Equality Search
A B-tree index can efficiently support equality conditions on its indexed key.
SELECT
customer_id,
customer_number,
display_name
FROM customers
WHERE customer_number = :customer_number;
CREATE UNIQUE INDEX ix_customers_number
ON customers
(
customer_number
);
The database can navigate to the relevant indexed value instead of scanning the entire customer table.
Range Search
Because B-tree keys are ordered, the index can support range conditions.
SELECT
order_id,
order_number,
ordered_at
FROM orders
WHERE ordered_at >= :start_time
AND ordered_at < :end_time
ORDER BY
ordered_at;
CREATE INDEX ix_orders_ordered_at
ON orders
(
ordered_at
);
After locating the beginning of the range, the database can read consecutive index entries until the range ends.
Supporting ORDER BY
An index can provide rows in an order compatible with an ORDER BY clause, potentially reducing or avoiding a separate sorting operation.
SELECT
order_id,
order_status,
ordered_at
FROM orders
WHERE customer_id = :customer_id
ORDER BY
ordered_at DESC,
order_id DESC;
CREATE INDEX ix_orders_customer_ordered_id
ON orders
(
customer_id,
ordered_at DESC,
order_id DESC
);
The database can navigate matching customer entries in the required ordering when the index and query are compatible.
Supporting Joins
Indexes on columns participating in joins can help the database locate related rows.
SELECT
o.order_number,
ol.line_number,
ol.product_id,
ol.quantity
FROM orders AS o
INNER JOIN order_lines AS ol
ON ol.order_id = o.order_id
WHERE
o.order_id = :order_id;
CREATE INDEX ix_order_lines_order
ON order_lines
(
order_id
);
Some databases do not automatically create an index on every foreign-key column. Foreign-key indexes should be selected according to joins, parent modifications, table size, and workload evidence.
What Is a Composite Index?
A composite index contains two or more key columns.
CREATE INDEX ix_orders_customer_status_date
ON orders
(
customer_id,
order_status,
ordered_at
);
The combined key is ordered first by customer_id, then by
order_status within each customer, and finally by
ordered_at within each customer-and-status group.
Composite ordering:
customer_id
|
+-- order_status
|
+-- ordered_at
Why Composite Column Order Matters
A composite index does not provide identical access for every permutation of its columns.
CREATE INDEX ix_orders_customer_status_date
ON orders
(
customer_id,
order_status,
ordered_at
);
This index is organized primarily by customer. Entries for the same customer are then organized by status and date.
It can be suitable for:
WHERE customer_id = :customer_id
WHERE customer_id = :customer_id
AND order_status = :status
WHERE customer_id = :customer_id
AND order_status = :status
AND ordered_at >= :start_time
It is generally less directly suitable for a query filtering only by
order_status or only by ordered_at, because the
leading customer column is absent.
Column-order rule: A composite index is ordered from left to right. Design the leading columns around the important query patterns, not the visual order of columns in the table.
Leftmost-prefix Principle
A composite B-tree index is commonly most useful when a query constrains a leading sequence of its key columns.
Index:
(tenant_id, customer_id, ordered_at)
Leading prefixes:
tenant_id
tenant_id, customer_id
tenant_id, customer_id, ordered_at
Compatible Query
SELECT
order_id,
ordered_at
FROM orders
WHERE tenant_id = :tenant_id
AND customer_id = :customer_id
ORDER BY
ordered_at DESC;
Missing Leading Column
SELECT
order_id,
ordered_at
FROM orders
WHERE customer_id = :customer_id;
The database may be unable to seek efficiently by customer using this index
when tenant_id is not supplied. The optimizer can still choose
an index scan or another access strategy, depending on the engine and
statistics.
Equality, Range, and Sort Columns
A practical composite-index design often considers:
- Columns used by equality conditions
- Columns used by range conditions
- Columns used for ordering
- Columns required to complete the result
Query Example
SELECT
order_id,
order_number,
ordered_at,
total_amount
FROM orders
WHERE tenant_id = :tenant_id
AND customer_id = :customer_id
AND order_status = :status
AND ordered_at >= :start_time
ORDER BY
ordered_at DESC,
order_id DESC;
Candidate Index
CREATE INDEX ix_orders_tenant_customer_status_date_id
ON orders
(
tenant_id,
customer_id,
order_status,
ordered_at DESC,
order_id DESC
);
The query uses equality conditions on tenant, customer, and status, followed by a date range and compatible ordering.
Range Conditions and Later Columns
A range condition can reduce how effectively later key columns narrow index navigation.
CREATE INDEX ix_orders_tenant_date_status
ON orders
(
tenant_id,
ordered_at,
order_status
);
SELECT
order_id
FROM orders
WHERE tenant_id = :tenant_id
AND ordered_at >= :start_time
AND order_status = :status;
The index can navigate the tenant and date range. The status condition can still be evaluated from the index or rows, but it might not narrow the initial range as effectively as it would before the range column.
Exact behaviour depends on the database optimizer and index implementation. Validate the execution plan.
Selectivity
Selectivity describes how strongly a condition reduces the number of matching rows.
Highly selective condition:
customer_number = one unique value
Low-selectivity condition:
customer_status = active
when most customers are active
A low-selectivity index is not automatically useless. It can still be valuable when combined with tenant, category, date, ordering, or covering requirements.
Selectivity rule: Selectivity matters, but it is not the only factor in column order. Equality, range, ordering, joins, grouping, and the complete workload also matter.
Tenant-first Composite Indexes
Multi-tenant queries commonly include a tenant condition.
SELECT
order_id,
order_number,
order_status
FROM orders
WHERE tenant_id = :trusted_tenant_id
AND customer_id = :customer_id;
CREATE INDEX ix_orders_tenant_customer
ON orders
(
tenant_id,
customer_id
);
Including the tenant key can support tenant-scoped access patterns and keep matching entries adjacent.
Tenant scope must come from trusted application identity and authorization context. An index does not enforce authorization by itself.
Sort Direction
Some database systems allow ascending and descending directions in index definitions.
CREATE INDEX ix_orders_customer_date
ON orders
(
customer_id ASC,
ordered_at DESC,
order_id DESC
);
Whether one index can satisfy forward, reverse, or mixed-direction ordering depends on the database platform and query shape.
Composite Indexes for Cursor Pagination
Cursor pagination commonly uses a deterministic composite ordering.
SELECT
order_id,
order_status,
ordered_at
FROM orders
WHERE tenant_id = :tenant_id
AND
(
ordered_at < :cursor_ordered_at
OR
(
ordered_at = :cursor_ordered_at
AND order_id < :cursor_order_id
)
)
ORDER BY
ordered_at DESC,
order_id DESC
LIMIT 21;
Supporting Index
CREATE INDEX ix_orders_tenant_date_id
ON orders
(
tenant_id,
ordered_at DESC,
order_id DESC
);
The unique order ID acts as a deterministic tiebreaker when several orders have the same timestamp.
Covering Indexes
A covering index contains the information required to evaluate and return a query without retrieving additional data from the underlying table.
Query
SELECT
order_id,
order_status,
ordered_at,
total_amount
FROM orders
WHERE customer_id = :customer_id
ORDER BY
ordered_at DESC;
Covering-index Concept
Key columns:
customer_id
ordered_at
order_id
Included or stored output columns:
order_status
total_amount
Some databases support non-key included columns through an
INCLUDE clause.
CREATE INDEX ix_orders_customer_date
ON orders
(
customer_id,
ordered_at DESC,
order_id
)
INCLUDE
(
order_status,
total_amount
);
Included-column syntax and behaviour are database-specific. In systems without this feature, all indexed columns can become key columns, increasing key width and maintenance cost.
Key Columns vs Included Columns
| Column Type | Primary Purpose |
|---|---|
| Index key column | Defines index ordering and supports navigation, filtering, and sorting |
| Included column | Provides additional output data without participating in key ordering |
Do not place every selected column into every index. Wide indexes increase storage, memory consumption, write work, logging, and maintenance cost.
Unique Indexes
A unique index prevents duplicate indexed key values according to the database's null and collation semantics.
CREATE UNIQUE INDEX ux_customers_tenant_number
ON customers
(
tenant_id,
customer_number
);
This design enforces uniqueness of the customer number inside a tenant.
Prefer a PRIMARY KEY or UNIQUE constraint when the requirement represents a logical integrity rule. The database can implement that constraint using a unique index.
Filtered and Partial Indexes
Some databases support indexing only rows that satisfy a predicate.
CREATE INDEX ix_orders_active_customer
ON orders
(
customer_id,
ordered_at DESC
)
WHERE order_status IN
(
'pending',
'confirmed',
'processing'
);
A filtered or partial index can be smaller and more selective when important queries repeatedly access a stable subset of rows.
Syntax, supported predicates, parameter behavior, and optimizer matching are platform-specific.
Expression and Computed-column Indexes
Some databases can index an expression or a computed column.
CREATE INDEX ix_users_lower_email
ON users
(
LOWER(email_address)
);
A query must use a compatible expression for the optimizer to consider the index.
SELECT
user_id,
email_address
FROM users
WHERE LOWER(email_address) =
LOWER(:email_address);
Expression-index support and function-determinism requirements differ by database.
Prefix Search
A B-tree index can often support a string prefix search.
SELECT
product_id,
product_name
FROM products
WHERE product_name LIKE 'Key%';
A pattern beginning with an unrestricted wildcard is usually less suitable for ordinary B-tree navigation.
SELECT
product_id,
product_name
FROM products
WHERE product_name LIKE '%board%';
Substring and linguistic search can require full-text or specialized search indexes rather than an ordinary B-tree.
Non-sargable Predicates
A predicate is commonly called sargable when the database can use it as a search argument for an index access path.
SELECT
order_id
FROM orders
WHERE DATE(ordered_at) = :order_date;
SELECT
order_id
FROM orders
WHERE ordered_at >= :day_start
AND ordered_at < :next_day_start;
The range expression is also clearer about timestamp boundaries and can
align with an ordinary index on ordered_at.
Implicit Conversion
Comparing incompatible types can introduce conversion and prevent efficient index use.
Indexed column:
customer_number VARCHAR
Parameter supplied as:
Integer
Indexed column:
customer_number VARCHAR
Parameter supplied as:
String using matching semantics
Parameter types, column types, collations, and comparison rules should align with the database schema.
Clustered and Nonclustered Indexes
Some database systems distinguish clustered and nonclustered indexes.
| Index Type | General Concept |
|---|---|
| Clustered index | Defines or strongly influences the physical organization of table data according to the database implementation |
| Nonclustered index | Stores a separate ordered structure with references to table rows |
Terminology and storage behavior vary significantly among database platforms. Learn the implementation used by the selected engine rather than assuming that all B-tree indexes behave identically.
Index Write Cost
Every applicable INSERT, UPDATE, and DELETE can require index maintenance.
INSERT row
|
+-- Update table data
|
+-- Update primary-key index
|
+-- Update unique indexes
|
+-- Update secondary indexes
|
+-- Write transaction-log records
Additional indexes can increase:
- Insert latency
- Update latency
- Delete latency
- Transaction-log volume
- Storage consumption
- Memory pressure
- Backup size
- Maintenance time
Trade-off rule: Every index should justify its ongoing write, storage, memory, and operational cost through an integrity rule or important query benefit.
Page Splits
When an index page lacks enough free space for a new entry, the database can split or reorganize pages according to its implementation.
Full leaf page:
[10] [20] [30] [40]
Insert:
25
Possible result:
Page A:
[10] [20]
Page B:
[25] [30] [40]
Splits can increase writes, logging, and fragmentation. The actual impact depends on insert order, key distribution, page management, fill settings, and database implementation.
Sequential and Random Keys
A sequential key commonly directs new entries toward one end of an index, while a randomly distributed key can place inserts throughout the index.
| Key Pattern | Potential Benefit | Potential Consideration |
|---|---|---|
| Sequential | Predictable insertion location | Can create a hot insertion page under heavy concurrency |
| Random | Can distribute insert locations | Can increase page churn, fragmentation, and key width |
The correct identifier strategy depends on workload, database engine, distribution requirements, privacy needs, and operational measurement.
Key Width
Wider index keys reduce the number of entries that fit on a page and can increase memory, storage, I/O, and comparison cost.
Narrow key:
BIGINT
Wider composite key:
tenant_id
+
customer_number
+
order_timestamp
+
long_status_text
Keep index keys focused. Place output-only columns in an included section where supported instead of expanding the ordered key unnecessarily.
Too Many Indexes
Indexing every column is not a valid optimization strategy.
Index every column.
Index every column pair.
Index every possible ORDER BY.
Never remove an index.
Identify important queries.
Create candidate indexes.
Measure query plans and latency.
Measure write impact.
Remove duplicate or unused indexes carefully.
Overlapping Indexes
CREATE INDEX ix_orders_customer
ON orders
(
customer_id
);
CREATE INDEX ix_orders_customer_date
ON orders
(
customer_id,
ordered_at
);
CREATE INDEX ix_orders_customer_date_status
ON orders
(
customer_id,
ordered_at,
order_status
);
These indexes overlap. That does not automatically mean that the shorter indexes are redundant, because key width, covering needs, query shapes, and optimizer choices can differ.
Review usage and plans before removing an index.
Statistics and Cardinality Estimates
The optimizer uses statistics to estimate how many rows a predicate or join will return.
Optimizer estimates:
- Rows matching tenant
- Rows matching status
- Rows inside date range
- Join cardinality
- Expected lookup count
Inaccurate estimates can cause the optimizer to choose an inefficient scan, join order, or lookup strategy even when useful indexes exist.
Index Seek vs Index Scan
| Access Pattern | General Meaning |
|---|---|
| Index seek | Navigate to a qualifying key or key range |
| Index scan | Examine a larger portion or all of the index |
| Table scan | Examine table data without a sufficiently selective index access path |
| Key or row lookup | Retrieve additional table columns after locating index entries |
A scan is not automatically bad. Scanning can be efficient when a query needs a large portion of the table or when the table is small.
Validate with Query Plans
EXPLAIN
SELECT
order_id,
order_status,
ordered_at
FROM orders
WHERE tenant_id = :tenant_id
AND customer_id = :customer_id
ORDER BY
ordered_at DESC;
EXPLAIN syntax and plan terminology differ by database platform.
Inspect whether the plan shows:
- The expected index
- A seek or scan
- Estimated and actual row counts
- Residual filtering
- Explicit sorting
- Repeated lookups
- Join algorithms
- Memory or spill indicators
Composite-index Design Workflow
- Capture the complete important query.
- Identify equality predicates.
- Identify range predicates.
- Identify join columns.
- Identify grouping and ordering.
- Identify required result columns.
- Identify tenant or authorization scope.
- Estimate result selectivity.
- Create the narrowest useful candidate index.
- Inspect the execution plan.
- Measure reads, latency, and returned rows.
- Measure write and storage impact.
- Test with representative data distribution.
- Review overlapping indexes.
- Monitor the index after deployment.
Query-to-index Examples
Customer's Recent Orders
SELECT
order_id,
order_status,
ordered_at
FROM orders
WHERE tenant_id = :tenant_id
AND customer_id = :customer_id
ORDER BY
ordered_at DESC,
order_id DESC
LIMIT 20;
CREATE INDEX ix_orders_tenant_customer_date_id
ON orders
(
tenant_id,
customer_id,
ordered_at DESC,
order_id DESC
);
Pending Orders by Date
SELECT
order_id,
customer_id,
ordered_at
FROM orders
WHERE tenant_id = :tenant_id
AND order_status = 'pending'
AND ordered_at < :cutoff
ORDER BY
ordered_at;
CREATE INDEX ix_orders_tenant_status_date
ON orders
(
tenant_id,
order_status,
ordered_at
);
Course Lessons
SELECT
lesson_id,
lesson_title,
display_order
FROM lessons
WHERE chapter_id = :chapter_id
ORDER BY
display_order;
CREATE UNIQUE INDEX ux_lessons_chapter_order
ON lessons
(
chapter_id,
display_order
);
The unique index can support retrieval order and enforce that a chapter cannot contain two lessons at the same display position.
Indexes and Security
Indexes do not replace authentication, authorization, row filtering, or tenant isolation.
SELECT
order_id,
order_status
FROM orders
WHERE tenant_id = :trusted_tenant_id
AND order_id = :order_id;
The index can accelerate the authorized query, but trusted tenant filtering and resource-level authorization still must be enforced.
Indexes can also duplicate sensitive key values in a separate physical structure. Database access, backups, replicas, diagnostics, and disposal procedures must protect indexed data appropriately.
Index Maintenance
Index maintenance requirements depend on database engine, write workload, page utilization, fragmentation, and query behaviour.
Operational activities can include:
- Updating statistics
- Rebuilding or reorganizing selected indexes
- Removing verified unused indexes
- Consolidating overlapping indexes
- Monitoring index size
- Monitoring page splits
- Validating corruption checks
- Reviewing indexes after query changes
Avoid applying a fixed maintenance routine to every index without measuring the database platform and workload.
Index Observability
Useful index metrics include:
- Index seeks and scans
- Lookup count
- Index size
- Page count
- Page-split activity
- Write-maintenance cost
- Index usage by query
- Unused-index evidence
- Duplicate or overlapping-index evidence
- Estimated versus actual row counts
- Sort and spill activity
- Query latency before and after index changes
Common Indexing Mistakes
Indexing Every Column
Excessive indexes increase write, storage, logging, memory, and maintenance costs.
Ignoring Composite Column Order
An index beginning with one column does not provide the same navigation as an index beginning with another.
Choosing the Most Selective Column First Automatically
Selectivity is important, but equality, ranges, sort order, grouping, tenancy, and the complete query workload also influence column order.
Ignoring the Leftmost Prefix
Queries omitting leading composite-key columns might not receive an efficient seek from that index.
Placing a Range Column Too Early
A broad range can reduce how effectively later columns narrow the index navigation.
Creating Very Wide Covering Indexes
Covering every query can create large indexes and significant write amplification.
Using Functions on Indexed Columns Carelessly
Applying a function to the indexed column can prevent ordinary index navigation unless a compatible expression index exists.
Using Mismatched Data Types
Implicit conversions can increase work or prevent efficient index use.
Assuming Every Scan Is Bad
A scan can be the correct plan when the query retrieves a large portion of the data.
Assuming an Index Is Used Because It Exists
The optimizer can choose another access path based on estimated cost, statistics, predicates, and row counts.
Removing an Apparently Unused Index Too Quickly
An index can support infrequent critical jobs, constraints, reporting, or seasonal workloads.
Testing with Unrealistically Small Data
An optimizer can choose a table scan for a small test table even when an index becomes important at production scale.
Recommended Test Cases
| Test | Expected Evidence |
|---|---|
| Equality lookup | The plan uses an appropriate indexed access path |
| Range query | The database navigates the required key range |
| Composite leading prefix | The query uses the leading indexed columns effectively |
| Missing leading column | The resulting seek, scan, or alternative plan is documented |
| ORDER BY support | The plan shows whether an explicit sort is required |
| Cursor pagination | The index supports the filter, ordering, and unique tiebreaker |
| Covering query | The plan shows whether additional row lookups are avoided |
| Unique composite key | Duplicate business-key combinations are rejected |
| Prefix search | The supported prefix pattern uses the expected access path |
| Non-sargable predicate | The plan difference is recorded after query rewriting |
| Write benchmark | Insert and update cost is compared before and after the index |
| Representative scale | The plan is tested with realistic volume and data distribution |
B-tree and Composite-index Best Practices
Recommended Practices
- Design indexes from important query patterns.
- Use B-tree indexes for suitable equality, range, join, and ordering needs.
- Choose composite column order deliberately.
- Respect the leftmost-prefix behaviour of composite indexes.
- Place equality and range columns according to the complete query shape.
- Include a deterministic tiebreaker for cursor pagination.
- Consider tenant scope in multi-tenant indexes.
- Keep index keys as narrow as practical.
- Use included columns only for justified covering requirements.
- Use UNIQUE constraints or indexes for real business uniqueness.
- Use filtered or partial indexes for stable, important subsets where supported.
- Write sargable predicates.
- Align parameter and indexed-column data types.
- Measure selectivity and data distribution.
- Inspect estimated and actual execution plans.
- Test with representative production-scale data.
- Measure write cost before adding several indexes.
- Review overlapping and unused indexes carefully.
- Maintain statistics according to database and workload needs.
- Monitor index usage, size, scans, seeks, lookups, and page activity.
Practice Exercise
Design and validate indexes for the online learning platform developed in previous lessons.
Requirements
- List all enrollments for one learner ordered by enrollment date.
- List active learners for one course.
- Prevent duplicate learner-course enrollment.
- List chapters in course display order.
- List lessons in chapter display order.
- Retrieve recent quiz attempts for one learner and quiz.
- Support cursor pagination for quiz attempts.
- Find incomplete enrollments for a scheduled reminder job.
- Include the tenant key in organization-scoped queries.
- Create the narrowest useful candidate indexes.
- Capture the execution plan before each index.
- Create one index at a time.
- Capture the execution plan after each index.
- Measure read and write latency.
- Document overlapping or unnecessary indexes.
Candidate Enrollment Index
CREATE UNIQUE INDEX ux_enrollments_tenant_learner_course
ON enrollments
(
tenant_id,
learner_id,
course_id
);
Candidate Course-roster Index
CREATE INDEX ix_enrollments_tenant_course_status_learner
ON enrollments
(
tenant_id,
course_id,
enrollment_status,
learner_id
);
Candidate Quiz-attempt Cursor Index
CREATE INDEX ix_attempts_tenant_learner_quiz_submitted_id
ON quiz_attempts
(
tenant_id,
learner_id,
quiz_id,
submitted_at DESC,
attempt_id DESC
);
Index Review Template
| Query | Equality Columns | Range or Sort Columns | Candidate Index | Measured Result |
|---|---|---|---|---|
| Learner enrollments | Tenant and learner | Enrollment date | Record candidate | Record plan and latency |
| Course roster | Tenant, course, and status | Learner or enrollment date | Record candidate | Record plan and latency |
| Quiz-attempt history | Tenant, learner, and quiz | Submission time and attempt ID | Record candidate | Record plan and latency |
| Incomplete progress | Tenant and status | Last-activity date | Record candidate | Record plan and latency |
Frequently Asked Questions
What is a database index?
A database index is a separate data structure that helps the database locate and order qualifying rows.
What is a B-tree index?
A B-tree index is a balanced ordered tree containing searchable keys and references used to locate data.
Why are B-tree indexes useful for ranges?
Their keys are ordered, allowing the database to find the beginning of a range and read consecutive qualifying entries.
What is a composite index?
A composite index is an index whose ordered key contains two or more columns.
Why does composite-index column order matter?
The index is ordered from its first key column onward, so different column sequences support different navigation and ordering patterns.
What is the leftmost-prefix principle?
It means a composite index is commonly most useful when a query constrains a leading sequence of its key columns.
What is a covering index?
A covering index contains the information needed to evaluate and return a query without retrieving additional columns from the underlying table.
Does every foreign key need an index?
Not automatically. Evaluate child lookups, joins, parent modifications, table size, and measured workload before creating the index.
Is an index scan always bad?
No. A scan can be efficient when a query requires a large portion of the indexed data or when the table is small.
Can an index slow down writes?
Yes. Applicable indexes must be maintained during inserts, updates, and deletes.
Should the most selective column always come first?
No. Selectivity is one consideration. Equality predicates, ranges, ordering, joins, tenancy, and the overall workload also matter.
What comes after B-tree and composite indexes?
The next topic is query plans, followed by connection pools.
Key Takeaway
B-tree indexes provide ordered access to keys and can support equality searches, ranges, joins, sorting, uniqueness, and cursor pagination. Composite indexes organize data according to a specific left-to-right column sequence, so column order must reflect important query predicates and ordering. Design indexes from measured workloads, keep keys focused, use covering columns carefully, write sargable predicates, inspect query plans, test with realistic data, and balance every read improvement against additional write, storage, logging, and maintenance cost.