MySQL Unique Index
A unique index is an index that also enforces a rule: no two rows may have the same value (or combination of values) in the indexed columns. It gives you two things at once: fast lookups, and a guarantee against duplicates such as two people booking the same seat.
1. Creating a Unique Index
CREATE UNIQUE INDEX index_name ON table_name (col1 [, col2 ...]);
ALTER TABLE table_name ADD UNIQUE INDEX index_name (col1, ...);
CREATE UNIQUE INDEX uq_event_seat ON bookings (event_id, seat_no);
INSERT INTO bookings (event_id, seat_no, email) VALUES (7, 'B4', 'sam@mail.example');
INSERT INTO bookings (event_id, seat_no, email) VALUES (7, 'B5', 'sam@mail.example');
Query OK, 0 rows affected
Records: 0 Duplicates: 0 Warnings: 0
ERROR 1062 (23000): Duplicate entry '7-B4' for key 'bookings.uq_event_seat'
Query OK, 1 row affected
2. Duplicates Already in the Table
A unique index can only be created if the existing data is already unique. Ana booked twice with the same email:
CREATE UNIQUE INDEX uq_email ON bookings (email);
ERROR 1062 (23000): Duplicate entry 'ana@mail.example' for key 'bookings.uq_email'
SELECT email, COUNT(*) AS bookings FROM bookings GROUP BY email HAVING COUNT(*) > 1;
Result: 1 row
| bookings | |
|---|---|
| ana@mail.example | 2 |
Here one email may book several times, so a unique index on email alone is the wrong rule. One email per event makes sense:
CREATE UNIQUE INDEX uq_event_email ON bookings (event_id, email);
Query OK, 0 rows affected
Records: 0 Duplicates: 0 Warnings: 0
3. NULLs Are Not Duplicates
A unique index allows many NULLs, because NULL means unknown. Several bookings can have no coupon, but each coupon code can be used only once:
CREATE UNIQUE INDEX uq_coupon ON bookings (coupon);
INSERT INTO bookings (event_id, seat_no, email, coupon) VALUES (8, 'A2', 'mo@mail.example', NULL);
INSERT INTO bookings (event_id, seat_no, email, coupon) VALUES (8, 'A3', 'zed@mail.example', 'EARLY10');
Query OK, 0 rows affected
Records: 0 Duplicates: 0 Warnings: 0
Query OK, 1 row affected
ERROR 1062 (23000): Duplicate entry 'EARLY10' for key 'bookings.uq_coupon'
SELECT COUNT(*) AS bookings_without_coupon FROM bookings WHERE coupon IS NULL;
Result: 1 row
| bookings_without_coupon |
|---|
| 4 |
4. Uniqueness Only for Some Rows
A cancelled booking shouldn't block the seat. MySQL has no partial (filtered) indexes, but a functional unique index can return NULL for rows that shouldn't count:
DROP INDEX uq_event_seat ON bookings;
CREATE UNIQUE INDEX uq_active_seat ON bookings
((IF(status = 'active', CONCAT(event_id, ':', seat_no), NULL)));
UPDATE bookings SET status = 'cancelled' WHERE event_id = 7 AND seat_no = 'B3';
INSERT INTO bookings (event_id, seat_no, email) VALUES (7, 'B3', 'new@mail.example'); -- seat freed
INSERT INTO bookings (event_id, seat_no, email) VALUES (7, 'B3', 'late@mail.example'); -- seat taken again
Query OK, 0 rows affected
Records: 0 Duplicates: 0 Warnings: 0
Query OK, 6 rows affected
Records: 6 Duplicates: 0 Warnings: 0
Query OK, 1 row affected
Rows matched: 1 Changed: 1 Warnings: 0
Query OK, 1 row affected
ERROR 1062 (23000): Duplicate entry '7:B3' for key 'bookings.uq_active_seat'
SELECT event_id, seat_no, email, status FROM bookings WHERE event_id = 7 AND seat_no = 'B3';
Result: 2 rows
| event_id | seat_no | status | |
|---|---|---|---|
| 7 | B3 | new@mail.example | active |
| 7 | B3 | raj@mail.example | cancelled |
5. Using a Unique Index for Upserts
Unique indexes make INSERT … ON DUPLICATE KEY UPDATE possible. A user rebooking the same event just updates their seat:
INSERT INTO bookings (event_id, seat_no, email) VALUES (8, 'A9', 'ana@mail.example') AS nb
ON DUPLICATE KEY UPDATE seat_no = nb.seat_no;
SELECT event_id, seat_no, email FROM bookings WHERE email = 'ana@mail.example' ORDER BY event_id;
Query OK, 2 rows affected
Result: 2 rows
| event_id | seat_no | |
|---|---|---|
| 7 | B2 | ana@mail.example |
| 8 | A9 | ana@mail.example |
6. Unique Index vs Unique Constraint vs Primary Key
| UNIQUE INDEX | UNIQUE constraint | PRIMARY KEY | |
|---|---|---|---|
| How created | CREATE UNIQUE INDEX | UNIQUE (col) in CREATE/ALTER TABLE | PRIMARY KEY (col) |
| Stored as | Unique B-tree index | The same unique index | Clustered index (the table itself) |
| NULLs | Many allowed | Many allowed | Not allowed |
| Per table | Many | Many | One |
| Functional / expression | Yes | No | No |
DROP INDEX.7. Summary
CREATE UNIQUE INDEXprevents duplicate values and speeds up lookups.- Composite unique indexes enforce unique combinations, like (event, seat).
- Existing duplicates must be fixed first; NULLs never count as duplicates.
- Functional unique indexes can enforce uniqueness for only some rows.
- Unique indexes power ON DUPLICATE KEY UPDATE.