CMD Guide
HomeDatabasesSQL Fundamentals

DELETE

DELETE does not erase bytes in place; it walks the rows that match your WHERE clause and, one row at a time, writes each row's pre-image into the transaction log (so the change is durable and reversible) before marking that row's slot as dead — which is why it is transactional, fires triggers, and can be rolled back, but is also slow on millions of rows.

Syntax and the components that matter

DELETE FROM employees
WHERE id = 1;

The engine evaluates WHERE against the current state of the table, collects the matching rows, then deletes them as a set inside the surrounding transaction. Nothing is visible to other sessions until you COMMIT.

A worked trace with real values

Start with this employees table (an id primary key plus a row-version/transaction id that the engine keeps internally — shown here to make the mechanism concrete):

idnamedeptsalaryrow state (xmin / live?)
1AshaSales52000tx 100 · live
2RaviEng78000tx 100 · live
3MeeraSales61000tx 105 · live

Now run, inside a transaction:

BEGIN;
DELETE FROM employees
WHERE id = 1;
  1. Scan / index lookup. id = 1 on a primary key is an index probe, not a table scan. It locates the row for Asha.
  2. Lock the row. A row-level exclusive lock is taken so no concurrent transaction can update or delete it underneath us.
  3. Log the pre-image. Before touching the page, the old row (1, Asha, Sales, 52000) is written to the write-ahead/undo log. This is what makes ROLLBACK possible.
  4. Mark it dead, do NOT reclaim space. Postgres stamps the row's xmax with this transaction's id (the row is now "deleted by tx 110" but its bytes still sit on the page). InnoDB marks it delete-flagged and links the old version into the undo log. The page does not shrink yet.
  5. Fire row triggers. Any BEFORE/AFTER DELETE trigger and ON DELETE referential action (e.g. CASCADE) runs here — per row.
  6. COMMIT vs ROLLBACK. COMMIT makes the dead mark permanent; the slot becomes reclaimable garbage. ROLLBACK simply ignores the xmax stamp and the row is alive again — no data was ever physically removed.

After COMMIT the logical table is:

idnamedeptsalary
2RaviEng78000
3MeeraSales61000

The space Asha occupied is freed only later — by VACUUM (Postgres) or purge of old undo (InnoDB). That is why a big DELETE can leave a table file just as large on disk as before.

diagram
diagram

DELETE vs TRUNCATE vs DROP — the mechanism, not the marketing

DELETETRUNCATEDROP
What it touchesmatching rowsall rowsrows + the table definition
WHERE filteryesnono
Loggingfull, per rowminimal (page deallocation)minimal
Speed on a huge tableslow (O(rows))near-instantnear-instant
Fires row triggersyesnono
Rollback inside a txalwaysPostgres/SQL Server: yes · MySQL: no (implicit commit)Postgres: yes · MySQL: no
Resets identitynoyesn/a (table gone)
Space returnedlater (VACUUM/purge)immediatelyimmediately

Rule of thumb: need a filter, triggers, or safe rollback → DELETE. Wiping every row of a big table and you own the identity reset → TRUNCATE. Want the table itself gone → DROP.

Pitfalls

The WHERE-less DELETE footgun

-- intends to delete one employee, but the WHERE was lost
DELETE FROM employees;   -- deletes EVERY row

Why the naive version is wrong: WHERE is optional syntax, so the parser happily accepts a clause-less DELETE and treats it as "match all rows." There is no warning. The most common real-world cause is a highlighted query in a SQL client where you select the DELETE FROM employees line but not the WHERE id = 1 line below it, then hit Run. The fix is procedural, not syntactic:

Foreign keys block or cascade

Deleting a parent row that is referenced by a child fails with a foreign-key violation unless the constraint is ON DELETE CASCADE — in which case it silently deletes the children too. Know which one you have before you run it in production.

The big-DELETE that bloats and locks

A single DELETE FROM orders WHERE created_at < '2020-01-01' over tens of millions of rows holds locks and undo for the whole statement, can blow up replication lag, and leaves the table physically as large as before (dead tuples await VACUUM). Delete in bounded batches (e.g. ... LIMIT 10000 in a loop, committing each batch), or if you are clearing the whole table use TRUNCATE.

DELETE does not shrink the file

Engineers are surprised that du on the data file is unchanged after a huge delete. The rows are logically gone but the pages are still allocated; you need VACUUM FULL / OPTIMIZE TABLE (which rewrites the table) to actually return disk to the OS.

Takeaways


Sources: ISO/IEC 9075 SQL standard (Data Manipulation — <delete statement>); PostgreSQL documentation (DELETE, TRUNCATE, and "Routine Vacuuming" on dead tuples and MVCC xmin/xmax); MySQL 8.0 Reference Manual (DELETE, TRUNCATE TABLE, InnoDB undo logs and purge, --safe-updates); Microsoft SQL Server docs (DELETE vs TRUNCATE TABLE, minimal logging). Worked trace, DELETE/TRUNCATE/DROP mechanism contrast, and SVG re-authored/deepened for this guide; image-placeholder before/after tables replaced with inline data.

🎯 STRICT STANDOUT: Why / mental model / when-not / worked / failure / hostile panel — DELETE

Why this concept exists (judgment layer)

DELETE is logged, row-by-row, trigger-firing DML — the safe selective remove. WHERE-less DELETE and giant single-statement deletes are classic outage patterns.

Mental model (install this intuition)

Locate rows → lock → log pre-image → mark dead (MVCC xmax / delete flag) → triggers/FK actions → commit. Space reclaimed later (VACUUM/purge). WHERE optional to parser, mandatory to survival.

Worked example with numbers or traced steps

DELETE FROM employees WHERE id=1:
  index probe → row lock → WAL/undo pre-image → xmax set → AFTER DELETE
ROLLBACK undoes xmax; COMMIT leaves dead tuple until VACUUM
Footgun: DELETE FROM employees;  -- all rows
10M-row DELETE: batch LIMIT 10k + commit; else replication lag + bloat

When NOT to use / named alternative

Wipe entire large table with no need for triggers → TRUNCATE. Remove table → DROP. Soft-delete product requirement → UPDATE deleted_at, not hard DELETE.

Failure mode & ops fingerprint

Fingerprint: client runs only DELETE FROM line without WHERE; table empty; disk usage unchanged after massive delete (need VACUUM FULL/OPTIMIZE); FK RESTRICT errors; CASCADE silently deletes children.

Hostile-panel drills (defend the decision)

Q1. Why doesn't DELETE free disk immediately?
Model answer: Rows marked dead; pages stay allocated until VACUUM/purge or table rewrite.

Q2. Safe procedure before production DELETE?
Model answer: SELECT same WHERE first; BEGIN; DELETE; check rowcount; COMMIT or ROLLBACK; consider --safe-updates.

Q3. DELETE vs TRUNCATE for 'empty staging table' on Postgres?
Model answer: TRUNCATE faster, resets identity, skips triggers, still transactional on PG — prefer TRUNCATE if those match intent.

🤖 Don't fully get this? Learn it with Claude

Stuck on DELETE? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **DELETE** (Databases) and want to truly understand it. Explain DELETE from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **DELETE** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **DELETE** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **DELETE** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes