สคริปต์สำรอง MySQL อัตโนมัติรายวันพร้อมลบไฟล์เก่า

สร้างสคริปต์ Bash สำรองทุกฐานข้อมูล MySQL หรือ MariaDB แยกไฟล์ บีบอัดด้วย gzip ลบไฟล์ที่เก่ากว่ากำหนดอัตโนมัติ และตั้งเวลาให้รันทุกวันด้วย Cron

โดย ทีมงาน THAI DATA CLOUD อัปเดต อ่าน 11 นาที เข้าชม 3 ครั้ง

แชร์FacebookXLINELinkedIn
MySQL — สคริปต์สำรอง MySQL อัตโนมัติรายวันพร้อมลบไฟล์เก่า
สคริปต์สำรอง MySQL อัตโนมัติรายวันพร้อมลบไฟล์เก่า

คำตอบสั้น ๆ: สร้างสคริปต์ Bash สำรองทุกฐานข้อมูล MySQL หรือ MariaDB แยกไฟล์ บีบอัดด้วย gzip ลบไฟล์ที่เก่ากว่ากำหนดอัตโนมัติ และตั้งเวลาให้รันทุกวันด้วย Cron

คู่มือนี้อธิบาย MySQL ทีละขั้นตอน พร้อมคำสั่งที่ใช้ได้จริงและปัญหาที่พบบ่อย

การสำรองฐานข้อมูลด้วยมือเป็นครั้งคราวไม่พอสำหรับระบบที่ใช้งานจริง เพราะวันที่ต้องกู้คืนมักเป็นวันที่ไม่มีใครได้สำรองไว้ บทความนี้สร้างสคริปต์ Bash ที่สำรองทุกฐานข้อมูลบน MySQL หรือ MariaDB แยกเป็นไฟล์ละฐานข้อมูล บีบอัดด้วย gzip ลบไฟล์ที่เก่ากว่าจำนวนวันที่กำหนด และตั้งให้ทำงานทุกวันด้วย Cron

สคริปต์ออกแบบให้ปลอดภัยต่อการใช้งานจริง คือไม่ใส่รหัสผ่านในสคริปต์หรือใน Command Line ไม่ทิ้งไฟล์ที่สำรองไม่ครบไว้ให้เข้าใจผิดว่าเป็นไฟล์ที่ใช้ได้ และไม่รันซ้อนกันถ้ารอบก่อนยังไม่เสร็จ

สิ่งที่ต้องเตรียม

  • เซิร์ฟเวอร์ Linux (Ubuntu 22.04/24.04 หรือ AlmaLinux/Rocky 8-9) ที่ติดตั้ง MySQL 8 หรือ MariaDB 10.x และมีสิทธิ์ root หรือ sudo
  • พื้นที่ดิสก์ว่างพอสำหรับไฟล์สำรองหลายวัน ประมาณได้จากขนาดไฟล์สำรองหนึ่งรอบคูณจำนวนวันที่จะเก็บ
  • พื้นฐานคำสั่ง mysqldump ดู สำรองและกู้คืนฐานข้อมูล MySQL/MariaDB ด้วย mysqldump

ถ้าใช้ Plesk หรือ DirectAdmin ระบบสำรองของ Control Panel อาจครอบคลุมอยู่แล้ว ดู สำรองและกู้คืนเว็บไซต์ด้วย Backup Manager บน Plesk และ สำรองและกู้คืนข้อมูลบน DirectAdmin สคริปต์ในบทความนี้เหมาะกับเซิร์ฟเวอร์ที่ไม่มี Control Panel หรือต้องการสำรองฐานข้อมูลแยกอีกชั้น

ขั้นตอนที่ 1: สร้างผู้ใช้สำหรับสำรองข้อมูลโดยเฉพาะ

ไม่ควรใช้ root ของฐานข้อมูลในสคริปต์ ให้สร้างผู้ใช้ที่มีสิทธิ์เท่าที่จำเป็นสำหรับการอ่านและ Dump เท่านั้น เข้า mysql ในฐานะผู้ดูแล

