跪拜 Guibai
← All articles
MySQL · Database

The One Index Every Junior DBA Adds That Wastes Disk Space

By 烬羽 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Every unnecessary index costs disk space and slows writes. Knowing that InnoDB's clustered index covers leftmost-prefix queries eliminates redundant indexes that most novices add by reflex — a small rule that compounds across large tables.

Summary

A real blog database design — users, articles, comments, likes, avatars, and tags — is split into six tables by treating entities and relationships as separate modeling units. The user table deliberately holds only three core fields, pushing low-frequency data like avatars into their own table to keep the most-queried table small and cache-friendly. Comments use a self-referencing foreign key on `parentId` to model unlimited nesting, with different cascade strategies for article deletion versus parent-comment deletion.

The central lesson lands on the likes table. A composite primary key of `(userId, postId)` enforces the one-like-per-user constraint without application code and, because InnoDB stores rows in a clustered index ordered by the primary key, queries on `userId` alone hit the leftmost prefix of that index. Adding a separate `KEY userId` duplicates what the clustered index already provides, burning disk space and write overhead for no query benefit. The only extra index needed is on `postId`, which is not the leftmost column.

The same composite-key logic repeats in the many-to-many junction table for article tags. The whole design flows from two questions: is this table an entity or a relationship, and is the field already the leftmost column of an existing composite index.

Takeaways
— A composite primary key like `(userId, postId)` already covers queries on `userId` alone via the leftmost prefix; a separate index on `userId` is redundant.
— The only extra index the likes table needs is on `postId`, because `postId` is not the leftmost column of the composite key.
— Split tables by business entity vs. relationship: entities get their own tables, relationships become relationship tables with foreign keys or junction tables.
— The user table stores only three core fields (id, name, password) to stay small and cache-friendly; ancillary data like avatars live in a separate table linked by foreign key.
— Comments model unlimited nesting with a self-referencing `parentId` foreign key; `ON DELETE SET NULL` keeps replies when a parent is deleted, while `ON DELETE CASCADE` on `postId` removes all comments when an article is deleted.
— Many-to-many relationships (articles↔tags) use a junction table with a composite primary key, following the same leftmost-prefix logic as the likes table.
— Image files are not stored in the database — only a `filename` path is kept, with actual files served from OSS or CDN.
Conclusions

The instinct to index every foreign key column individually is so common that it qualifies as a reliable signal of DBA inexperience — and it's reinforced by ORMs and migration tools that auto-index foreign keys without checking whether a composite index already covers them.

Keeping the user table to three columns is a deliberate performance trade-off that contradicts the typical impulse to centralize all user profile data; it treats query frequency as the primary factor in column placement, not conceptual grouping.

The `parentId` self-reference for comments is space-efficient but shifts complexity to the query layer — fetching a full comment tree requires recursive CTEs or application-level assembly, a cost the article acknowledges but does not solve.

Concepts & terms
Clustered index (InnoDB)
In InnoDB, the primary key is a clustered index: the table's rows are physically stored in the index's leaf pages, ordered by the primary key columns. This means the primary key IS the table's storage order.
Leftmost prefix principle
A composite index can serve queries that filter on a leading subset of its columns. For a composite key on (A, B), a query filtering only on A can use the index; a query filtering only on B cannot.
Self-referencing foreign key
A foreign key in a table that points to the same table's primary key, used to model hierarchical data like comment threads where each reply references its parent comment.
Junction table (many-to-many)
A table that stores pairs of foreign keys to link two entities in a many-to-many relationship, typically with a composite primary key on both foreign key columns.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