Keys & Integrity Constraints
How a relational engine enforces data correctness declaratively — keys, uniqueness, referential integrity, and row predicates checked on every write. Interviewers probe this to see whether you push invariants into the database or rely on racy application checks.
part ofRelational database conceptsoverview, primer and where to startread it →on this pageshowhide
explore
- Primary Keys in Practice4 questions
- Foreign Key Enforcement4 questions
- Referential Actions4 questions
- Unique Constraints & NULLs4 questions
- CHECK Constraints5 questions
- NOT NULL & DEFAULT3 questions
- Deferrable & Deferred Checking5 questions
- Unique Index vs Unique Constraint5 questions
- Adding Constraints to Live Tables5 questions
- Database vs Application Enforcement4 questions
- Constraint Costs on Writes5 questions
- BI Analystroleanchors this topic
- Java SDETroleanchors this topic
- QA Engineerroleanchors this topic
- SQLskillanchors this topic
- AI & Data Scientistrole
- AI Engineerrole
- Backend Developerrole
- Computer Scienceskill
- Cyber Security Expertrole
- Data Analystrole
- Data Engineerrole
- Forward Deployed Engineerrole
- Full Stack Developerrole
- Java Backend Developerrole
- Kotlin Backend Developerrole
- MLOps Engineerrole
- Machine Learning Engineerrole
- Server-Side Game Developerrole
- Software Architectrole
questions
page 2 of 2Rank the per-row write cost of a CHECK constraint, a UNIQUE constraint, and a FOREIGN KEY constraint on the same table, and explain what makes them differ by orders of magnitude.
basics
~20 sCHECK is cheapest: pure CPU on the row's own column values, no extra pages, no locks. UNIQUE costs an index probe plus an index write. FOREIGN KEY costs a probe into another table plus a lock on the parent row held to commit, so it is the only one that makes writers contend.
Compared with a plain non-unique index on the same column, what extra work does a unique index perform on every INSERT, and why can engines not defer or buffer that work the way they can for non-unique index maintenance?
basics
~20 sBoth write a leaf entry. The unique one must first read the key's position and prove no live duplicate exists, and if another uncommitted transaction inserted the same key it must wait for that transaction to finish. That read and that wait cannot be deferred.
Why can a CHECK constraint not enforce a rule such as 'at most one active row per customer' or 'this value must exist in another table', and what mechanisms do relational engines offer instead?
basics
~20 sA CHECK sees only the single row being written, so it cannot inspect other rows or other tables, and mainstream engines forbid subqueries in it. Cross-row rules need a different mechanism: a unique or partial unique index, a foreign key, an exclusion constraint, or a trigger.
Once a unique constraint is the mechanism preventing duplicate rows, how should the write path be structured around it — catching the constraint-violation error, or using an insert-if-absent statement — and what changes for the surrounding transaction?
basics
~20 sCatch the violation when a duplicate is genuinely an error the user should hear about; use an insert-if-absent statement when a duplicate is a normal outcome. Match on the constraint name, not the message text, and remember an error may abort the transaction unless you wrap the insert in a savepoint.
A batch job opens a transaction, issues SET CONSTRAINTS ALL DEFERRED, and then performs a few million writes. What operational consequences does that have — for error reporting, for concurrent sessions, and for the transaction's resource use?
basics
~20 sViolations now surface at COMMIT with a constraint name but no guilty statement, and the entire batch is lost. The engine queues one pending check per affected row, costing memory. Concurrent sessions see nothing invalid, but hold-open locks last longer.
You need to make an existing nullable column NOT NULL on a busy table of 200 million rows, with no maintenance window. Describe a rollout that avoids holding a long exclusive lock.
basics
~20 sStop the app writing NULLs, backfill existing NULLs in small batches, add CHECK (col IS NOT NULL) NOT VALID (brief lock), validate it under a weak lock, then SET NOT NULL — which PostgreSQL 12+ can do without rescanning because the validated CHECK proves it.
SQL Server lets you add a CHECK or FOREIGN KEY constraint WITH NOCHECK so existing rows are not verified. What state does that leave the constraint in, what does the query optimizer do differently as a result, and how would you find and repair such constraints later?
basics
~20 sThe constraint is enforced for new writes but marked untrusted, so the optimizer will not reason from it — losing simplifications like join elimination and predicate-based partition elimination. Fix by cleaning the data, then ALTER TABLE ... WITH CHECK CHECK CONSTRAINT to rescan and re-trust it.
When a transaction inserts a row whose foreign key column references a parent row, what lock does the engine take on that referenced parent row, and why not simply an exclusive row lock?
basics
~20 sIt takes a shared, key-preserving lock on the parent row — enough to stop the parent being deleted or its key changed before commit, but weak enough that many children can be inserted concurrently. An exclusive lock would serialise every child insert under the same parent.
A developer creates a unique index on a lookup table's code column, then tries to add a foreign key from another table referencing that column, and the database refuses with 'no unique constraint matching given keys'. What does the referenced side actually have to provide for a foreign key to be legal?
basics
~20 sThe referenced columns must be provably unique for every row of the table. The standard requires a declared PRIMARY KEY or UNIQUE constraint; engines that accept a bare unique index still require it to be full-table, non-partial, over plain columns and immediately checked. Partial and expression unique indexes never qualify.
A table keeps soft-deleted rows using a deleted_at timestamp, and the business rule is 'at most one live account per email address' while deleted rows must be retained. How do you enforce that rule in the database?
basics
~20 sPlain UNIQUE (email) is wrong — it blocks re-registering a deleted address. Enforce it with a partial (filtered) unique index on email restricted to rows where deleted_at IS NULL. Where partial indexes are unavailable, index a generated column that holds the email only for live rows.
A rule requires that at most one row per customer may have an empty (NULL) discount code — that is, uniqueness must treat two NULLs as equal. What options does a relational database give you, and what does each cost?
basics
~20 sThree routes: declare the key with NULLS NOT DISTINCT where the engine supports it (SQL:2023, PostgreSQL 15+); replace NULL with a non-null sentinel and use an ordinary unique constraint; or add a partial unique index over the rows where the column IS NULL. The sentinel is the portable choice.
A DELETE that removes a single row from a parent table occasionally runs for minutes and blocks other sessions. Explain how deleting one row can generate a very large amount of write work, and how you would bound the blast radius.
basics
~20 sCascading referential actions multiply: one parent row deletes all its children, each child deletes its own children, and every deleted row costs table and index maintenance, logging and a lock held to commit. Bound it by deleting in batches yourself, archiving instead of deleting, or dropping whole partitions.
How do you decide which business rules belong in database constraints, which belong in application code, and which need a different mechanism entirely?
basics
~20 sPut stable, structural, row- or table-scoped invariants in the schema; put volatile or contextual policy in code. Rules the engine cannot express — cross-service, aggregate, temporal — need an explicit mechanism such as index-based enforcement, serialisable transactions, or asynchronous reconciliation with alerting.
Product wants email addresses to be unique in a 100-million-row users table that currently contains duplicates, and there is no maintenance window. Lay out the rollout you would run, including how you decide when it is safe to enforce.
basics
~20 sSettle the semantics, enforce in the application first, dedupe the history in batches, build the unique index without blocking writes (CREATE UNIQUE INDEX CONCURRENTLY / ONLINE=ON), then attach it as a constraint with a brief lock. Uniqueness has no unvalidated mode, so the index build is the staging mechanism.
Some engines historically parsed CHECK constraint syntax but never enforced it — MySQL accepted and ignored CHECK clauses before version 8.0.16. What are the practical consequences of an unenforced constraint, and how would you verify that a constraint in a given database is actually enforced?
basics
~20 sThe schema claims an invariant the data does not have: bad rows accumulate silently, and code that trusts the constraint breaks later. Verify empirically — attempt an insert that must fail. If it succeeds, the constraint is decoration, and you need application checks plus a data audit.
An architect proposes declaring every constraint in the schema DEFERRABLE so that batch jobs can always rely on commit-time validation. Would you adopt that as a default? Justify the call.
basics
~20 sNo. Keep constraints NOT DEFERRABLE by default and make individual ones deferrable where a real cycle or swap demands it. Blanket deferrability costs error attribution and pending-check memory, restricts features like upsert conflict inference, and hides modelling problems.
You are setting the schema convention for a large database: should uniqueness always be declared as a named table constraint, or are bare unique indexes acceptable? How would you decide, and what follows from the choice?
basics
~20 sDefault to named constraints: they are the objects foreign keys, upsert targets and schema tooling can cite, and they are hard to delete by accident. Allow bare unique indexes only where constraint syntax cannot express the rule — conditional or computed uniqueness — and document each exception.
You are modelling an optional identifier — most rows will not have one, but when present it must be unique — and the schema has to behave identically on PostgreSQL, MySQL and Microsoft SQL Server. How do you approach the design?
basics
~20 sDo not let correctness depend on engine NULL rules, which disagree. Either move the optional identifier into its own table where the column is NOT NULL and uniquely constrained, or keep it in place with an engine-specific object (filtered index on SQL Server, plain unique constraint elsewhere) chosen deliberately.
showing 31–48 of 48