sudo mysql
CREATE USER 'backup'@'localhost' IDENTIFIED BY 'ตั้งรหัสผ่านยาวและสุ่ม';
GRANT SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, EVENT, PROCESS, RELOAD ON *.* TO 'backup'@'localhost';
FLUSH PRIVILEGES;

สิทธิ์ PROCESS ใช้สำหรับ Dump ข้อมูล Tablespace บน MySQL 8 และ RELOAD ใช้ในบางกรณีร่วมกับ --single-transaction ผู้ใช้นี้แก้ไขหรือลบข้อมูลไม่ได้

ขั้นตอนที่ 2: เก็บรหัสผ่านในไฟล์ตั้งค่าที่มีสิทธิ์จำกัด

การใส่ -pรหัสผ่าน ใน Command Line จะทำให้รหัสผ่านปรากฏในรายการ Process ที่ผู้ใช้อื่นเห็นได้ ให้เก็บในไฟล์แทน

sudo nano /root/.my-backup.cnf
[client]
user=backup
password=ตั้งรหัสผ่านยาวและสุ่ม

ถ้ารหัสผ่านมีอักขระ # หรือ ; ให้ครอบด้วยเครื่องหมายคำพูด เช่น password="ab#12;cd" จากนั้นจำกัดสิทธิ์ให้ root อ่านได้เท่านั้น

sudo chmod 600 /root/.my-backup.cnf

ทดสอบว่าเชื่อมต่อได้ ตัวเลือก --defaults-extra-file ต้องเป็นตัวเลือกแรกของคำสั่งเสมอ

sudo mysql --defaults-extra-file=/root/.my-backup.cnf -e "SHOW DATABASES;"

ขั้นตอนที่ 3: สร้างสคริปต์สำรองข้อมูล

sudo nano /usr/local/sbin/mysql-backup.sh

ใส่เนื้อหาต่อไปนี้

#!/usr/bin/env bash
# สำรองทุกฐานข้อมูล MySQL/MariaDB แยกไฟล์ และลบไฟล์ที่เก่ากว่า RETENTION_DAYS
set -euo pipefail

BACKUP_DIR="/var/backups/mysql"
RETENTION_DAYS=14
CNF="/root/.my-backup.cnf"
STAMP="$(date +%F_%H%M)"

# MariaDB 11 ใช้ชื่อ mariadb / mariadb-dump ถ้าไม่มี mysql / mysqldump
MYSQL="$(command -v mysql || command -v mariadb)"
DUMP="$(command -v mysqldump || command -v mariadb-dump)"

# ป้องกันการรันซ้อน
exec 9>/run/mysql-backup.lock
flock -n 9 || { echo "$(date '+%F %T') previous backup still running, skip"; exit 1; }

umask 077
mkdir -p "$BACKUP_DIR"

DBS="$("$MYSQL" --defaults-extra-file="$CNF" -N -B -e 'SHOW DATABASES' \
  | grep -Ev '^(information_schema|performance_schema|sys|mysql)$')"

FAILED=0
for DB in $DBS; do
  OUT="$BACKUP_DIR/${DB}_${STAMP}.sql.gz"
  if "$DUMP" --defaults-extra-file="$CNF" --single-transaction --quick \
       --routines --triggers --events "$DB" | gzip > "$OUT.part"; then
    mv "$OUT.part" "$OUT"
    echo "$(date '+%F %T') OK   $DB -> $OUT ($(du -h "$OUT" | cut -f1))"
  else
    rm -f "$OUT.part"
    echo "$(date '+%F %T') FAIL $DB"
    FAILED=1
  fi
done

# ลบไฟล์สำรองที่เก่ากว่ากำหนด ทำเฉพาะเมื่อรอบนี้สำเร็จทั้งหมด
if [ "$FAILED" -eq 0 ]; then
  find "$BACKUP_DIR" -type f -name '*.sql.gz' -mtime +"$RETENTION_DAYS" -print -delete
