Master database partitioning in MySQL to keep your Laravel application performant at scale. Learn to implement range partitioning for logs and historical data.
Previously in this course, we explored Advanced Indexing Strategies to speed up read operations. While indexes are essential, they eventually hit a wall when tables grow into the hundreds of millions of rows. At that scale, even the most efficient B-Tree index becomes too large to fit in memory, leading to disk I/O bottlenecks.
This lesson introduces Partitioning, a technique that splits a single logical table into smaller, physically independent segments. By organizing data this way, we can achieve "partition pruning"—where MySQL ignores entire sections of a table that don't contain the requested data, dramatically reducing query latency.
In the context of scaling a SaaS platform, we often deal with "append-only" data like application logs, audit trails, or clickstream events. These tables grow indefinitely. Partitioning allows us to divide these tables based on a specific key—most commonly a created_at timestamp.
Think of it as the difference between searching for a specific page in one massive, 10,000-page book versus looking for a specific folder in a filing cabinet where each folder contains only one month of records.
| Strategy | Use Case | Benefit |
|---|---|---|
| Range | Time-series, Logs, Audit trails | Efficient for "older than X" data purging |
| List | Regional data, Status codes | Isolate specific subsets of data |
| Hash | Evenly distributed data | Spreads I/O load across disk segments |
Before touching your database, you must define the "partition key." This key must be part of your primary key or any unique index on the table. This is a hard requirement in MySQL: if you try to partition by created_at but your primary key is just id, the database will throw an error.
For our SaaS project, let’s assume we have an audit_logs table. We want to partition this by month so we can quickly drop old partitions (archiving) without running expensive DELETE queries that cause table fragmentation.
To implement this, we need to modify our migration. Since Laravel’s Schema Builder doesn't natively support PARTITION BY syntax, we use DB::statement to execute the raw SQL during the migration process.
PHPuse Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; use Illuminate\Support\Facades\DB; return new class extends Migration { public function up() { Schema::create('audit_logs', function (Blueprint $table) { $table->bigIncrements('id'); $table->unsignedBigInteger('user_id'); $table->string('action'); $table->timestamp('created_at'); #6A9955">// The partition key MUST be part of the primary key $table->primary(['id', 'created_at']); }); #6A9955">// Define partitions DB::statement("ALTER TABLE audit_logs PARTITION BY RANGE COLUMNS(created_at) ( PARTITION p2023_10 VALUES LESS THAN('2023-11-01'), PARTITION p2023_11 VALUES LESS THAN('2023-12-01'), PARTITION p2023_12 VALUES LESS THAN('2024-01-01'), PARTITION p_future VALUES LESS THAN MAXVALUE )"); } };
By using RANGE COLUMNS(created_at), we instruct MySQL to physically store rows based on the date. When you run SELECT * FROM audit_logs WHERE created_at > '2023-11-15', the database engine will ignore the p2023_10 partition entirely.
site_metrics table with id and recorded_at columns.['id', 'recorded_at'].DB::statement to add three range partitions for the current and next two months.EXPLAIN PARTITIONS SELECT * FROM site_metrics WHERE recorded_at = '2024-05-01'; in your database console. Look at the partitions column in the output.unique index on an email address, you cannot partition by created_at unless you also include created_at in that unique index.MAXVALUE Trap: Always include a PARTITION ... VALUES LESS THAN MAXVALUE partition. If you don't, and a row is inserted that exceeds your defined ranges, the query will fail with an error.Partitioning is a powerful tool for scaling logs and time-series data. By aligning your physical storage with your business logic (e.g., monthly reporting), you significantly reduce I/O overhead. Remember that partitioning requires strict adherence to primary key constraints and necessitates an automated maintenance plan to manage partition lifecycles.
Up next: We will explore how to route these queries across multiple servers using Read/Write Database Splitting.
Scale your database capacity by offloading heavy read traffic to replicas. Learn how to configure Laravel to automatically route read/write database queries.
Read moreMaster SQL indexing for joins by learning to analyze execution plans and build covering indexes that eliminate table scans in high-traffic Laravel applications.
Database Partitioning Techniques
Custom Middleware Development
Database Connection Pooling
Handling Large Data Exports
Security Header Configuration
Database Sharding Concepts
Real-time Data Synchronization
Database Deadlock Prevention
Managing Third-Party API Integrations