Cứu dữ liệu MySQL: Cách xử lý lỗi InnoDB Corruption thực chiến

MySQL tutorial - IT technology blog
MySQL tutorial - IT technology blog

Khi MySQL “đình công” vào lúc nửa đêm

Tưởng tượng Slack báo lỗi dồn dập vào lúc 2 giờ sáng. Bạn SSH vào server, cố gắng khởi động lại MySQL nhưng chỉ nhận được dòng thông báo cụt ngủn: Job for mysql.service failed. Kiểm tra error.log, bạn thấy chi chít những dòng InnoDB corruption, Assertion failure hoặc checksum mismatch. Lúc này, service không thể khởi động, và mọi lệnh truy vấn đều vô tác dụng.

Kinh nghiệm thực tế của mình: Một database production 50GB chạy MySQL 8.0 từng sập hoàn toàn sau khi trung tâm dữ liệu mất điện đột ngột. File log báo lỗi hỏng trang dữ liệu (page corruption), khiến MySQL cứ chạy được 3 giây là crash. Trong tình huống này, các bản backup thông thường (nếu có) cũng khó lòng khôi phục ngay lập tức. Cách duy nhất là can thiệp vào cơ chế recovery của InnoDB.

Tại sao engine “nồi đồng cối đá” như InnoDB vẫn hỏng?

Dù nổi tiếng với cơ chế ACID bền bỉ, InnoDB vẫn có những điểm yếu trước các tác động ngoại cảnh. Thông thường, dữ liệu bị hỏng do:

  • Phần cứng xuống cấp: Ổ cứng bị bad sector ngay tại vị trí lưu file .ibd quan trọng.
  • Sự cố điện: Hệ thống đang ghi dữ liệu từ Buffer Pool xuống đĩa thì bị ngắt điện, khiến cấu trúc file bị dở dang.
  • Lỗi Kernel: Hệ điều hành bị panic khiến file system không kịp đồng bộ dữ liệu.
  • Tắt cưỡng bức (Kill -9): Việc lạm dụng lệnh kill khiến transaction log bị hỏng nặng.

Công cụ cứu cánh: innodb_force_recovery

Đây là tham số “vàng” trong file my.cnf. Nó cho phép bạn bỏ qua các bước kiểm tra toàn vẹn để khởi động MySQL ở chế độ Read-only. Nhờ đó, bạn có thể trích xuất dữ liệu (dump) ra ngoài an toàn. Tham số này có 6 cấp độ từ thấp đến cao.

Các cấp độ phục hồi bạn cần nắm vững:

  • 1 (SRV_FORCE_IGNORE_CORRUPT): Bỏ qua các trang dữ liệu bị hỏng để server tiếp tục chạy.
  • 2 (SRV_FORCE_NO_BACKGROUND): Chặn master thread, tránh tình trạng crash trong khi dọn dẹp (purge).
  • 3 (SRV_FORCE_NO_TRX_UNDO): Không chạy rollback cho các transaction cũ.
  • 4 (SRV_FORCE_NO_IBUF_MERGE): Ngăn chặn việc gộp dữ liệu từ insert buffer.
  • 5 (SRV_FORCE_NO_UNDO_LOG_SCAN): Bỏ qua việc kiểm tra undo logs khi khởi động.
  • 6 (SRV_FORCE_NO_LOG_REDO): Không chạy redo log. Đây là mức độ cuối cùng, tiềm ẩn rủi ro mất dữ liệu cao nhất.

Lưu ý: Khi bật chế độ này, tuyệt đối không thực hiện INSERT hoặc UPDATE. Mục tiêu duy nhất là dùng mysqldump để cứu dữ liệu.

Quy trình 5 bước hồi sinh dữ liệu

Dưới đây là các bước mình đã dùng để cứu 50GB dữ liệu mà không mất một bản ghi nào.

Bước 1: Cô lập và sao lưu dữ liệu thô

