MySQL Çöküşü Sonrası Veritabanı Kurtarma Yöntemi
Gece 2’de telefonun çalması, ekranında “MySQL down – site erişilemiyor” yazması… Her DBA’in korkulu rüyası. Panik yapmadan, sistematik bir şekilde ilerlemek gerekiyor. Bu yazıda MySQL crash sonrasında nasıl kurtarma yapacağını, hangi araçları kullanacağını ve bir daha aynı duruma düşmemek için neler yapabileceğini anlatacağım. Gerçek senaryolardan öğrenilmiş dersler eşliğinde.
MySQL Crash Neden Olur?
Kurtarmaya geçmeden önce neyle karşılaştığını anlamak önemli. MySQL’in çökmesi birkaç temel nedenden kaynaklanır:
- InnoDB corruption: En yaygın senaryo. Disk yazma hatası, güç kesintisi veya kernel bug’ı sonucunda InnoDB tablespace bozulur
- Yetersiz bellek (OOM Killer): Linux kernel, MySQL process’ini bellek baskısı altında öldürür
- Disk dolması: Binary log veya data dizini bulunduğu partition dolduğunda MySQL write yapamaz hale gelir
- Dosya sistemi hatası: ext4 veya XFS’te journal corruption yaşanabilir
- Yanlış yapılandırma: my.cnf’te yapılan bir değişiklik sonrası MySQL başlamayı reddeder
Her birinin kurtarma yöntemi farklı, bu yüzden önce tanı koymak şart.
Adım 1: Durumu Değerlendirmek
Sunucuya bağlandığında ilk yapacağın şey panik değil, log okumak olmalı.
# MySQL servis durumunu kontrol et
systemctl status mysql
# veya MariaDB kullanıyorsan
systemctl status mariadb
# Son 50 satır log
journalctl -u mysql -n 50 --no-pager
# MySQL error log konumunu bul ve oku
grep "log_error" /etc/mysql/mysql.conf.d/mysqld.cnf
tail -100 /var/log/mysql/error.log
Error log’da şunları arıyorsun:
- InnoDB: Database was not shutdown normally satırı – beklenmedik kapanma olduğunu gösterir
- [ERROR] InnoDB: Corruption – tablespace bozulması
- [ERROR] Fatal error: Can’t open and lock privilege tables – sistem tablolarında sorun
- Out of memory veya Killed – OOM durumu
Disk durumunu da kontrol etmeyi unutma:
# Disk kullanımı
df -h
# Disk I/O hatalarını kontrol et
dmesg | grep -i "error|fail|corrupt" | tail -30
# MySQL data dizininin durumu
ls -la /var/lib/mysql/
Adım 2: InnoDB Recovery Mode ile Başlatma
MySQL başlamıyorsa ve error log’da InnoDB corruption görüyorsan, innodb_force_recovery parametresi hayat kurtarır. Bu parametre MySQL’i kademeli olarak daha agresif recovery modlarında başlatmana izin verir.
# my.cnf veya mysqld.cnf dosyasını düzenle
nano /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld] bölümüne şunu ekle:
[mysqld]
innodb_force_recovery = 1
Ardından MySQL’i başlatmayı dene:
systemctl start mysql
# Başladıysa hemen dump al, bekletme!
mysqldump --all-databases > /backup/emergency_dump_$(date +%Y%m%d_%H%M).sql
Eğer 1 ile başlamıyorsa, kademeli olarak artır. Her seviyenin ne yaptığını bilmek önemli:
- 1: Corruption tespit edildiğinde crash yerine devam eder
- 2: Background thread’leri devre dışı bırakır
- 3: Transaction rollback’i atlar
- 4: Insert buffer merge’i atlar
- 5: Undo log’u yok sayar
- 6: Bozuk sayfalara bile erişmeye çalışır (en son çare)
Kritik uyarı: innodb_force_recovery 0’dan büyük olan herhangi bir değerde MySQL’i production olarak kesinlikle çalıştırma. Bu mod sadece veri kurtarma amaçlıdır. Dump aldıktan sonra temiz kurulum yapman gerekecek.
# Recovery mode 4 ile dene (genellikle bu yeterli olur)
nano /etc/mysql/mysql.conf.d/mysqld.cnf
# innodb_force_recovery = 4 yap
systemctl start mysql
# Başarılı olursa tüm veritabanlarını dump et
mysqldump -u root -p --all-databases --single-transaction --quick
--triggers --routines --events
> /backup/full_recovery_$(date +%Y%m%d_%H%M).sql
echo "Dump boyutu: $(du -sh /backup/full_recovery_*.sql | tail -1)"
Adım 3: ibdata1 ve InnoDB Dosyalarını Onarmak
Bazı durumlarda ibdata1 dosyası bozulur ama tablespace dosyaları (.ibd) sağlamdır. Bu durumda dosyaları taşıyarak kurtarabilirsin.
# Mevcut InnoDB dosyalarını yedekle
cp -a /var/lib/mysql/ibdata1 /backup/ibdata1.bak
cp -a /var/lib/mysql/ib_logfile0 /backup/ib_logfile0.bak
cp -a /var/lib/mysql/ib_logfile1 /backup/ib_logfile1.bak
# MySQL durdur
systemctl stop mysql
# Log dosyalarını sil (bunlar yeniden oluşturulacak)
rm /var/lib/mysql/ib_logfile0
rm /var/lib/mysql/ib_logfile1
Log dosyalarını silmek MySQL’i yeniden başlatırken sıfırdan oluşturmasını sağlar. ibdata1 çok büyüdüyse veya tamamen bozuksa, per-table tablespace kullanılıyorsa (.ibd dosyaları varsa) işin daha kolay.
# Her veritabanı için ayrı .ibd dosyası var mı kontrol et
ls /var/lib/mysql/veritabani_adi/
# innodb_file_per_table aktif mi?
mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_file_per_table';"
Adım 4: MyISAM Tablolarını Onarmak
MySQL’in eski kurulumlarında veya bazı sistem tablolarında MyISAM kullanılıyor. myisamchk ve REPAIR TABLE bu tablolar için çalışır.
# Önce MySQL'i durdur (myisamchk için)
systemctl stop mysql
# Tüm .MYI dosyalarını tara ve onar
find /var/lib/mysql -name "*.MYI" -exec myisamchk --recover --quick {} ;
# Daha agresif onarım gerekiyorsa
find /var/lib/mysql -name "*.MYI" -exec myisamchk --recover --force {} ;
# MySQL'i başlat
systemctl start mysql
MySQL çalışırken de onarım yapabilirsin:
mysql -u root -p
# Hangi tablolar bozuk?
CHECK TABLE veritabani.tablo_adi;
# Onar
REPAIR TABLE veritabani.tablo_adi;
# Tüm tablolar için extended check
CHECK TABLE veritabani.tablo_adi EXTENDED;
Adım 5: Binary Log ile Point-in-Time Recovery
Elinde yedek var ama son crash’ten önceki birkaç saatlik işlemleri de kurtarmak istiyorsun. Binary log aktifse bu mümkün.
# Binary log aktif mi?
mysql -u root -p -e "SHOW VARIABLES LIKE 'log_bin';"
mysql -u root -p -e "SHOW BINARY LOGS;"
# Binary log dosyalarını listele
ls -lh /var/lib/mysql/mysql-bin.*
# Belirli bir zaman aralığındaki işlemleri göster
mysqlbinlog --start-datetime="2024-01-15 08:00:00"
--stop-datetime="2024-01-15 14:30:00"
/var/lib/mysql/mysql-bin.000123
Diyelim ki saat 14:30’da yedek aldın ama crash 17:45’te oldu. 14:30-17:45 arasındaki işlemleri kurtarmak için:
# Önce yedeği geri yükle
mysql -u root -p < /backup/backup_2024_01_15_1430.sql
# Sonra binary log'dan eksik işlemleri uygula
mysqlbinlog --start-datetime="2024-01-15 14:30:00"
--stop-datetime="2024-01-15 17:44:00"
/var/lib/mysql/mysql-bin.000123
/var/lib/mysql/mysql-bin.000124 | mysql -u root -p
echo "Point-in-time recovery tamamlandı"
Eğer belirli bir transaction’a kadar gitmek istiyorsan position numarasını kullan:
# Binlog'da belirli bir noktayı bul
mysqlbinlog /var/lib/mysql/mysql-bin.000123 | grep -A 2 "DROP TABLE"
# O noktaya kadar uygula
mysqlbinlog --start-position=4
--stop-position=158456
/var/lib/mysql/mysql-bin.000123 | mysql -u root -p
Adım 6: Veri Dizinini Sıfırlayıp Yedeği Geri Yüklemek
Hiçbir şey işe yaramıyorsa nükleer seçenek: data dizinini temizleyip sıfırdan başlamak.
# MySQL durdur
systemctl stop mysql
# Veri dizinini yedekle (silmeden önce!)
mv /var/lib/mysql /var/lib/mysql_corrupted_$(date +%Y%m%d)
# Yeni veri dizini oluştur
mkdir /var/lib/mysql
chown mysql:mysql /var/lib/mysql
chmod 750 /var/lib/mysql
# MySQL'i initialize et
mysqld --initialize --user=mysql
# veya eski versiyonlarda
mysql_install_db --user=mysql --datadir=/var/lib/mysql
# MySQL başlat
systemctl start mysql
# Geçici şifre log'da
grep "temporary password" /var/log/mysql/error.log
# Şifreyi değiştir
mysql -u root -p
ALTER USER 'root'@'localhost' IDENTIFIED BY 'yeni_guclu_sifre';
FLUSH PRIVILEGES;
# Yedeği geri yükle
mysql -u root -p < /backup/emergency_dump_TARIH.sql
Gerçek Dünya Senaryosu: E-Ticaret Sitesi
Geçen yıl yaşadığım bir vakadan bahsedeyim. 50.000 üyeli bir e-ticaret sitesinin MySQL’i gece 3’te çöktü. Error log şunu gösteriyordu:
[ERROR] InnoDB: Database page corruption on disk or a failed file read of page [page id: space=23, page number=1591].
[ERROR] InnoDB: Unable to read page [page id: space=23, page number=1591] into the buffer pool after 100 attempts.
Bu klasik bir InnoDB page corruption vakasıydı. Adımlar şöyle ilerledi:
# 1. Recovery mode 1 ile başlamayı dene
echo "innodb_force_recovery = 1" >> /etc/mysql/mysql.conf.d/mysqld.cnf
systemctl start mysql
# HATA: Başlamadı
# 2. Recovery mode 3 ile dene
sed -i 's/innodb_force_recovery = 1/innodb_force_recovery = 3/' /etc/mysql/mysql.conf.d/mysqld.cnf
systemctl start mysql
# BASARI: Başladı!
# 3. Hemen dump al
mysqldump -u root -p --all-databases
--single-transaction
--quick
--max_allowed_packet=512M
> /backup/emergency_$(date +%Y%m%d_%H%M).sql
# 4. Dump boyutunu kontrol et (normal boyutta mı?)
du -sh /backup/emergency_*.sql
# 5. Hangi tablo bozuk? Kontrol et
mysql -u root -p -e "SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema NOT IN ('information_schema','performance_schema','mysql','sys');"
> /tmp/table_list.txt
# Her tabloyu check et
while IFS=$'t' read -r schema table; do
echo "Checking: $schema.$table"
mysql -u root -p"sifre" -e "CHECK TABLE `$schema`.`$table`;" 2>/dev/null | grep -v "^$schema"
done < /tmp/table_list.txt
Bozuk tablo orders tablosuydu. 3 sayfa corrupt olmuştu ama mysqldump bu tabloyu yine de dışa aktarabildi (recovery mode 3 sayesinde). Sıfırdan kurulum yapıp dump’ı geri yükledik, 47 dakikada site ayağa kalktı.
Adım 7: Sistem Tablolarını Kurtarma
Bazen bozulma mysql veritabanında (sistem tabloları) olur. Bu durumda kimse giriş yapamaz.
# MySQL'i skip-grant-tables ile başlat
echo "skip-grant-tables" >> /etc/mysql/mysql.conf.d/mysqld.cnf
systemctl restart mysql
# Şimdi şifresiz giriş yap
mysql -u root
# Sistem tablolarını onar
USE mysql;
REPAIR TABLE user, db, tables_priv, columns_priv, procs_priv;
# mysql_upgrade çalıştır
exit
# skip-grant-tables satırını kaldır
sed -i '/skip-grant-tables/d' /etc/mysql/mysql.conf.d/mysqld.cnf
systemctl restart mysql
# Upgrade kontrolü yap
mysql_upgrade -u root -p
OOM Killer Kurbanı MySQL’i Kurtarmak
OOM durumunda MySQL process’i öldürülür ama veri bütünlüğü genellikle korunur. Sorun şu: aynı durum tekrar yaşanacak.
# OOM kill oldu mu kontrol et
dmesg | grep -i "out of memory" | tail -20
grep "oom" /var/log/syslog | tail -20
# MySQL bellek kullanımını kontrol et
mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
# Anlık bellek durumu
free -h
ps aux --sort=-%mem | head -10
OOM sonrası MySQL genellikle düzgün başlar ama önlem almak şart:
# my.cnf'te bellek ayarlarını düzenle
nano /etc/mysql/mysql.conf.d/mysqld.cnf
Şu parametreleri sunucunun RAM’ine göre ayarla:
- innodb_buffer_pool_size: Toplam RAM’in %50-70’i (dedicated MySQL sunucu için)
- innodb_log_buffer_size: 16M-64M arası yeterli
- query_cache_size: MySQL 5.7’de 0 yap, 8.0’da kaldırıldı
- max_connections: Her connection bellek tüketir, gerçekçi bir değer koy
# OOM Killer'ın MySQL'i öldürmesini zorlaştır
echo -17 > /proc/$(pgrep mysqld)/oom_adj
# veya modern sistemlerde
echo -1000 > /proc/$(pgrep mysqld)/oom_score_adj
Önleyici Tedbirler: Bir Daha Aynı Duruma Düşmemek
Kurtarma bitti, şimdi sıra önleme alınmaya geldi.
# 1. Binary log'u aktif et (yoksa)
echo "log_bin = /var/lib/mysql/mysql-bin" >> /etc/mysql/mysql.conf.d/mysqld.cnf
echo "binlog_expire_logs_seconds = 604800" >> /etc/mysql/mysql.conf.d/mysqld.cnf
echo "sync_binlog = 1" >> /etc/mysql/mysql.conf.d/mysqld.cnf
# 2. InnoDB'yu daha güvenli yapılandır
echo "innodb_flush_log_at_trx_commit = 1" >> /etc/mysql/mysql.conf.d/mysqld.cnf
echo "innodb_file_per_table = ON" >> /etc/mysql/mysql.conf.d/mysqld.cnf
# 3. Otomatik yedekleme scripti
cat > /usr/local/bin/mysql_backup.sh << 'EOF'
#!/bin/bash
BACKUP_DIR="/backup/mysql"
DATE=$(date +%Y%m%d_%H%M)
MYSQL_USER="backup_user"
MYSQL_PASS="backup_password"
mkdir -p $BACKUP_DIR
mysqldump -u $MYSQL_USER -p$MYSQL_PASS
--all-databases
--single-transaction
--quick
--flush-logs
--master-data=2
| gzip > $BACKUP_DIR/full_backup_$DATE.sql.gz
# 7 günden eski yedekleri sil
find $BACKUP_DIR -name "*.sql.gz" -mtime +7 -delete
echo "Yedek alındı: $BACKUP_DIR/full_backup_$DATE.sql.gz"
EOF
chmod +x /usr/local/bin/mysql_backup.sh
# Crontab'a ekle - her gece 02:00'de
echo "0 2 * * * root /usr/local/bin/mysql_backup.sh >> /var/log/mysql_backup.log 2>&1" > /etc/cron.d/mysql_backup
# 4. MySQL sağlık kontrolü için monitoring scripti
cat > /usr/local/bin/mysql_health_check.sh << 'EOF'
#!/bin/bash
ALERT_EMAIL="[email protected]"
# MySQL erişilebilir mi?
if ! mysqladmin -u monitor_user -pmonitor_pass ping > /dev/null 2>&1; then
echo "KRITIK: MySQL cevap vermiyor!" | mail -s "MySQL DOWN" $ALERT_EMAIL
exit 1
fi
# Slave lag kontrolü (replica varsa)
SLAVE_LAG=$(mysql -u monitor_user -pmonitor_pass -e "SHOW SLAVE STATUSG" 2>/dev/null | grep "Seconds_Behind_Master" | awk '{print $2}')
if [ "$SLAVE_LAG" -gt 300 ] 2>/dev/null; then
echo "UYARI: Replica 5 dakikadan fazla geride: $SLAVE_LAG saniye" | mail -s "MySQL Replica Lag" $ALERT_EMAIL
fi
# Disk kontrolü
DISK_USAGE=$(df /var/lib/mysql | awk 'NR==2{print $5}' | tr -d '%')
if [ "$DISK_USAGE" -gt 85 ]; then
echo "UYARI: MySQL disk kullanimi %$DISK_USAGE" | mail -s "MySQL Disk Uyarisi" $ALERT_EMAIL
fi
echo "$(date): MySQL sagliklı" >> /var/log/mysql_health.log
EOF
chmod +x /usr/local/bin/mysql_health_check.sh
echo "*/5 * * * * root /usr/local/bin/mysql_health_check.sh" > /etc/cron.d/mysql_health
Percona XtraBackup ile Hot Backup
Production sistemlerde mysqldump yerine Percona XtraBackup kullanmak çok daha iyi. Çalışan sistemi durdurmadan hot backup alırsın:
# Percona XtraBackup kur
apt install percona-xtrabackup-80 # Ubuntu/Debian için
# veya
yum install percona-xtrabackup-80 # RHEL/CentOS için
# Full backup al
xtrabackup --backup
--user=root
--password=sifre
--target-dir=/backup/xtrabackup/full_$(date +%Y%m%d)
# Backup'ı geri yüklemeye hazırla
xtrabackup --prepare
--target-dir=/backup/xtrabackup/full_20240115
# Geri yükle
systemctl stop mysql
rsync -avrP /backup/xtrabackup/full_20240115/ /var/lib/mysql/
chown -R mysql:mysql /var/lib/mysql
systemctl start mysql
Sonuç
MySQL crash durumunda paniklemek yerine sistematik ilerlemek her şeyi değiştiriyor. Özet olarak:
- Önce log oku: Neyle karşılaştığını anlamadan müdahale etme
- innodb_force_recovery: En değerli aracın, 1’den başlayıp kademeli artır
- Dump’ı mümkün olan en kısa sürede al: MySQL kurtarma modunda ayağa kalkar kalkmaz hemen dump’a geç
- Binary log altın: Aktif binary log olmadan point-in-time recovery imkansız
- innodb_file_per_table açık olsun: Her tablo ayrı dosyada olursa sadece bozuk tabloyu kurtarırsın
En önemlisi: test edilmemiş yedek, yedek değildir. Haftada bir yedekten restore dene, gerçek ihtiyaç anında sürprizle karşılaşma. Monitoring ve otomatik yedekleme kurulduktan sonra gece 2’deki telefon korkusu biraz azalıyor, birazcık.