fi

exit "$FAILED"

หลักการทำงานที่สำคัญของสคริปต์

  • set -o pipefail ทำให้ถ้า mysqldump ล้มเหลว ทั้ง Pipe จะถือว่าล้มเหลว แม้ gzip จะทำงานสำเร็จ
  • เขียนลงไฟล์ .part ก่อน แล้วค่อยเปลี่ยนชื่อเมื่อเสร็จ ไฟล์ .sql.gz ทุกไฟล์ในโฟลเดอร์จึงเป็นไฟล์ที่สำรองครบ
  • ลบไฟล์เก่าเฉพาะเมื่อรอบนี้สำเร็จ ถ้าการสำรองเสียต่อเนื่องหลายวัน ไฟล์ดีชุดสุดท้ายจะไม่ถูกลบทิ้ง
  • --single-transaction สำรองตาราง InnoDB ได้โดยไม่ล็อกตาราง แต่ตาราง MyISAM ยังอาจไม่สอดคล้องกันถ้ามีการเขียนระหว่างสำรอง
  • ฐานข้อมูลระบบ mysql ถูกข้ามไว้ เพราะการกู้คืนข้ามเวอร์ชันมักมีปัญหา ผู้ใช้และสิทธิ์ควรบันทึกเป็นคำสั่ง CREATE USER และ GRANT แยกไว้

ให้สิทธิ์รันสคริปต์

sudo chmod 700 /usr/local/sbin/mysql-backup.sh

ขั้นตอนที่ 4: ทดสอบสคริปต์ด้วยมือ

ก่อนตั้งเวลา ให้ดูก่อนว่าคำสั่งลบจะเลือกไฟล์ใด โดยใช้ find แบบไม่มี -delete ซึ่งจะแสดงรายการเท่านั้น

sudo find /var/backups/mysql -type f -name '*.sql.gz' -mtime +14 -print

จากนั้นรันสคริปต์

sudo /usr/local/sbin/mysql-backup.sh

ผลลัพธ์ควรเป็นบรรทัด OK หนึ่งบรรทัดต่อหนึ่งฐานข้อมูล เช่น

2026-09-19 02:30:01 OK   shopdb -> /var/backups/mysql/shopdb_2026-09-19_0230.sql.gz (48M)

ขั้นตอนที่ 5: ตั้งเวลาให้รันทุกวัน

สร้างไฟล์ใน /etc/cron.d/ ซึ่งอ่านง่ายและไม่ปนกับ Crontab ของผู้ใช้ (ไฟล์ในโฟลเดอร์นี้ต้องมีช่องชื่อผู้ใช้ก่อนคำสั่ง)

sudo nano /etc/cron.d/mysql-backup
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
30 2 * * * root /usr/local/sbin/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1

ตัวอย่างนี้รันเวลา 02:30 น. ทุกวันตามเวลาของเซิร์ฟเวอร์ ตรวจว่าเซิร์ฟเวอร์ตั้ง Timezone ถูกต้องตาม ตั้งค่า Timezone และซิงก์เวลาอัตโนมัติบน Linux Server รายละเอียดรูปแบบเวลาของ Cron ดูได้ที่ วิธีตั้ง Cronjob หรือ Crontab เพื่อให้ Script ทำงานอัตโนมัติบน Linux

ไฟล์ Log จะโตขึ้นเรื่อย ๆ ควรหมุนเวียนด้วย logrotate ตาม ตั้งค่า logrotate ไม่ให้ไฟล์ Log เต็มดิสก์

เลือกจำนวนวันที่เก็บ

-mtime +14 หมายถึงไฟล์ที่แก้ไขล่าสุดนานกว่า 14 วันเต็ม (find นับเป็นช่วง 24 ชั่วโมงและปัดเศษทิ้ง) ในทางปฏิบัติจึงเหลือไฟล์ประมาณ 15 ชุด แนวทางเลือกค่า

RETENTION_DAYSเหมาะกับ
7ฐานข้อมูลใหญ่ พื้นที่ดิสก์จำกัด และมีสำเนานอกเครื่องอีกชุด
14ค่าเริ่มต้นที่สมดุลสำหรับเว็บไซต์ทั่วไป
30 ขึ้นไประบบที่ต้องย้อนดูข้อมูลได้นาน หรือเป็นไปตามนโยบายขององค์กร

ไฟล์สำรองที่อยู่บนเซิร์ฟเวอร์เดียวกับฐานข้อมูลไม่ช่วยอะไรถ้าดิสก์หรือเซิร์ฟเวอร์เสียหายทั้งเครื่อง ควรส่งสำเนาไปเก็บอีกที่ด้วย ดู สำรองข้อมูลไปยังเซิร์ฟเวอร์อื่นด้วย rsync และ Cronjob

ตรวจสอบผลลัพธ์

วันถัดไปให้ตรวจ Log และไฟล์ที่ได้

sudo tail -n 20 /var/log/mysql-backup.log
sudo ls -lh /var/backups/mysql/

ตรวจว่าไฟล์ gzip ไม่เสียหาย และท้ายไฟล์มีบรรทัด Dump completed ซึ่ง mysqldump เขียนไว้เมื่อ Dump จบสมบูรณ์

sudo gzip -t /var/backups/mysql/shopdb_2026-09-19_0230.sql.gz && echo "gzip OK"
sudo zcat /var/backups/mysql/shopdb_2026-09-19_0230.sql.gz | tail -n 1

ไฟล์สำรองที่ไม่เคยทดสอบกู้คืนยังเชื่อถือไม่ได้ ควรทดลองกู้คืนลงฐานข้อมูลทดสอบอย่างน้อยเดือนละครั้ง

ปัญหาที่พบบ่อยเกี่ยวกับ MySQL

Access denied; you need (at least one of) the PROCESS privilege(s)

MySQL 8.0.21 ขึ้นไปต้องใช้สิทธิ์ PROCESS เพื่อ Dump ข้อมูล Tablespace ให้เพิ่มสิทธิ์ตามขั้นตอนที่ 1 หรือเพิ่มตัวเลือก --no-tablespaces ในคำสั่ง mysqldump ถ้าไม่ได้ใช้ Tablespace ที่กำหนดเอง

รันด้วยมือได้ แต่ Cron ไม่สร้างไฟล์

สาเหตุที่พบบ่อยคือ PATH ของ Cron ไม่มีโฟลเดอร์ของคำสั่ง ไฟล์ใน /etc/cron.d/ ไม่มีช่องชื่อผู้ใช้ หรือชื่อไฟล์ใน /etc/cron.d/ มีจุดหรือนามสกุล ซึ่งบน Ubuntu จะถูกข้ามไป ตรวจ Log ของ Cron ด้วย journalctl -u cron บน Ubuntu หรือ journalctl -u crond บน AlmaLinux/Rocky ดูเพิ่มเติมที่ อ่าน Log ระบบบน Linux ด้วย journalctl

ดิสก์เต็มเพราะไฟล์สำรอง

ลดค่า RETENTION_DAYS หรือย้าย BACKUP_DIR ไปไว้บน Data Disk แยก ดู ดิสก์เต็ม: ตรวจสอบพื้นที่และหาไฟล์ขนาดใหญ่บน Linux ถ้าสคริปต์รายงาน FAIL ติดต่อกันหลายวัน ไฟล์เก่าจะไม่ถูกลบตามที่ออกแบบไว้ ให้แก้สาเหตุของ FAIL ก่อน

หากต้องการคำแนะนำเรื่องการวางระบบสำรองข้อมูลบนเซิร์ฟเวอร์ของคุณ ติดต่อทีมงาน THAI DATA CLOUD ได้ที่ https://thaidata.cloud/contact/

อ่านเพิ่มเติมเกี่ยวกับ MySQL

แชร์FacebookXLINELinkedIn

มีคำถามเรื่องบริการ? ติดต่อทีมงาน