Chapter 11 · Index-Aware Logical and Physical Design

How Indexes Change Physical Design

Understand how indexes reshape physical database design, access paths, constraints, clustering decisions, and the practical relationship between logical schemas and workload performance.

Beginner65–90 minutesIndex fundamentals + physical designLast reviewed: August 2026

Learning outcomes

Indexes are often introduced as a performance feature added after schema design. In production systems, that is incomplete. Indexes influence how tables are physically accessed, which constraints are practical, how write-heavy workloads behave, and which logical designs remain operationally affordable. Good index design begins from the workload and from the invariants the database must protect.

01

Explain the difference between logical schema design and physical access-path design.

02

Understand how B-tree-style indexes reduce search work.

03

Connect indexes to primary keys, unique constraints, foreign keys, joins, and ordering.

04

Recognize that every added index changes write and storage behavior.

Logical design versus physical design

A logical schema answers questions such as:

  • What entities exist?
  • What is the row grain of each relation?
  • What are the keys and functional dependencies?
  • Which relationships and constraints must hold?

Physical design answers different questions:

  • How will rows be located efficiently?
  • Which indexes support the dominant access patterns?
  • What sort orders or clustering choices matter?
  • How much write and storage overhead is acceptable?

Why indexes exist

Without a useful index, a query may need to inspect a large fraction of a table. An index organizes selected key values so the engine can locate matching rows through a much smaller search structure.

sql · example
SELECT *FROM work_orderWHERE work_order_id = 84217;

A primary-key index makes this lookup fundamentally different from scanning every WorkOrder row.

A simplified B-tree intuition

Many relational databases use B-tree or B+-tree-family structures for ordinary indexes. Values are maintained in sorted order through internal pages and leaf pages. Searches descend from root to leaf instead of checking rows sequentially.

model · example
             [root]            /      \       [1..500]   [501..]        /   \       /   \      leaf leaf   leaf leaf

The exact implementation differs by database engine, but the design intuition is stable: sorted index structures make equality, ordered traversal, and many range predicates efficient.

Indexes do not change relational meaning

Adding an index does not change the logical result of:

sql · example
SELECT ...FROM work_orderWHERE asset_id = ?;

It changes the possible execution plan. This separation is powerful: you can often tune access without changing the logical schema or application query contract.

Primary keys and indexes

Most relational engines implement primary-key enforcement using a unique index or an equivalent physical structure. The logical constraint is “key values are unique and non-null”; the index is one implementation mechanism.

Important distinction

A primary key is a logical integrity rule. The index is a physical structure that commonly helps enforce and access that key.

Unique constraints

WorkshopHub may require:

model · example
UNIQUE (employee_number)UNIQUE (sku)UNIQUE (manufacturer_id, serial_number)

These rules usually imply unique indexes. Therefore integrity requirements already influence physical design.

Foreign keys and indexes

A foreign key does not universally imply that the referencing column is automatically indexed. Yet many common queries need it:

sql · example
SELECT *FROM work_orderWHERE asset_id = ?;

and parent deletion/update checks may also benefit. Therefore child-side foreign-key columns are common index candidates.

Indexing joins

Consider:

sql · example
SELECT wo.work_order_id, a.serial_numberFROM work_order AS woJOIN asset AS a  ON a.asset_id = wo.asset_idWHERE a.customer_id = ?;

Useful indexes might include Asset.customer_id and WorkOrder.asset_id depending on row counts, selectivity, and the optimizer's chosen join strategy.

Indexes and ORDER BY

An index can sometimes deliver rows in the order a query requests:

sql · example
SELECT *FROM work_orderWHERE asset_id = ?ORDER BY opened_at DESC;

A composite index beginning with asset_id and then opened_at may support both filtering and ordering.

Indexes and range predicates

sql · example
SELECT *FROM work_orderWHERE opened_at >= :start  AND opened_at < :end;

Sorted indexes are naturally suited to ranges because the engine can seek to the beginning and scan only the relevant portion.

Indexes and normalization

Developers sometimes denormalize because a normalized query joins several tables. Before duplicating data, first ask whether the logical design is correct and whether appropriate indexes make the normalized access path fast enough.

Indexes are not free

Every inserted, deleted, or relevant updated row may require changes to multiple index structures. This creates:

  • additional writes;
  • more storage;
  • more memory/cache pressure;
  • longer bulk-load or migration time;
  • maintenance work as indexes grow.

Physical design is workload-specific

A schema serving:

  • 100 writes/second and a few point reads;
  • mostly dashboard reads;
  • large time-range reports;
  • batch imports;

may need very different index portfolios even if the logical tables are identical.

WorkshopHub initial index map

TableLikely baseline indexReason
CustomerPK(customer_id)Identity lookup.
AssetPK(asset_id), customer_id, unique manufacturer+serialOwnership lookup and business uniqueness.
WorkOrderPK(work_order_id), asset_idAsset history.
AssignmentPK(assignment_id), work_order_id, technician_idOrder and technician views.
PartPK(part_id), UNIQUE(sku)Internal and business lookup.
PartUsagework_order_id, part_idOrder detail and part usage history.

Explain plans belong to physical design

Do not assume the optimizer uses an index merely because it exists. Query planners estimate costs using statistics, row counts, data distribution, and available access paths. Always inspect actual plans for important queries.

Practice: identify index roles

Three WorkshopHub queries

  1. Find one Part by SKU.
  2. List all WorkOrders for an Asset.
  3. List active Assignments for a Technician ordered by start time.

Which columns are natural index candidates?

Review answer

SKU needs a unique index because it is a business key. WorkOrder.asset_id is a strong candidate for asset history. For active assignments by technician and start time, a composite/partial index involving technician_id, active-state predicate, and started_at may be appropriate; later lessons develop the exact design.

Summary and next lesson

Indexes are part of physical database design, not a late-stage decoration. They support key enforcement, joins, filters, ranges, and ordering while imposing real write and storage costs. The next lesson focuses on selectivity and cardinality so index candidates are chosen from evidence rather than intuition.

References

  • PostgreSQL documentation on index types and query planning.
  • Markus Winand, SQL Performance Explained.
  • Martin Kleppmann, Designing Data-Intensive Applications.

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.