Skip to main content

Database Optimization & Repair

Overview

Regular database maintenance keeps your WordPress site running smoothly. WP-CLI provides powerful commands to check, repair, and optimize your database—essential for performance, reliability, and troubleshooting.

Understanding Database Maintenance

WordPress databases can degrade over time due to:

  • Table fragmentation - Wasted space from deleted data
  • Overhead - Unused space in tables
  • Corruption - Server crashes, improper shutdowns
  • Bloat - Revisions, transients, spam comments

WP-CLI helps you maintain database health through:

  • Check - Detect table errors
  • Repair - Fix corrupted tables
  • Optimize - Reclaim space and improve performance

Database Checking

Check All Tables

# Check all WordPress tables for errors
wp db check

What it checks:

  • Table structure integrity
  • Index corruption
  • Data consistency

Example output:

wp_posts                                   OK
wp_postmeta OK
wp_users OK
wp_usermeta OK
wp_options OK
wp_comments OK
wp_commentmeta OK

Check Specific Tables

# Check specific tables
wp db check wp_posts wp_postmeta

# Check tables with pattern
wp db tables | grep "woocommerce" | xargs wp db check

Automated Health Checks

#!/bin/bash
# db-health-check.sh

echo "=== Database Health Check ==="
echo "Date: $(date)"
echo ""

# Run check
if wp db check > /dev/null 2>&1; then
echo "✓ All tables OK"
exit 0
else
echo "✗ Database issues detected!"
echo "Running detailed check..."
wp db check

# Send alert
echo "Database issues detected on $(hostname)" | mail -s "DB Alert" admin@example.com
exit 1
fi

Add to cron for daily checks:

0 3 * * * /path/to/db-health-check.sh

Database Repair

Repair All Tables

# Repair all tables
wp db repair

When to use:

  • After server crash
  • "Table is marked as crashed" errors
  • Database corruption warnings
  • Site showing database errors

What it does:

  • Attempts to fix corrupted tables
  • Rebuilds damaged indexes
  • Recovers data when possible

Repair Specific Tables

# Repair specific table
wp db repair wp_posts

# Repair multiple tables
wp db repair wp_posts wp_postmeta wp_options

Emergency Repair Workflow

#!/bin/bash
# emergency-repair.sh

echo "=== Emergency Database Repair ==="

# 1. Backup first (if possible)
echo "Creating emergency backup..."
wp db export emergency-backup-$(date +%Y%m%d-%H%M%S).sql 2>/dev/null || echo "⚠ Backup failed, continuing with repair..."

# 2. Check database
echo "Checking database..."
wp db check

# 3. Repair
echo "Repairing database..."
wp db repair

# 4. Verify repair
echo "Verifying repair..."
if wp db check > /dev/null 2>&1; then
echo "✓ Repair successful!"
else
echo "✗ Repair incomplete - manual intervention required"
exit 1
fi

# 5. Optimize after repair
echo "Optimizing database..."
wp db optimize

echo "=== Emergency Repair Complete ==="

Repair with MySQL Directly

# For severe corruption, use MySQL directly
wp db cli --execute="REPAIR TABLE wp_posts;"

# Repair all tables
wp db tables | xargs -I {} wp db cli --execute="REPAIR TABLE {};"

Database Optimization

Optimize All Tables

# Optimize all WordPress tables
wp db optimize

What it does:

  • Defragments tables
  • Reclaims unused space
  • Rebuilds indexes
  • Improves query performance

Benefits:

  • Faster queries
  • Reduced disk usage
  • Better performance

Optimize Specific Tables

# Optimize specific tables
wp db optimize wp_posts wp_postmeta

# Optimize large tables only
wp db query "
SELECT table_name
FROM information_schema.TABLES
WHERE table_schema = DATABASE()
AND data_length > 10485760
" --skip-column-names | xargs wp db optimize

Scheduled Optimization

#!/bin/bash
# weekly-optimization.sh

echo "=== Weekly Database Optimization ==="
echo "Date: $(date)"

# Get database size before
BEFORE=$(wp db query "
SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2)
FROM information_schema.TABLES
WHERE table_schema = DATABASE();
" --skip-column-names)

