✏️ Explanatory Question

A big UPDATE/DELETE is locking your table and blocking the app — how do you run large batch operations safely in production?

👁 11 Views
📘 Detailed Answer
🟢 Easy
💡

Answer with Explanation

92

A big UPDATE/DELETE is locking your table and blocking the app — how do you batch it safely?

Level: Very Hard — A real production emergency; the naive one-shot statement can take down an entire application.

Scenario: You need to delete 5 million old rows from a 50-million-row logs table. You run DELETE FROM logs WHERE created_at < '2025-01-01'. It runs for 20 minutes, holds locks, fills the undo log, replication lags badly, and the app starts timing out. How should this have been done?

The cause: A single huge DELETE is one giant transaction. It holds row locks on millions of rows, generates a massive undo/redo log, and the replica must apply the entire operation at once — causing lock contention, disk pressure, and replication lag. It's "all or nothing," so it can't even be interrupted safely.

Why interviewers ask this: It tests production maturity. The answer is chunking (batching) — breaking one huge transaction into many small ones — plus awareness of replication, locking, and undo-log impact.

The Wrong Way vs The Right Way

One Giant Statement

  • Locks millions of rows at once
  • Huge undo log, long transaction
  • Severe replication lag
  • App times out; can't be paused

Small Batches (Chunking)

  • Locks only a few thousand rows briefly
  • Small transactions commit fast
  • Replica keeps up
  • Can pause/stop between batches

The Fix — Batched DELETE

-- THE PROBLEM: one massive locking transaction
DELETE FROM logs WHERE created_at < '2025-01-01';   -- 5M rows at once

-- THE FIX: delete in small chunks, committing each batch
-- Run this in a loop (from a script or stored procedure)
DELETE FROM logs
WHERE created_at < '2025-01-01'
ORDER BY id            -- delete in PK order for efficiency
LIMIT 5000;            -- small batch

-- Repeat until 0 rows are affected. Between batches:
--   * COMMIT (each DELETE auto-commits if autocommit=1)
--   * sleep briefly to let the replica catch up

A Robust Batching Loop (application pseudo-logic)

-- Driver-side loop (Python/PHP/Java), MySQL statements shown:
-- WHILE affected_rows > 0:
--     DELETE FROM logs WHERE created_at < '2025-01-01'
--     ORDER BY id LIMIT 5000;
--     -- check ROW_COUNT(); if 0, break
--     -- SLEEP(0.2) to ease replication lag & I/O
--     -- monitor: SHOW REPLICA STATUS -> Seconds_Behind_Master

-- For UPDATEs, batch by a moving key range instead of OFFSET:
UPDATE users
SET status = 'archived'
WHERE id BETWEEN 1 AND 10000 AND last_login < '2024-01-01';
-- next batch: id BETWEEN 10001 AND 20000, etc.

Even Better Alternatives

  • Partitioning: If the table is partitioned by date, ALTER TABLE logs DROP PARTITION p2024; removes millions of rows instantly with almost no locking or undo cost.
  • Create-and-swap: Copy the rows you want to keep into a new table, then RENAME — often faster than deleting most of a table.
  • Online tools: pt-archiver (Percona Toolkit) is purpose-built for safe, throttled bulk archiving/deletion.
Batch size tuning: Too small (e.g., 100) = too many round-trips and slow overall. Too large (e.g., 500K) = back to locking problems. 1,000–10,000 rows per batch is the usual sweet spot — tune based on row size and replication lag.
Interviewer follow-up: "Your batched DELETE gets slower with each batch even though batch size is constant — why?" → Likely the WHERE created_at < ... keeps re-scanning already-deleted ranges, or the index is fragmenting. Fix by seeking on the primary key (track the last deleted id and use WHERE id > last_id) so each batch starts where the last ended.