Definition
- Primary key – A logical constraint that declares which columns uniquely identify each row; it enforces
UNIQUEandNOT NULL, there is at most one per table, and the database automatically backs it with an index (usually clustered). - Index – A physical performance structure that speeds up lookups; you can create many per table on any columns, it enforces no business rule, and it may allow duplicates and nulls.
- Relationship – Every primary key is backed by an index, but most indexes are not primary keys and carry no identity or not-null semantics.
- Foreign keys – Foreign keys reference the primary key, not arbitrary indexes, because the primary key is the row's canonical identity.
- Unique index – The middle ground: enforces uniqueness like a primary key but is not the row's identity and (depending on the engine) may permit a null.
At the National Bank of India, the primary key is the official rule in the banking regulations: "every account is identified by exactly one account number, it can never be blank, and no two accounts can share it." That rule is the law. An index is just a helpful finding aid the staff create to work faster, like a card catalog sorted by customer name or by branch – useful for speed, but it enforces no law, it can list the same branch many times, and you can keep several catalogs. The primary key is a promise about correctness; the index is a convenience for speed.
Understanding the Concepts
What is a primary key?
A primary key is first and foremost a constraint. It makes three guarantees: the value is unique across all rows, the value is never null, and there is exactly one primary key per table. These are business-correctness rules the database enforces so that application bugs cannot create a row with no identity or a duplicate identity.
-- National Bank of India: account_id is the primary key
CREATE TABLE accounts (
account_id VARCHAR(20) PRIMARY KEY, -- unique + not null + one per table
customer_name VARCHAR(100) NOT NULL,
pan VARCHAR(10) NOT NULL,
branch_code VARCHAR(10) NOT NULL,
balance DECIMAL(15,2) NOT NULL DEFAULT 0.00
);
-- The database enforces:
-- 1. No two rows can have the same account_id
-- 2. account_id can never be NULL
-- 3. This is the ONLY primary key on this table
-- This fails: duplicate primary key
INSERT INTO accounts VALUES ('SAV2001', 'Rajesh', 'ABC', 'MUM001', 50000);
INSERT INTO accounts VALUES ('SAV2001', 'Priya', 'DEF', 'DEL001', 30000);
-- ERROR: duplicate key value violates unique constraint
-- This fails: null primary key
INSERT INTO accounts VALUES (NULL, 'Amit', 'GHI', 'BLR001', 20000);
-- ERROR: null value in column "account_id" violates not-null constraint
What is an index?
An index is a different kind of object entirely. Its only job is performance: it is a sorted structure that lets lookups, joins, sorts, and groupings avoid full scans. It enforces nothing about correctness unless you explicitly make it a unique index.
-- Plain index on branch_code: allows duplicates, allows nulls
CREATE INDEX ix_accounts_branch ON accounts(branch_code);
-- Many accounts share branch 'MUM001' – no rule violated
SELECT * FROM accounts WHERE branch_code = 'MUM001';
-- Returns 5000 rows, all with the same branch_code
-- You can create many indexes on one table
CREATE INDEX ix_accounts_name ON accounts(customer_name);
CREATE INDEX ix_accounts_balance ON accounts(balance);
-- Three indexes on the same table, none enforcing any rule
How are they different?
| Feature | Primary Key | Index |
|---|---|---|
| Type | Constraint (correctness) | Structure (performance) |
| Uniqueness | Always enforced | Only if explicitly UNIQUE |
| NULL allowed | Never | Yes (unless UNIQUE NOT NULL) |
| Count per table | Exactly one | Many |
| Auto-created | Creates an index behind it | Created manually |
| Clustered (InnoDB) | Yes (the table itself) | No (separate structure) |
| Foreign key target | Yes – FK references PK | No – FK cannot reference plain index |
| Identity semantics | Row's canonical identity | No identity semantics |
What is the relationship between them?
Every primary key is backed by an index, but not every index is a primary key. The primary key adds the not-null rule, the single-per-table rule, and the "this is the row's identity" semantics. An ordinary index adds none of that.
-- The primary key creates an index automatically
-- You can see it in the system catalog
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'accounts';
indexname | indexdef
---------------+----------------------------------------------
accounts_pkey | CREATE UNIQUE INDEX accounts_pkey ON accounts
| USING btree (account_id)
ix_accounts_branch | CREATE INDEX ix_accounts_branch ON accounts
| USING btree (branch_code)
Why do foreign keys reference the primary key?
The primary key is the row's canonical identity. Foreign keys in other tables reference it to enforce referential integrity.
-- transactions.account_id references accounts.account_id (the PK)
CREATE TABLE transactions (
txn_id BIGSERIAL PRIMARY KEY,
account_id VARCHAR(20) NOT NULL
REFERENCES accounts(account_id), -- references the PK
amount DECIMAL(15,2) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- This succeeds: account SAV2001 exists
INSERT INTO transactions (account_id, amount)
VALUES ('SAV2001', 15000.00);
-- This fails: account SAV9999 does not exist
INSERT INTO transactions (account_id, amount)
VALUES ('SAV9999', 5000.00);
-- ERROR: insert or update on table "transactions" violates
-- foreign key constraint
What is a unique index and how does it compare?
A unique index is the middle ground. It enforces uniqueness like a primary key but is not the row's canonical identity and (depending on the engine) may permit one null.
-- Unique index on PAN: enforces uniqueness, not the row's identity
CREATE UNIQUE INDEX ux_accounts_pan ON accounts(pan);
-- Unique index on email: enforces uniqueness, not the row's identity
CREATE UNIQUE INDEX ux_accounts_email ON accounts(email);
-- The PK is still account_id – that is what FKs reference
-- PAN and email are secondary natural keys protected by unique indexes
| Feature | Primary Key | Unique Index | Plain Index |
|---|---|---|---|
| Enforces uniqueness | Yes | Yes | No |
| Allows NULL | No | Depends on engine | Yes |
| Count per table | One | Many | Many |
| FK target | Yes | Not typically | No |
| Identity semantics | Yes | No | No |
How does a primary key behave in InnoDB vs PostgreSQL?
-- InnoDB: primary key IS the clustered index
-- Leaf pages of the PK B-tree contain the actual row data
-- Every secondary index stores the PK as its row pointer
-- PostgreSQL: primary key is a UNIQUE NOT NULL index over a heap
-- Rows are stored in a heap (no physical order)
-- The PK index points to the heap location (ctid)
// com.bank.service.AccountService
package com.bank.service;
import java.math.BigDecimal;
import java.sql.*;
public class AccountService {
/**
* PK lookup: fastest path, uses the clustered index.
* Constraint guarantees exactly 0 or 1 row.
*/
public BigDecimal getBalanceByPK(Connection conn, String accountId)
throws SQLException {
String sql = "SELECT balance FROM accounts WHERE account_id = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, accountId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) return rs.getBigDecimal("balance");
return null;
}
}
}
/**
* Index lookup: uses the branch_code index.
* No uniqueness guarantee – may return many rows.
*/
public int countByBranch(Connection conn, String branchCode)
throws SQLException {
String sql = "SELECT COUNT(*) FROM accounts WHERE branch_code = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, branchCode);
try (ResultSet rs = ps.executeQuery()) {
rs.next();
return rs.getInt(1);
}
}
}
}
What are common mistakes?
The most common mistake is confusing a primary key with an index and thinking they are interchangeable.
-- Wrong thinking: "I have an index on email, so email is a primary key"
-- No! The index enforces no rule unless it is UNIQUE
-- Another mistake: making a wide natural key the PK
-- Bad: email as PK (long string, changes over time)
CREATE TABLE accounts_bad (
email VARCHAR(255) PRIMARY KEY -- wide, mutable
);
-- Good: compact surrogate PK + unique index on natural key
CREATE TABLE accounts_good (
account_id VARCHAR(20) PRIMARY KEY, -- compact, immutable
email VARCHAR(255) NOT NULL
);
CREATE UNIQUE INDEX ux_accounts_email ON accounts_good(email);
What are common interview follow-up questions?
- Why does InnoDB embed the primary key in every secondary index?
- What is the difference between a unique index and a primary key?
- Can a table have no primary key? What happens?
- Why should the primary key be compact and immutable?
Visual Overview
┌─────────────────────────────────────────────────┐ │ Primary Key vs Index │ ├─────────────────────────────────────────────────┤ │ │ │ Primary Key (constraint + index): │ │ ┌─────────────────────────────────┐ │ │ │ Rules: │ │ │ │ - UNIQUE (no duplicates) │ │ │ │ - NOT NULL (never blank) │ │ │ │ - One per table │ │ │ │ - FK target (row identity) │ │ │ │ │ │ │ │ Backed by: │ │ │ │ B-tree index (auto-created) │ │ │ │ Clustered in InnoDB │ │ │ └─────────────────────────────────┘ │ │ │ │ Index (performance structure only): │ │ ┌─────────────────────────────────┐ │ │ │ Rules: NONE (unless UNIQUE) │ │ │ │ Allows duplicates: YES │ │ │ │ Allows NULLs: YES │ │ │ │ Count per table: MANY │ │ │ │ FK target: NO │ │ │ └─────────────────────────────────┘ │ │ │ │ Unique Index (middle ground): │ │ ┌─────────────────────────────────┐ │ │ │ Enforces uniqueness: YES │ │ │ │ Identity semantics: NO │ │ │ │ May allow one NULL │ │ │ │ Count per table: MANY │ │ │ └─────────────────────────────────┘ │ └─────────────────────────────────────────────────┘
Key Takeaway
A primary key and an index are often confused because a primary key is implemented with an index, but they are fundamentally different kinds of object. A primary key is a constraint – a business-correctness rule. It guarantees that the key columns are unique and never null, there is exactly one primary key per table, and that key is the row's canonical identity, which is what foreign keys in other tables reference. To enforce the uniqueness rule efficiently, the database automatically creates an index behind the primary key, and in clustered engines that backing index is also the clustered index.
An index, by itself, is purely a performance structure. Its only job is to make lookups, joins, sorts, and groupings faster by avoiding full scans. It enforces no rule unless you explicitly make it a unique index, you can create many of them on any columns, and a plain index happily stores duplicates and nulls. So every primary key is backed by an index, but most indexes are not primary keys and carry none of the identity, not-null, or one-per-table semantics.
At the National Bank of India Rajesh used all three variants in the accounts table to make the distinction concrete. The account id is the primary key – the unique, never-blank identity that the transactions table's foreign key points at. The branch code has a plain index because operations search by branch all day and thousands of accounts share a branch, so duplicates are expected and no rule applies. The PAN has a unique index because it is a real-world unique natural key, but it is not the account's identity, so transactions still reference the account id, not the PAN.
The nuance Pankaj likes to close with is that the unique index is the deliberate middle ground: it enforces uniqueness just like a primary key, but it is not the row's identity and, depending on the engine, may permit a null. That is exactly why the team kept a compact surrogate account id as the primary key while using a unique index for the PAN, rather than making the PAN itself the primary key.
Remember one sentence: a primary key is a correctness rule backed by an index, while an index is a speed structure with no rule – and you can have many.
---