echo "Database size before: ${BEFORE}MB"

# Optimize
wp db optimize

# Get database size after
AFTER=$(wp db query "
SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2)
FROM information_schema.TABLES
WHERE table_schema = DATABASE();
" --skip-column-names)

echo "Database size after: ${AFTER}MB"

# Calculate savings
SAVED=$(echo "$BEFORE - $AFTER" | bc)
echo "Space reclaimed: ${SAVED}MB"

echo "=== Optimization Complete ==="

Add to cron (weekly on Sunday at 3 AM):

0 3 * * 0 /path/to/weekly-optimization.sh

Cleaning Database Bloat

Remove Post Revisions

# Count revisions
wp db query "SELECT COUNT(*) FROM wp_posts WHERE post_type='revision';" --skip-column-names

# Delete all revisions
wp db query "DELETE FROM wp_posts WHERE post_type='revision';"

# Optimize after deletion
wp db optimize wp_posts

Clean Transients

# Delete expired transients
wp transient delete --expired

# Delete all transients (use with caution)
wp transient delete --all

# Count transients
wp db query "SELECT COUNT(*) FROM wp_options WHERE option_name LIKE '_transient_%';" --skip-column-names

Remove Spam Comments

# Count spam comments
wp comment list --status=spam --format=count

# Delete spam comments
wp comment delete $(wp comment list --status=spam --format=ids)

# Optimize comments table
wp db optimize wp_comments wp_commentmeta

Clean Trashed Posts

# Count trashed posts
wp post list --post_status=trash --format=count

# Delete trashed posts permanently
wp post delete $(wp post list --post_status=trash --format=ids) --force

# Optimize posts table
wp db optimize wp_posts wp_postmeta

Remove Orphaned Metadata

#!/bin/bash
# clean-orphaned-metadata.sh

echo "Cleaning orphaned metadata..."

# Orphaned postmeta
ORPHANED_POSTMETA=$(wp db query "
SELECT COUNT(*)
FROM wp_postmeta pm
LEFT JOIN wp_posts p ON pm.post_id = p.ID
WHERE p.ID IS NULL;
" --skip-column-names)

echo "Orphaned postmeta entries: $ORPHANED_POSTMETA"

if [ "$ORPHANED_POSTMETA" -gt 0 ]; then
wp db query "
DELETE pm
FROM wp_postmeta pm
LEFT JOIN wp_posts p ON pm.post_id = p.ID
WHERE p.ID IS NULL;
"
echo "✓ Orphaned postmeta cleaned"
fi

# Orphaned commentmeta
ORPHANED_COMMENTMETA=$(wp db query "
SELECT COUNT(*)
FROM wp_commentmeta cm
LEFT JOIN wp_comments c ON cm.comment_id = c.comment_ID
WHERE c.comment_ID IS NULL;
" --skip-column-names)

echo "Orphaned commentmeta entries: $ORPHANED_COMMENTMETA"

if [ "$ORPHANED_COMMENTMETA" -gt 0 ]; then
wp db query "
DELETE cm
FROM wp_commentmeta cm
LEFT JOIN wp_comments c ON cm.comment_id = c.comment_ID
WHERE c.comment_ID IS NULL;
"
echo "✓ Orphaned commentmeta cleaned"
fi

# Orphaned usermeta
ORPHANED_USERMETA=$(wp db query "
SELECT COUNT(*)
FROM wp_usermeta um
LEFT JOIN wp_users u ON um.user_id = u.ID
WHERE u.ID IS NULL;
" --skip-column-names)

echo "Orphaned usermeta entries: $ORPHANED_USERMETA"

if [ "$ORPHANED_USERMETA" -gt 0 ]; then
wp db query "
DELETE um
FROM wp_usermeta um
LEFT JOIN wp_users u ON um.user_id = u.ID
WHERE u.ID IS NULL;
"
echo "✓ Orphaned usermeta cleaned"
fi

echo "Orphaned metadata cleanup complete!"

Comprehensive Maintenance Script

#!/bin/bash
# comprehensive-db-maintenance.sh

echo "========================================="
echo " WordPress Database Maintenance"
echo "========================================="
echo "Started: $(date)"
echo ""

# Configuration
BACKUP_DIR="/backups/wordpress"
RETENTION_DAYS=7

# Create backup directory
mkdir -p $BACKUP_DIR

# 1. BACKUP
echo "1. Creating backup..."
BACKUP_FILE="$BACKUP_DIR/maintenance-backup-$(date +%Y%m%d-%H%M%S).sql.gz"
wp db export "$BACKUP_FILE" --add-drop-table --single-transaction
echo "✓ Backup created: $BACKUP_FILE"
echo ""

# 2. DATABASE SIZE (BEFORE)
echo "2. Database size analysis..."
SIZE_BEFORE=$(wp db query "
SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2)
FROM information_schema.TABLES
WHERE table_schema = DATABASE();
" --skip-column-names)
echo "Database size: ${SIZE_BEFORE}MB"
echo ""

# 3. CHECK DATABASE
echo "3. Checking database integrity..."
if wp db check > /dev/null 2>&1; then
echo "✓ Database check passed"
else
echo "⚠ Database issues detected - running repair..."
wp db repair
fi
echo ""

# 4. CLEAN REVISIONS
echo "4. Cleaning post revisions..."
REVISIONS=$(wp db query "SELECT COUNT(*) FROM wp_posts WHERE post_type='revision';" --skip-column-names)
if [ "$REVISIONS" -gt 0 ]; then
echo "Deleting $REVISIONS revisions..."
wp db query "DELETE FROM wp_posts WHERE post_type='revision';"
echo "✓ Revisions cleaned"
else
echo "No revisions to clean"
fi
echo ""

# 5. CLEAN TRANSIENTS
echo "5. Cleaning expired transients..."
wp transient delete --expired
echo "✓ Transients cleaned"
echo ""

# 6. CLEAN SPAM COMMENTS
echo "6. Cleaning spam comments..."
SPAM_COUNT=$(wp comment list --status=spam --format=count)
if [ "$SPAM_COUNT" -gt 0 ]; then
echo "Deleting $SPAM_COUNT spam comments..."
wp comment delete $(wp comment list --status=spam --format=ids)
echo "✓ Spam comments cleaned"
else
echo "No spam comments to clean"
fi
echo ""

# 7. CLEAN TRASHED POSTS
echo "7. Cleaning trashed posts..."
TRASH_COUNT=$(wp post list --post_status=trash --format=count)
if [ "$TRASH_COUNT" -gt 0 ]; then
echo "Deleting $TRASH_COUNT trashed posts..."
wp post delete $(wp post list --post_status=trash --format=ids) --force
echo "✓ Trashed posts cleaned"
else
echo "No trashed posts to clean"
fi
echo ""

# 8. CLEAN ORPHANED METADATA
echo "8. Cleaning orphaned metadata..."
# Orphaned postmeta
wp db query "
DELETE pm
FROM wp_postmeta pm
LEFT JOIN wp_posts p ON pm.post_id = p.ID
WHERE p.ID IS NULL;
" 2>/dev/null
# Orphaned commentmeta
wp db query "
DELETE cm
FROM wp_commentmeta cm
LEFT JOIN wp_comments c ON cm.comment_id = c.comment_ID
WHERE c.comment_ID IS NULL;
" 2>/dev/null
echo "✓ Orphaned metadata cleaned"
echo ""

# 9. OPTIMIZE DATABASE
echo "9. Optimizing database..."
wp db optimize
echo "✓ Database optimized"
echo ""

# 10. DATABASE SIZE (AFTER)
echo "10. Final database size..."
SIZE_AFTER=$(wp db query "
SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2)
FROM information_schema.TABLES
WHERE table_schema = DATABASE();
" --skip-column-names)
echo "Database size: ${SIZE_AFTER}MB"

# Calculate savings
SAVED=$(echo "$SIZE_BEFORE - $SIZE_AFTER" | bc)
echo "Space reclaimed: ${SAVED}MB"
echo ""

# 11. CLEANUP OLD BACKUPS
echo "11. Cleaning old backups..."
find $BACKUP_DIR -name "maintenance-backup-*.sql.gz" -mtime +$RETENTION_DAYS -delete
echo "✓ Old backups cleaned (retention: $RETENTION_DAYS days)"
echo ""

echo "========================================="
echo " Maintenance Complete!"
echo "========================================="
echo "Finished: $(date)"

Add to cron (weekly on Sunday at 2 AM):

0 2 * * 0 /path/to/comprehensive-db-maintenance.sh >> /var/log/wp-db-maintenance.log 2>&1

Performance Monitoring

Database Size Tracking

#!/bin/bash
# track-db-size.sh

LOG_FILE="/var/log/wp-db-size.log"

SIZE=$(wp db query "
SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2)
FROM information_schema.TABLES
WHERE table_schema = DATABASE();
" --skip-column-names)

echo "$(date +%Y-%m-%d\ %H:%M:%S),$SIZE" >> $LOG_FILE

echo "Current database size: ${SIZE}MB"

Add to cron (daily):

0 0 * * * /path/to/track-db-size.sh

Table Size Analysis

#!/bin/bash
# analyze-table-sizes.sh

echo "=== Database Table Size Analysis ==="
echo ""

wp db query "
SELECT
table_name AS 'Table',
ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'Size (MB)',
table_rows AS 'Rows',
ROUND(((data_length + index_length) / table_rows), 2) AS 'Avg Row Size (bytes)'
FROM information_schema.TABLES
WHERE table_schema = DATABASE()
ORDER BY (data_length + index_length) DESC;
"

Best Practices

1. Regular Maintenance Schedule

# Daily: Check database health
0 3 * * * wp db check

# Weekly: Optimize database
0 2 * * 0 wp db optimize

# Monthly: Comprehensive maintenance
0 1 1 * * /path/to/comprehensive-db-maintenance.sh

2. Always Backup Before Maintenance

# Before any maintenance operation
wp db export pre-maintenance-$(date +%Y%m%d).sql

3. Monitor Database Growth

# Track size over time
wp db query "SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) FROM information_schema.TABLES WHERE table_schema = DATABASE();"

4. Clean Regularly

# Weekly cleanup routine
wp transient delete --expired
wp comment delete $(wp comment list --status=spam --format=ids)

5. Test on Staging First

# Test maintenance scripts on staging before production
# Clone production to staging
# Run maintenance
# Verify results
# Then apply to production

Quick Reference

Essential Commands

# Check database
wp db check # Check all tables
wp db check wp_posts wp_postmeta # Check specific tables

# Repair database
wp db repair # Repair all tables
wp db repair wp_posts # Repair specific table

# Optimize database
wp db optimize # Optimize all tables
wp db optimize wp_posts wp_postmeta # Optimize specific tables

# Clean data
wp transient delete --expired # Delete expired transients
wp comment delete $(wp comment list --status=spam --format=ids) # Delete spam
wp post delete $(wp post list --post_status=trash --format=ids) --force # Delete trash

# Analysis
wp db query "SHOW TABLE STATUS;" # Table statistics
wp db tables # List all tables

Troubleshooting

Repair Fails

# Try MySQL repair directly
wp db cli --execute="REPAIR TABLE wp_posts;"

# Check MySQL error log
tail -f /var/log/mysql/error.log

# Verify table engine
wp db query "SHOW TABLE STATUS WHERE Name='wp_posts';"

Optimization Takes Too Long

# Optimize tables individually
for table in $(wp db tables); do
echo "Optimizing $table..."
wp db optimize "$table"
done

# Or optimize only large tables
wp db query "
SELECT table_name
FROM information_schema.TABLES
WHERE table_schema = DATABASE()
AND data_length > 10485760;
" --skip-column-names | xargs wp db optimize

Summary

OperationCommandFrequencyPurpose
Checkwp db checkDailyDetect errors
Repairwp db repairAs neededFix corruption
Optimizewp db optimizeWeeklyImprove performance
Clean RevisionsCustom SQLMonthlyReduce bloat
Clean Transientswp transient delete --expiredWeeklyRemove expired data
Clean Spamwp comment deleteWeeklyRemove spam
Key Takeaway

Regular database maintenance is essential for performance, reliability, and site health. Automate checks, repairs, and optimization with cron jobs for hands-free maintenance.

Next Steps