Partitioning in MySQL

MySQL · Advanced · 12 min

Partitioning makes dropping old data instant on huge tables, but its restrictions shape the whole schema.

MySQL partitions a table into separately stored pieces declared in CREATE TABLE ... PARTITION BY:

  • RANGE / RANGE COLUMNS: by ranges, typically dates. RANGE COLUMNS(created_at) works directly on date columns; plain RANGE needs an integer expression such as TO_DAYS(created_at).
  • LIST / LIST COLUMNS: by discrete values.
  • HASH / KEY: spread rows evenly by a hash.

The optimizer prunes partitions that can't match the WHERE clause; EXPLAIN shows which ones are read in the partitions column.

Restrictions to design around:

  • Every unique key, including the primary key, must include all columns of the partitioning expression. A table partitioned by created_at needs a primary key like (id, created_at).
  • Partitioned InnoDB tables can't have foreign keys and can't be referenced by them.
  • Each partition is a separate tablespace file; thousands of partitions cost memory and open files.

Maintenance operations are the main benefit: ALTER TABLE t DROP PARTITION p2025_01 deletes a month instantly; REORGANIZE PARTITION splits a catch-all MAXVALUE partition to add new ranges; EXCHANGE PARTITION swaps a partition with a standalone table for fast bulk loads or archiving.

Partitioning doesn't speed up primary-key lookups; use it for data lifecycle and pruning on time-based queries.

Example

CREATE TABLE event (
    id         BIGINT NOT NULL AUTO_INCREMENT,
    created_at DATETIME NOT NULL,
    kind       VARCHAR(20) NOT NULL,
    PRIMARY KEY (id, created_at)          -- must include the partitioning column
)
PARTITION BY RANGE COLUMNS (created_at) (
    PARTITION p2026_04 VALUES LESS THAN ('2026-05-01'),
    PARTITION p2026_05 VALUES LESS THAN ('2026-06-01'),
    PARTITION pmax     VALUES LESS THAN (MAXVALUE)
);
INSERT INTO event (created_at, kind) VALUES ('2026-04-20', 'login'), ('2026-05-02', 'click');

EXPLAIN SELECT COUNT(*) FROM event WHERE created_at >= '2026-05-01' AND created_at < '2026-06-01';
-- partitions: p2026_05

ALTER TABLE event REORGANIZE PARTITION pmax INTO (
    PARTITION p2026_06 VALUES LESS THAN ('2026-07-01'),
    PARTITION pmax     VALUES LESS THAN (MAXVALUE));
ALTER TABLE event DROP PARTITION p2026_04;   -- instant retention

Remember

  • RANGE COLUMNS on a date is the usual time-series layout
  • Unique keys must include the partitioning columns; no foreign keys
  • DROP and REORGANIZE PARTITION make data lifecycle cheap

Watch out for

  • Partitioning a table that relies on foreign keys, which MySQL doesn't allow.

Check yourself

  1. A table is partitioned by created_at. Why is PRIMARY KEY (id) rejected?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: dev.mysql.com/doc/refman/8.4/en/partitioning.html