MySQL
Composite Primary Keys
Database Design
SQL Tutorial
Data Modeling

How to properly create composite primary keys - MYSQL

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A composite primary key is a primary key made from more than one column. It is the right tool when row uniqueness comes from a combination of values rather than from a single identifier.

When a Composite Key Makes Sense

The classic example is a junction table in a many-to-many relationship. Suppose one student can enroll in many courses and one course can contain many students. The enrollments table is uniquely identified by the pair (student_id, course_id).

That is a natural composite key because neither column is unique by itself, but the pair is.

Creating the Table Correctly

In MySQL, define the participating columns first, then declare the composite primary key once:

sql
1CREATE TABLE enrollments (
2    student_id BIGINT NOT NULL,
3    course_id BIGINT NOT NULL,
4    enrolled_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
5    PRIMARY KEY (student_id, course_id)
6);

This gives you two things:

  • row uniqueness across the pair
  • an index that starts with student_id and then course_id

That second point matters for query performance.

Column Order Is Not Cosmetic

The order of columns in a composite primary key affects index usage. A primary key on (student_id, course_id) supports efficient lookups by:

  • 'student_id'
  • 'student_id plus course_id'

It does not support the same efficient access pattern for queries filtering only by course_id.

If you often query by course first, add another index:

sql
CREATE INDEX idx_enrollments_course_id
    ON enrollments (course_id);

Do not assume the composite primary key automatically optimizes every access pattern.

Referencing a Composite Key

If another table needs to reference the composite key, the foreign key must include all key columns in the same order and compatible types.

sql
1CREATE TABLE attendance (
2    student_id BIGINT NOT NULL,
3    course_id BIGINT NOT NULL,
4    attended_on DATE NOT NULL,
5    status VARCHAR(20) NOT NULL,
6    PRIMARY KEY (student_id, course_id, attended_on),
7    CONSTRAINT fk_attendance_enrollment
8        FOREIGN KEY (student_id, course_id)
9        REFERENCES enrollments (student_id, course_id)
10);

This is where composite keys become slightly more verbose, but the model stays honest.

Composite Key Versus Surrogate Key

A common alternative is to add an auto-increment id column and put a UNIQUE constraint on the natural pair:

sql
1CREATE TABLE enrollments (
2    id BIGINT NOT NULL AUTO_INCREMENT,
3    student_id BIGINT NOT NULL,
4    course_id BIGINT NOT NULL,
5    enrolled_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
6    PRIMARY KEY (id),
7    UNIQUE KEY uq_enrollments_student_course (student_id, course_id)
8);

This version is often easier for ORMs and downstream foreign keys. The tradeoff is that the real business rule now lives in the unique constraint rather than in the primary key.

Neither design is universally better. Use a composite primary key when the combined columns are the real identity of the row and you are comfortable carrying them through relationships.

Updates and Stability

Primary keys should be stable. If one part of the composite key changes often, making it part of the primary key is usually a design smell because updates become more expensive and more relationships are affected.

That is a good litmus test:

  • stable natural identity: composite primary key is reasonable
  • frequently changing attribute: use a surrogate primary key instead

Common Pitfalls

The most common mistake is declaring multiple separate PRIMARY KEY clauses. A table can only have one primary key, even if that key spans multiple columns.

Another mistake is mixing column types across related tables. If the parent uses BIGINT UNSIGNED, the child should match exactly or foreign key creation may fail.

A third issue is ignoring index order. Developers often create (a, b) and later wonder why filtering only by b is still slow. That is expected behavior for composite indexes.

Summary

  • Create composite primary keys with a single PRIMARY KEY (col1, col2) clause.
  • Use them when row identity truly depends on a column combination.
  • Remember that key column order affects index usefulness.
  • Foreign keys must reference all composite key columns in matching order and type.
  • Prefer a surrogate key when the natural key is large, awkward, or unstable.

Course illustration
Course illustration

All Rights Reserved.