Minghsin University of Science and Technology
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

1

Local AI

--

Uedu Code

--

明新科大 AQI 70 24°C PM2.5 19
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 14

備份與復原

mysqldump、二進位日誌、匯出匯入與檢查修復表格
Interaction實作導覽 25 minute(s)

Learning Objectives

  • 說明為什麼要備份,分辨邏輯備份與實體備份、完整備份與二進位日誌
  • 用 mysqldump 備份單一資料庫、多個資料庫與整台伺服器,並用 mysql 還原
  • 用 SELECT … INTO OUTFILE 匯出、LOAD DATA INFILE 匯入表格資料,說明 secure_file_priv、IGNORE 與 REPLACE
  • 用完整備份加上二進位日誌(mysqlbinlog)把資料庫還原到誤刪之前的那一刻
  • 用 CHECK、ANALYZE、OPTIMIZE、REPAIR TABLE 與 mysqlcheck 檢查與維護表格,知道 InnoDB 不支援 REPAIR
  • 依 3-2-1 原則規劃備份,並定期測試還原

為什麼要備份

備份是在另一個地方保留一份資料的副本,原本的資料損壞時,可以用副本把資料找回來。資料庫可能因為下面這些原因損壞或遺失:

  • 硬體故障:硬碟壞軌、伺服器主機板燒毀
  • 軟體瑕疵:程式的錯誤把資料寫錯
  • 惡意攻擊:勒索軟體把檔案加密、入侵者刪除資料
  • 人為疏失:最常見的一種,例如 DELETE FROM employeeSkills; 忘了寫 WHERE

備份只能把資料還原到備份的那個時間點。每天凌晨備份一次,下午出事時,早上到下午之間的變更就沒有了; 兩次備份間隔越長,可能遺失的資料就越多。MySQL 的做法是「定期完整備份」再加上「二進位日誌」,把兩次備份之間的每一筆變更也記下來。

類型 做法 優點 缺點
邏輯備份 把資料轉成 SQL 敘述或文字檔:mysqldump、SELECT … INTO OUTFILE 檔案可以閱讀、可以只還原一個表格、可以搬到不同版本或不同作業系統 資料量大時備份與還原都慢
實體備份 直接複製 MySQL 的資料檔(/var/lib/mysql) 速度快,適合很大的資料庫 要停機或使用專門工具;只能還原到相同版本的 MySQL
二進位日誌 伺服器自動記錄每一筆修改資料的事件 可以還原到任何一個時間點 必須搭配一份完整備份才有用

原課程介紹的 mysqlhotcopy(複製 MyISAM 資料檔的 Perl 腳本)在 MySQL 5.7 已經移除; BACKUP TABLE、RESTORE TABLE 兩個敘述在 MySQL 5.5 就已移除,在 MySQL 8 執行會得到 ERROR 1064 語法錯誤。本課改用目前的做法。

mysqldump:把資料庫轉成 SQL 檔

mysqldump 連上伺服器,把表格的結構轉成 CREATE TABLE、資料轉成 INSERT,寫成一個 SQL 檔。還原時,把這個檔案交給 mysql 執行一次即可:

mysqldump -u root -p --single-transaction employee > employee.sql   # 備份
mysql -u root -p employee < employee.sql                            # 還原

> 與 < 是作業系統終端機的「重新導向」:> 把程式的輸出寫進檔案,< 把檔案內容當成程式的輸入。

選項 作用
--single-transaction 在一個交易裡讀取所有表格,取得同一個時間點的一致資料,而且不鎖住表格,備份時其他人照常使用。只對 InnoDB 有效
--databases db1 db2(-B) 備份一個或多個資料庫,檔案裡會加上 CREATE DATABASE 與 USE,還原時不必先建資料庫
--all-databases(-A) 備份所有資料庫,包括存放帳號與權限的 mysql 系統資料庫
--routines(-R)、--events 一併備份預存程序與函式(第 11 課)、排程事件;預設不會備份
--triggers 備份觸發程序,預設已開啟
--source-data=2 在檔案裡以註解記下備份當時的二進位日誌位置,時間點復原會用到(舊名 --master-data,8.0.26 起改名)
--opt 一組常用選項(加上 DROP TABLE、一次 INSERT 多列、快速讀取等),預設已開啟,原課程的 mysqldump --opt 現在不必寫

幾個要注意的地方:

  • 原講義的 -p password(-p 和密碼之間有空格)是錯的:mysqldump 會要你輸入密碼,再把 password 當成資料庫名稱。-p 後面不寫密碼,執行時再輸入最安全
  • 沒有 --single-transaction 時,mysqldump 預設在備份每個資料庫時鎖住它的表格(--lock-tables),備份期間別人不能寫入,所以原講義說它不適合在尖峰時段使用。 全部是 InnoDB 表格時,加上 --single-transaction 就沒有這個問題;MyISAM 表格不支援交易,仍然要鎖表格
  • 備份進行中不要執行 ALTER TABLE、DROP TABLE 這類修改結構的敘述,--single-transaction 無法保護它們

導覽:用 mysqldump 備份與還原

以下在 Ubuntu 24.04 的 MySQL 8.0 上操作,root 帳號用 sudo 登入(第 13 課介紹過)。備份檔的內容是在真實的 MySQL 8.0.44 上產生的;檔案大小、日期是示意值,版本號後面的 -0ubuntu0.24.04.1 會隨 Ubuntu 套件更新而不同。

  1. 1

    備份一個資料庫

    mysqldump 把結果印到螢幕,> 把它存成 employee.sql。成功時不會顯示任何訊息,用 ls -lh 確認檔案已經產生。> 是你自己的帳號在寫檔,所以檔案屬於 ubuntu,不是 root。

  2. 2

    看看備份檔裡有什麼

    備份檔就是一般的文字檔。開頭記錄 mysqldump 與伺服器的版本,接著暫時關閉外鍵檢查(FOREIGN_KEY_CHECKS=0),表格才能依字母順序還原,不必管誰參照誰。每個表格依序是:DROP TABLE IF EXISTS、CREATE TABLE、鎖定表格、一個包含所有資料列的 INSERT、解除鎖定。/*!40101 … */ 是 MySQL 的「版本註解」:伺服器版本至少是 4.01.01 才執行裡面的內容,其他資料庫會把它當成註解略過。下面是節錄,「…」代表省略的部分。

  3. 3

    還原到另一個資料庫

    只備份一個資料庫時,檔案裡沒有 CREATE DATABASE,還原時要指定資料庫,而且它必須已經存在,否則出現 ERROR 1049。先還原到另一個名稱(employee_test)是好習慣:確認備份檔可以用,又不會覆蓋正在使用的資料。還原的時間大約和備份差不多,資料量很大時可能要好幾個小時。

  4. 4

    --databases、--all-databases 與 --routines

    加上 --databases,檔案裡會有 CREATE DATABASE IF NOT EXISTS 與 USE,還原時直接 mysql < 檔案,不必指定資料庫。--routines --events 把預存程序、函式與排程事件也一起備份。--all-databases 備份整台伺服器,包括 mysql 系統資料庫裡的帳號與權限,適合整台搬家;它不會備份 information_schema、performance_schema 這些由伺服器即時產生的資料庫。

  5. 5

    壓縮備份檔

    SQL 備份檔是文字,壓縮率很高。用管線 | 把 mysqldump 的輸出直接交給 gzip 壓縮;$(date +%F) 會換成今天的日期(例如 2026-09-01),每天的備份就不會互相覆蓋。還原時用 gunzip 解壓縮,再交給 mysql。要每天自動備份,可以把第一行寫進 cron 排程;腳本裡不要寫明文密碼,用 root 的 sudo 或權限設為 600 的設定檔 ~/.my.cnf 提供帳號密碼。

這段導覽完成了

SELECT … INTO OUTFILE 與 LOAD DATA INFILE

mysqldump 備份整個資料庫。只想把一個表格的資料存成文字檔(例如交給試算表分析,或搬到另一個表格),可以在 SQL 裡完成:

SELECT * FROM department INTO OUTFILE '/var/lib/mysql-files/department.txt';   -- 匯出
LOAD DATA INFILE '/var/lib/mysql-files/department.txt' INTO TABLE department_restore;   -- 匯入
  • 檔案是由伺服器(作業系統帳號 mysql)寫在伺服器那台主機上,不是寫在你的電腦;需要 FILE 權限
  • MySQL 8 的 secure_file_priv 限制只能使用某個目錄,Ubuntu 預設是 /var/lib/mysql-files/,用其他目錄會出現 ERROR 1290
  • INTO OUTFILE 不會覆蓋已經存在的檔案,會出現 ERROR 1086,避免不小心蓋掉重要檔案
  • 預設格式:欄位之間是 tab,每列一行,NULL 寫成 \N(反斜線加大寫 N),字串裡的 tab、換行、反斜線前面會加上反斜線
  • LOAD DATA 遇到重複的主鍵,預設整個敘述失敗,一列也不會加入。加上 IGNORE 會略過重複的列(並產生警告); 加上 REPLACE 會用檔案裡的資料取代原本的列

用上面的指令匯出 department,檔案內容是(→ 代表 tab):

42→Finance
128→Research and Development
129→Human Resources
130→Marketing

要給試算表使用,可以指定成 CSV 格式,LOAD DATA 也要寫相同的格式選項:

SELECT * FROM client INTO OUTFILE '/var/lib/mysql-files/client.csv'
  FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
  LINES TERMINATED BY '\n';
1,"Telco Inc","1 Collins St Melbourne","Fred Smith","95551234"
2,"The Bank","100 Bourke St Melbourne","Jan Tristan","95559876"
3,"Startup Co",\N,\N,\N

LOAD DATA LOCAL INFILE 讀取的是用戶端電腦上的檔案,不受 secure_file_priv 限制,但 MySQL 8 預設關閉(伺服器的 local_infile 是 OFF), 直接使用會出現 ERROR 3948 (42000): Loading local data is disabled; this must be enabled on both the client and server sides,要伺服器與用戶端兩邊都開啟才能用。

這種匯出只有資料,沒有 CREATE TABLE,也沒有索引與外鍵,所以範例都先用 CREATE TABLE … LIKE 建立結構相同的空表格。 直譯器把檔案存在記憶體裡,同一個範例之內有效。

匯出再匯入一個表格

先確認 secure_file_priv 的目錄,試著寫到 /tmp 會被拒絕;寫到允許的目錄成功之後,再寫一次同一個檔名會被拒絕。最後建立一個結構相同的空表格,把檔案匯入。

用匯出檔救回誤刪的資料

先把 employeeSkills 匯出,接著「不小心」刪掉員工 7513 的 3 項技能。直接 LOAD DATA 會因為其他 5 列已經存在而整個失敗;加上 IGNORE 之後,已經存在的 5 列被略過,只把刪掉的 3 列補回來。

NULL 在檔案裡寫成 \N

新增一個只有名稱的客戶,其他欄位都是 NULL。匯出後檔案的第三行是 3→Startup Co→\N→\N→\N,匯入時 \N 會還原成 NULL,而不是空字串或文字「N」。(直譯器目前沒有畫出檔案內容,這一行是在真實 MySQL 8.0.44 上匯出的結果。)

想一想:匯出與匯入

1.

在 Ubuntu 的 MySQL 8 執行 SELECT * FROM department INTO OUTFILE '/tmp/department.txt'; 會出現什麼?

2.

同一個範例裡連續執行兩次下面的敘述,第二次會出現什麼?

SELECT * FROM department INTO OUTFILE '/var/lib/mysql-files/d.txt';
3.

employeeSkills 原本有 8 列。下面三個敘述執行完,SELECT COUNT(*) FROM employeeSkills; 是多少?

SELECT * FROM employeeSkills INTO OUTFILE '/var/lib/mysql-files/s.txt';
DELETE FROM employeeSkills WHERE employeeID = 7513;   -- 刪除 3 列
LOAD DATA INFILE '/var/lib/mysql-files/s.txt' IGNORE INTO TABLE employeeSkills;
第 1 次作答

二進位日誌與時間點復原

二進位日誌(binary log,簡稱 binlog)是伺服器自動記錄的「修改紀錄」:每一個新增、修改、刪除資料或改變結構的事件,都依發生順序寫進日誌,SELECT 不會記錄。 MySQL 8 預設就開啟,檔案在資料目錄,名稱是 binlog.000001、binlog.000002…:

  • 伺服器每次重新啟動、檔案超過 max_binlog_size(預設 1 GB)或執行 mysqladmin flush-logs,就換一個新檔案
  • binlog_expire_logs_seconds 預設 2592000 秒(30 天),超過的舊檔會自動刪除,所以完整備份的間隔一定要比它短
  • 預設格式 binlog_format=ROW:記錄每一列「改之前」和「改之後」的值,而不是原本的 SQL 敘述
  • SHOW BINARY LOGS; 列出目前的日誌檔,SHOW MASTER STATUS; 顯示正在寫的檔案與位置

binlog 是二進位格式,要用 mysqlbinlog 轉成看得懂、也可以直接執行的 SQL 文字。原課程的寫法 mysqlbinlog logfile > updates.sql 現在仍然可用。

時間點復原(point-in-time recovery)的想法是:

  1. 先還原最近一次的完整備份(例如今天凌晨 3 點的 mysqldump),資料回到凌晨 3 點
  2. 再用 mysqlbinlog 重播凌晨 3 點之後、出事之前的日誌,把這段時間的正常變更補回來
  3. 出事的那個敘述不重播,資料就回到出事前的那一刻

所以備份時要知道「這份備份對應到日誌的哪個位置」,mysqldump 的 --source-data=2 就是把這個位置寫進備份檔。 (SQL 直譯器沒有 binlog,下面用導覽說明;導覽裡的輸出是在真實的 MySQL 8.0.44 上操作的結果,時間與位置依你的伺服器而不同。)

導覽:把資料救回到誤刪前一刻

情境:凌晨做了完整備份;上午有人新增一位員工、修改一位員工的職稱;下午有人執行了 DELETE FROM employeeSkills;,忘了寫 WHERE,8 項技能全部消失。目標是保留上午的兩筆正常變更,只救回被刪掉的技能。輸出經過節錄,「…」代表省略的內容;日期與時間是示意值。

  1. 1

    確認二進位日誌已開啟

    log_bin 是 ON 就表示有記錄。檔案大小是示意值。

  2. 2

    完整備份時記下日誌位置

    --source-data=2 在備份檔開頭寫一行註解,記錄備份那一刻 binlog 的檔名與位置:這份備份包含 binlog.000003 位置 4091 之前的所有變更。這個選項需要 RELOAD 權限,而且會極短暫地鎖住所有表格,以取得準確的位置。

  3. 3

    用 mysqlbinlog 找出出事的位置

    從備份的位置 4091 開始讀日誌。-v --base64-output=DECODE-ROWS 把 ROW 格式的資料轉成以 ### 開頭、看得懂的虛擬 SQL(@1、@2 是第 1、第 2 個欄位)。每個事件前面的 # at 位置 是它在檔案裡的起點。可以看到三個交易:位置 4091 起是新增 Mia Chen、4410 起是修改 Nora 的職稱、4791 起是刪除 8 項技能。所以要重播的是 4091 到 4791(不含)之間的事件。

  4. 4

    還原完整備份,再重播日誌

    先 mysqladmin flush-logs 換一個新的日誌檔,之後的還原動作會寫進新檔,不會和要讀取的 binlog.000003 混在一起。接著還原凌晨的完整備份(資料回到凌晨),再把位置 4091 到 4791 之間的事件交給 mysql 執行(--stop-position 指定的位置不包含在內),上午的兩筆變更回來了,下午的刪除則沒有重播。也可以用時間指定範圍:--start-datetime="2026-09-01 03:00:00" --stop-datetime="2026-09-01 14:20:37";同一秒內有好幾個事件時,用位置比較精確。橫跨好幾個日誌檔時,把檔名依序全部列在同一個 mysqlbinlog 指令後面。

這段導覽完成了

檢查與維護表格

敘述 作用 InnoDB MyISAM
CHECK TABLE t; 檢查表格與索引有沒有損壞 支援 支援
REPAIR TABLE t; 修復損壞的表格 不支援,只回一則 note 支援
ANALYZE TABLE t; 重新統計索引的分布,讓查詢最佳化器選對索引 支援 支援
OPTIMIZE TABLE t; 重新整理資料檔,回收刪除資料後留下的空間 改為重建表格再 ANALYZE 支援

結果都是 Table、Op、Msg_type、Msg_text 四欄:status 加上 OK 表示正常;note 是說明;Error 是錯誤。一次可以寫好幾個表格,用逗號分隔。

InnoDB 為什麼不能 REPAIR? InnoDB 每次寫入都先記在重做日誌(redo log),伺服器當機後重新啟動時會自動依日誌把資料修好(crash recovery),一般不需要手動修復。 真的發生 InnoDB 資料檔損壞,正確的做法是從備份還原。原講義說 CHECK 與 REPAIR「可用於 InnoDB 及 MyISAM 表格」,在 MySQL 8 只有 CHECK 對兩者都成立。

命令列也有對應的工具:

工具 說明
mysqlcheck 連上伺服器執行 CHECK/REPAIR/ANALYZE/OPTIMIZE TABLE,可以一次處理整個資料庫:預設是檢查,--repair(-r)、--analyze(-a)、--optimize(-o),--all-databases 處理所有資料庫
myisamchk 只能用於 MyISAM,直接讀寫 .MYI/.MYD 檔案,不經過伺服器;使用前要停止伺服器(或確定沒有人在用那個表格),否則可能把表格弄壞。原講義的快速修復 myisamchk -q -r 表格 仍然可用

MySQL 8 的系統表格與預設引擎都已經是 InnoDB,大部分情況用 mysqlcheck 或 SQL 敘述即可。

CHECK、ANALYZE、OPTIMIZE 與 REPAIR

對 InnoDB 的 employee 依序執行四種維護敘述,再建立一個 MyISAM 表格比較 REPAIR 的結果。直譯器只會回報 OK,不會真的發現損壞;這裡練習的是看懂結果的格式。

導覽:mysqlcheck 與停機複製資料目錄

mysqlcheck 的輸出格式是在真實的 MySQL 8.0.44 上取得的。停機複製只示範步驟,複製的大小是示意值。

  1. 1

    用 mysqlcheck 檢查整個資料庫

    mysqlcheck 資料庫 對每個表格執行 CHECK TABLE,每個表格一行。加 --analyze 改成 ANALYZE TABLE;--optimize、--repair 對 InnoDB 會出現和 SQL 敘述相同的 note。在資料庫名稱後面加上表格名稱,就只處理那幾個表格。

  2. 2

    停機複製資料目錄(冷備份)

    最簡單的實體備份是先停止 MySQL,把整個資料目錄 /var/lib/mysql 連同設定檔 /etc/mysql 複製到另一個地方,再啟動 MySQL。cp -a 會保留檔案的擁有者與權限,還原時檔案必須仍然屬於 mysql 帳號。伺服器執行中直接複製資料檔是不安全的:InnoDB 的資料可能還在記憶體裡沒有寫入檔案,複製出來的檔案彼此不一致。停機期間沒有人能使用資料庫,所以只適合可以安排停機時間的系統;還原時也要用相同版本的 MySQL。

這段導覽完成了

線上實體備份工具與備份策略

資料量很大(數百 GB 以上)時,mysqldump 備份與還原都太慢,又不能每次都停機,這時會用線上實體備份工具: 在伺服器運作中複製資料檔,同時記錄複製期間的變更,最後產生一份一致的備份。本課只介紹,不實際操作:

工具 說明
MySQL Enterprise Backup(mysqlbackup) Oracle 的商業版 MySQL Enterprise Edition 內附,需要付費授權
Percona XtraBackup(xtrabackup) Percona 公司的開放原始碼工具,可以免費使用;8.0 版對應 MySQL 8.0

3-2-1 備份原則是規劃備份時常用的檢查清單:

  • 3 份資料:正式資料加上至少 2 份備份
  • 2 種不同的儲存媒體:例如伺服器的硬碟和另一台備份主機(或磁帶),不會因為同一顆硬碟壞掉而一起遺失
  • 1 份放在異地:另一棟建築、另一個機房或雲端儲存,火災、水災、勒索軟體才不會把所有副本一起毀掉

其他常見的做法:

  • 完整備份+二進位日誌:例如每天一次 mysqldump(或實體備份)並保留 binlog,就能還原到任何一個時間點
  • 備份檔也要保護:備份檔裡有全部的資料,要限制存取權限,必要時加密
  • 定期測試還原:沒有實際還原過的備份,不能確定它真的能用。至少每學期(或每次改變備份方式時)還原到測試用的資料庫一次,並記錄還原需要多少時間

想一想:備份方式與維護

1.

對 InnoDB 表格執行 REPAIR TABLE employee; 會得到什麼?

2.

用 mysqldump 備份全部是 InnoDB 表格的資料庫,希望備份期間其他人仍然可以新增、修改資料,又要拿到同一個時間點的一致資料,應該加上哪個選項?

3.

下午 2 點 20 分有人誤刪資料。要把資料庫還原到 2 點 20 分之前的那一刻,需要哪些東西?

第 1 次作答

動手寫:用匯出檔備份 employee 表格

請依序完成:(1) 把 employee 表格匯出到 /var/lib/mysql-files/employee.txt;(2) 建立一個結構和 employee 相同的空表格 employee_backup;(3) 把檔案匯入 employee_backup;(4) 依 employeeID 排序列出 employee_backup 的所有資料。

你的輸出
預期輸出

重點整理

工作 做法
備份一個資料庫 mysqldump --single-transaction 資料庫 > 檔案.sql
備份多個或全部資料庫 --databases a b、--all-databases;預存程序與事件要加 --routines --events
還原 mysql 資料庫 < 檔案.sql(用 --databases 備份的檔案不必指定資料庫)
匯出、匯入一個表格的資料 SELECT … INTO OUTFILE、LOAD DATA INFILE(限 secure_file_priv 目錄;不覆蓋檔案;NULL 寫成 \N;重複時用 IGNORE 或 REPLACE)
還原到某個時間點 完整備份(--source-data=2 記下位置)+ mysqlbinlog --start-position … --stop-position … \| mysql
檢查與維護 CHECK、ANALYZE、OPTIMIZE TABLE、mysqlcheck;InnoDB 不支援 REPAIR TABLE,myisamchk 只用於 MyISAM
大型資料庫 實體備份:停機複製資料目錄,或 MySQL Enterprise Backup、Percona XtraBackup
  • 已移除:mysqlhotcopy(5.7)、BACKUP TABLE/RESTORE TABLE(5.5);--opt 預設已開啟
  • 3-2-1:3 份資料、2 種媒體、1 份異地;而且要定期測試還原

下一課討論資料庫的安全性:root 密碼、匿名帳號、最小權限與連線安全。

這一課完成了!
Next lesson:安全性
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。