ลดความยุ่งยากในการใช้คลาวด์ พูดคุยกับเจ้าหน้าที่

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

สคริปต์สำรอง 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)

ผู้ให้บริการคลาวด์ไทย
เพื่อธุรกิจของคนไทย

"มุ่งมั่น" และ "มั่นคง"
พร้อมรับมือทุกการเติบโต
Trust Cloud
คลาว์ที่ปลอดภัย
คือรากฐานที่มั่นคง
cloud security
)" 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"

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

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

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

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

ปัญหาที่พบบ่อย

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/

ผู้ให้บริการคลาวด์ไทย
เพื่อธุรกิจของคนไทย

"มุ่งมั่น" และ "มั่นคง"
พร้อมรับมือทุกการเติบโต
Trust Cloud
คลาว์ที่ปลอดภัย
คือรากฐานที่มั่นคง
cloud security