MySQL DELETE
Overview
The DELETE statement removes rows from a table. This chapter covers how to delete specific rows, use conditions, and implement soft delete patterns.
DELETE Syntax
sql
DELETE FROM table_name WHERE condition;Basic DELETE
Delete Specific Rows
sql
-- Delete single row
DELETE FROM users WHERE id = 1;
-- Delete multiple rows
DELETE FROM users WHERE status = 'inactive' AND last_login < '2022-01-01';
-- Delete with multiple conditions
DELETE FROM products WHERE stock = 0 AND discontinued = 1;Delete All Rows
sql
-- Delete all rows
DELETE FROM temporary_data;
-- Delete all rows (DANGEROUS!)
DELETE FROM logs; -- All records deleted
-- Better: Use TRUNCATE for better performance
TRUNCATE TABLE logs;DELETE with Conditions
WHERE Clause
sql
-- Delete based on ID
DELETE FROM users WHERE id IN (1, 2, 3, 4, 5);
-- Delete with pattern
DELETE FROM logs WHERE message LIKE '%error%' AND created_at < '2024-01-01';
-- Delete with subquery
DELETE FROM orders WHERE user_id IN (
SELECT user_id FROM users WHERE status = 'banned'
);
-- Delete with EXISTS
DELETE FROM products WHERE NOT EXISTS (
SELECT 1 FROM categories WHERE categories.id = products.category_id
);ORDER BY and LIMIT
sql
-- Delete with ORDER BY
DELETE FROM logs ORDER BY created_at LIMIT 1000;
-- Delete oldest 100 records
DELETE FROM audit_log
ORDER BY created_at ASC
LIMIT 100;
-- Delete with ORDER BY and LIMIT
DELETE FROM sessions
ORDER BY last_activity ASC
LIMIT 1000
WHERE expired = 1;JOIN in DELETE
sql
-- Delete with INNER JOIN
DELETE u FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.status = 'cancelled';
-- Delete with LEFT JOIN
DELETE p FROM products p
LEFT JOIN categories c ON p.category_id = c.id
WHERE c.id IS NULL;
-- Delete with multiple tables
DELETE o, oi FROM orders o
INNER JOIN order_items oi ON o.id = oi.order_id
WHERE o.status = 'cancelled';Soft Delete
Add Deleted Column
sql
-- Add soft delete column
ALTER TABLE users ADD COLUMN deleted_at TIMESTAMP NULL DEFAULT NULL;
ALTER TABLE users ADD COLUMN deleted_by INT DEFAULT NULL;
-- Create index for performance
CREATE INDEX idx_users_deleted ON users(deleted_at);Implement Soft Delete
sql
-- Instead of DELETE, use UPDATE
UPDATE users SET
deleted_at = NOW(),
deleted_by = 1,
status = 'deleted'
WHERE id = 1;
-- Query with soft delete filter
SELECT * FROM users WHERE deleted_at IS NULL;
-- Include deleted records
SELECT * FROM users; -- Shows all including deleted
-- Include deleted with filter
SELECT * FROM users WHERE deleted_at IS NOT NULL;Soft Delete Benefits
sql
-- Data preserved for audit
SELECT * FROM users WHERE id = 1; -- Shows record even if deleted
-- Recovery possible
UPDATE users SET deleted_at = NULL, status = 'active' WHERE id = 1;
-- No foreign key issues
-- Related records remain intactBulk Delete Operations
Chunked Deletion
sql
-- Delete in chunks to avoid locking
DELETE FROM logs WHERE created_at < '2024-01-01' LIMIT 1000;
-- Repeat until all deleted
-- Use stored procedure
DELIMITER //
CREATE PROCEDURE CleanupOldLogs()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE batch_size INT DEFAULT 1000;
REPEAT
DELETE FROM logs
WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR)
LIMIT batch_size;
UNTIL ROW_COUNT() = 0
END REPEAT;
END //
DELIMITER ;
CALL CleanupOldLogs();Scheduled Cleanup
bash
#!/bin/bash
# cleanup.sh - Delete old logs in chunks
while true; do
mysql -u root -p -e "
DELETE FROM logs
WHERE created_at < DATE_SUB(NOW(), INTERVAL 90 DAY)
LIMIT 10000;
"
if [ $? -ne 0 ]; then
break
fi
sleep 5
doneDELETE vs TRUNCATE
| Aspect |-------------|--------|----------| | Syntax | WHERE clause | Performance | AUTO_INCREMENT | Triggers | Transaction | Rollback
Safe DELETE Practices
Always Use WHERE
sql
-- DANGEROUS: Deletes all rows
DELETE FROM users; -- Don't do this!
-- SAFE: With WHERE clause
DELETE FROM users WHERE id = 1;Backup Before Delete
sql
-- Create backup
CREATE TABLE deleted_users_backup AS
SELECT * FROM users WHERE id IN (1, 2, 3);
-- Or use transaction
START TRANSACTION;
DELETE FROM users WHERE id IN (1, 2, 3);
-- Verify
-- COMMIT; or ROLLBACK;Test with SELECT First
sql
-- First, verify what will be deleted
SELECT * FROM users WHERE status = 'inactive' AND last_login < '2022-01-01';
SELECT COUNT(*) FROM users WHERE status = 'inactive' AND last_login < '2022-01-01';
-- Then delete
DELETE FROM users WHERE status = 'inactive' AND last_login < '2022-01-01';Common DELETE Patterns
Delete Old Records
sql
-- Delete records older than 1 year
DELETE FROM audit_log WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR);
-- Delete records older than specific date
DELETE FROM sessions WHERE expires_at < NOW();
-- Delete duplicate records
DELETE u1 FROM users u1
INNER JOIN users u2
WHERE u1.id < u2.id AND u1.email = u2.email;Delete Based on Related Tables
sql
-- Delete orphaned records
DELETE FROM products WHERE category_id NOT IN (SELECT id FROM categories);
-- Delete with cascade simulation
DELETE o FROM orders o
WHERE NOT EXISTS (SELECT 1 FROM users u WHERE u.id = o.user_id);
-- Delete old cart items
DELETE FROM cart_items
WHERE cart_id IN (
SELECT id FROM carts WHERE updated_at < DATE_SUB(NOW(), INTERVAL 30 DAY)
);Troubleshooting
Common Errors
sql
-- Foreign key constraint error
-- Error: Cannot delete or update a parent row
-- Solution: Delete child records first or use ON DELETE CASCADE
-- Lock wait timeout
-- Error: Lock wait timeout exceeded
-- Solution: Delete in smaller batches
-- Table is read only
-- Error: Table is read only
-- Solution: Check table engine and permissionsDebug DELETE
sql
-- Check affected rows
SELECT ROW_COUNT();
-- Show tables using row
SHOW OPEN TABLES WHERE In_use > 0;
-- Check locks
SHOW ENGINE INNODB STATUS;Summary
DELETE statement includes:
- Basic Syntax: Remove rows with WHERE
- WHERE Clause: Filter rows to delete
- JOIN Deletes: Delete from multiple tables
- Soft Delete: Preserve data with deleted_at
- Bulk Operations: Chunked deletion
- Safety: Always use WHERE, backup first
- TRUNCATE: Faster alternative for full table
Previous: UPDATE
Next: LIKE Clause