Đừng bao giờ thao tác trực tiếp trên file lỗi mà không có bản dự phòng. Hãy copy toàn bộ thư mục data ra một nơi khác trước khi bắt đầu.

# Dừng MySQL
sudo systemctl stop mysql

# Sao lưu thư mục data (thường ở /var/lib/mysql)
sudo cp -R /var/lib/mysql /var/lib/mysql_backup_emergency

Bước 2: Kích hoạt chế độ Recovery

Mở file cấu hình (/etc/mysql/my.cnf) và thêm dòng sau vào mục [mysqld]:

innodb_force_recovery = 1

Thử khởi động lại MySQL. Nếu vẫn thất bại, hãy tăng dần giá trị lên 2, 3… cho đến khi service ổn định.

Bước 3: Trích xuất dữ liệu (Dump)

Khi MySQL đã tạm ổn, hãy nhanh chóng dump dữ liệu ra file .sql. Lời khuyên của mình là dump từng database riêng lẻ để dễ kiểm soát nếu có bảng bị hỏng quá nặng.

mysqldump -u root -p --all-databases --routines --triggers > full_dump.sql

Nếu gặp bảng bị lỗi khiến dump bị dừng, hãy dùng thêm tham số --ignore-table để bỏ qua bảng đó và xử lý riêng sau.

Bước 4: Làm sạch môi trường

Sau khi có file dump, hãy tắt MySQL và dọn dẹp thư mục data cũ. Vì đã có bản backup ở Bước 1, bạn có thể yên tâm thực hiện bước này.

sudo systemctl stop mysql
sudo rm -rf /var/lib/mysql/*
# Nhớ xóa hoặc comment dòng innodb_force_recovery trong file config

Khởi tạo lại database trống bằng lệnh mysqld --initialize hoặc khởi động lại service để MySQL tự tạo cấu trúc mới.

Bước 5: Nạp lại dữ liệu

Bây giờ, hãy đưa dữ liệu từ file dump trở lại hệ thống sạch.

sudo systemctl start mysql
mysql -u root -p < full_dump.sql

Kinh nghiệm “xương máu” để tránh mất dữ liệu vĩnh viễn

Trong cơn hoảng loạn, một sai lầm nhỏ cũng có thể khiến dữ liệu ra đi mãi mãi. Hãy ghi nhớ:

  1. Đi từ Level thấp đến cao: Đừng bao giờ nhảy thẳng lên Level 6. Nó có thể phá hủy cấu trúc dữ liệu mà không thể phục hồi.
  2. Kiểm tra dung lượng đĩa: Đôi khi MySQL crash đơn giản vì ổ cứng đầy 100%. Hãy gõ df -h để kiểm tra trước.
  3. Theo dõi log liên tục: Mở một terminal riêng chạy tail -f /var/log/mysql/error.log để biết chính xác MySQL đang gặp khó ở đâu.

Giải pháp phòng bệnh hơn chữa bệnh

Sau sự cố 50GB đó, mình đã thay đổi cách vận hành hệ thống. Những việc bạn nên làm ngay hôm nay:

  • Cấu hình innodb_flush_log_at_trx_commit = 1: Dữ liệu sẽ được ghi xuống đĩa ngay khi commit. Chậm hơn một chút nhưng an toàn tuyệt đối.
  • Dùng Percona XtraBackup: Công cụ này cho phép backup nóng (hot backup) mà không gây khóa bảng hay gián đoạn dịch vụ.
  • Giám sát I/O: Cài đặt cảnh báo khi ổ cứng đạt ngưỡng 80% hoặc khi hệ điều hành báo lỗi I/O.

Xử lý sự cố database là một cuộc đấu trí. Hãy bình tĩnh, copy dữ liệu thô ra trước, rồi mới bắt đầu thử nghiệm. Chúc bạn sớm đưa hệ thống trở lại quỹ đạo!

Share: