MySQL DELETE: Complete Guide
Remove data from MySQL tables safely and efficiently.
Basic DELETE Syntax
The DELETE statement removes rows from a table:
For example, deleting a specific user:
The Cardinal Rule: Always Use WHERE
A DELETE without WHERE removes every row in the table:
This is rarely what you want. Always include a WHERE clause:
Safety tip: In production, run a SELECT with the same WHERE clause first to verify which rows will be affected:
SELECT * FROM users WHERE status = 'deleted'
DELETE with LIMIT
Limit how many rows are deleted—useful for batching large deletes:
Run this repeatedly until no rows are affected. This prevents long-running transactions that lock the table.
DELETE with ORDER BY
Control which rows are deleted when using LIMIT:
DELETE with JOIN
Delete rows based on data in another table:
The table to delete from comes right after DELETE. The FROM clause specifies the joined tables.
Multi-Table DELETE
Delete from multiple tables in one statement:
DELETE vs TRUNCATE
For removing all rows, TRUNCATE is faster:
| Feature | DELETE | TRUNCATE | |---------|--------|----------| | WHERE clause | Yes | No | | Triggers fire | Yes | No | | Can rollback | Yes | No (in most cases) | | Resets AUTO_INCREMENT | No | Yes | | Speed | Slow (row by row) | Fast |
Use TRUNCATE when:
- You want to remove ALL rows
- You don't need triggers to fire
- You want to reset AUTO_INCREMENT
Soft Delete Pattern
Instead of actually deleting data, mark it as deleted:
Benefits:
- Recoverable: Can "undelete" by setting deleted_at back to NULL
- Audit trail: Know when something was deleted
- Referential integrity: Foreign keys still work
Downsides:
- Query complexity: Must filter deleted_at everywhere
- Storage: Deleted data still takes space
- Indexes: May need partial indexes
Safe Deletion Workflow
- Preview what will be deleted:
- Count the rows:
- Back up if important:
- Delete with LIMIT first:
- Verify, then delete the rest:
Foreign Key Constraints
DELETE can fail if foreign keys reference the row:
Options:
1. Delete child rows first
2. Use ON DELETE CASCADE
3. Use ON DELETE SET NULL
Performance Considerations
Large Deletes Lock Tables
Deleting millions of rows in one statement can lock the table for a long time:
Better approach—delete in batches:
Or use a simple script that loops until done.
Indexes Matter
Ensure your WHERE clause columns are indexed:
Quick Reference
| Command | Description |
|---------|-------------|
| DELETE FROM t WHERE ... | Delete matching rows |
| DELETE FROM t WHERE ... LIMIT n | Delete up to n rows |
| DELETE FROM t ORDER BY ... LIMIT n | Delete specific n rows |
| DELETE t FROM t JOIN ... | Delete using JOIN |
| TRUNCATE TABLE t | Remove all rows (fast) |
Common Mistakes
Forgetting WHERE
Wrong JOIN Syntax
Not Testing First
Always run SELECT with your WHERE clause before DELETE.
Summary
- Always use WHERE unless you want to delete all rows
- SELECT first to preview what will be deleted
- Use LIMIT for large deletes to avoid long locks
- Consider soft deletes for data you might need to recover
- Watch for foreign keys—they'll block deletes or cascade unexpectedly
- TRUNCATE is faster when you want to remove everything