Primary key

On this page 7

A primary key is the column, or minimal set of columns, whose values uniquely identify every row in a relational database table. Among the database fundamentals, it is the core mechanism of entity integrity: no two rows may share a primary key value, and no part of it may be NULL. Each table can have at most one primary key, although it may have several other unique columns.

The primary key is more than a label. Other tables reference it through foreign keys, the database engine usually builds an index on it automatically, and in many storage engines it also determines the physical order of rows on disk. Choosing it well therefore affects correctness, performance and the ease of future schema changes.

How a primary key enforces uniqueness

A primary key is declared in the schema as a constraint, for example PRIMARY KEY (id) in a CREATE TABLE statement. The DBMS then rejects any INSERT or UPDATE that would create a duplicate value or a NULL in the key columns, so uniqueness is guaranteed by the engine rather than by application code.

To check uniqueness efficiently, the database maintains a unique index, usually a B-tree. The table below shows how common engines implement this index and what it means for storage.

EnginePrimary key indexPractical consequence
MySQL and MariaDB (InnoDB)Clustered index; rows stored in key orderSecondary indexes store the key, so long keys enlarge them
SQL ServerClustered by default, can be nonclusteredClustering choice is a separate design decision
PostgreSQLSeparate B-tree index over a heap tableRow order on disk is independent of the key
SQLiteInteger key aliases the internal rowidAn INTEGER PRIMARY KEY is the fastest lookup path
Primary key storage in common database engines

Because a primary key is NOT NULL by definition, it differs from a plain UNIQUE constraint, which in most engines still allows NULL values. This rule ensures that every row can always be addressed.

Natural, surrogate and composite keys

Every table usually has one or more candidate keys, meaning minimal column sets that are unique. The designer picks one as the primary key, and the remaining candidates are often enforced with unique constraints. The choice falls into three broad types.

TypeExampleStrengthWeakness
Natural keyISO country code, ISBNMeaningful; no extra columnReal-world values can change or repeat
Surrogate keyAuto-increment integer, UUIDStable and compactCarries no business meaning
Composite key(order_id, line_number)Models dependent rows exactlyWider foreign keys and joins
Types of primary keys and when each applies

Surrogate keys dominate in practice because business identifiers such as email addresses, tax numbers or product codes change more often than designers expect. A sound pattern is a surrogate primary key plus a unique constraint on the natural key, which keeps references stable while still preventing duplicates.

Choosing the key data type

The data type of a primary key affects index size, insert speed and how identifiers can be generated. Narrow, immutable and monotonically increasing values generally perform best in clustered engines.

Data typeSizeGenerationMain trade-off
INT4 bytesSequence or auto-incrementLimit of about 2.1 billion signed values
BIGINT8 bytesSequence or auto-incrementPractically unlimited; safe default
UUID version 416 bytesRandom, anywhereRandom inserts fragment clustered indexes
UUID version 716 bytesTime-ordered, anywhereSequential inserts but wider than BIGINT
Natural text keyVariableSupplied by the businessLarger indexes and possible changes
Common primary key data types compared

Running out of an INT range is a real production failure, so BIGINT is the safer default for tables that may grow quickly. UUIDs suit distributed systems where clients or multiple nodes create rows without coordination, and time-ordered variants avoid most of the index fragmentation caused by random identifiers.

The primary key works together with other constraints, and confusing them is a common modeling error. Each answers a different question about the data.

ConceptPurposeNULL allowedNumber per table
Primary keyIdentifies each rowNoAt most one
Unique constraintPrevents duplicate valuesUsually yesAny number
Foreign keyReferences a key in another tableYes, unless declared NOT NULLAny number
Candidate keyAny minimal unique column setNo, if chosen as primaryOne or more
Primary key and neighboring key concepts

A foreign key normally points to the primary key of the referenced table, although standard SQL also allows a reference to any unique key. When the referenced primary key changes, the database must update or block every dependent row, which is a strong argument for immutable key values.

Common mistakes with primary keys

Most primary key problems appear only after a system has grown, when changing the key is expensive. The following mistakes recur in practice.

  • Omitting a primary key leaves duplicate rows possible and makes replication, updates and ORM mapping harder or impossible.
  • Using mutable business data such as an email address forces cascading updates across every referencing table.
  • Exposing sequential IDs publicly in URLs can reveal record counts and invite enumeration; a separate public identifier avoids this.
  • Choosing a key that is too narrow, such as a 32-bit integer, leads to overflow errors in high-volume tables.
  • Inserting random UUIDs into clustered indexes causes page splits and slower writes as the table grows.

The idea in brief

A primary key is a unique ID number for every row in a table. It works like a passport number. Two travelers can share a name and a birthday. They never share a passport number, so the number reliably tells them apart.

The database refuses a row without this ID. It also refuses an ID that is already taken. That simple rule keeps the data trustworthy. Other tables can then point to exactly the right record.

A practical scenario

An online bookstore first designs its customers table with the email address as the primary key. Orders, reviews and invoices all reference that email. When customers begin changing their addresses, each change must cascade through millions of related rows, and several updates fail because of locking during peak hours.

The team migrates to a BIGINT surrogate key generated by a sequence and keeps a unique constraint on the email column. Orders now store a compact 8-byte customer ID instead of a long text value, the related indexes shrink noticeably, and an email change becomes a single-row update that no longer touches any other table.