What is the actual difference between a PRIMARY KEY and a UNIQUE constraint in SQL?
Both guarantee that no two rows carry the same value in the column set, but they differ on three points: how many you may declare, what NULL is allowed to do, and what the key means to the engine.
CREATE TABLE accounts (
id INTEGER PRIMARY KEY, -- identifies the row; exactly one per table
email TEXT NOT NULL UNIQUE, -- a business rule; many of these are allowed
external_ref TEXT UNIQUE -- a business rule that tolerates NULLs
);
INSERT INTO accounts (id, email, external_ref) VALUES
(1, '[email protected]', 'EXT-100'),
(2, '[email protected]', NULL);
SELECT id, email, external_ref FROM accounts ORDER BY id;Count. A table has at most one primary key. It can have any number of unique constraints — most real schemas carry three or four, one per business identifier that must not repeat.
NULL. This is the part people get wrong. A UNIQUE constraint permits multiple NULLs, because in SQL NULL means "unknown", and two unknowns are not known to be equal. The primary key column is NOT NULL by definition, so exactly one null key is not an option. Notice the second row above: external_ref is NULL and no conflict arises, while email could never repeat.
Meaning. The primary key is the row's identity — the value other tables reference in foreign keys, the value that shows up in a WHERE id = ? lookup, and the value a replica routes on. A unique constraint is a data-quality rule about one attribute: this email has not been used before.
Mechanically the two are close. In SQLite both create a unique index, in PostgreSQL both create a unique B-tree index, and in MySQL the primary key is additionally the clustered index so the whole row is physically ordered by it — which is why a MySQL table with an AUTO_INCREMENT primary key and a random UUID unique key has very different write behaviour.