MySQL -- 拷貝表
CREATE DATABASE db1; USE db1; CREATE TABLE t(id INT PRIMARY KEY, a INT, b INT, INDEX(a)) ENGINE=InnoDB; DELIMITER ;; CREATE PROCEDURE idata() BEGIN DECLARE i INT; SET i=1; WHILE (i <= 1000) DO INSERT INTO t VALUES (i,i,i); SET i=i+1; END WHILE; END;; DELIMITER ; CALL idata(); CREATE DATABASE db2; CREATE TABLE db2.t LIKE db1.t; -- 目標:把db1.t裡面a>900的資料匯出來,插入到db2.t
mysqldump
$ mysqldump -uroot --add-locks=0 --no-create-info --single-transaction--set-gtid-purged=OFF db1 t --where="a>900" --result-file=/tmp/t.sql # 部分結果 $ cat /tmp/t.sql INSERT INTO `t` VALUES (901,901,901),(902,902,902),(903,903,903)...(999,999,999),(1000,1000,1000);
- mysqldump命令將資料匯出成一組
INSERT
語句,把結果輸出到 客戶端 的臨時檔案 -
--single-transaction
- 匯出資料時不需要對錶db1.t加 表鎖
- 採用的是
START TRANSACTION WITH CONSISTENT SNAPSHOT
-
--add-locks=0
- 表示在輸出到檔案結果裡,不增加
LOCK TABLES t WRITE
- 表示在輸出到檔案結果裡,不增加
-
--no-create-info
- 不需要匯出表結構
-
--set-gtid-purged=OFF
- 不輸出跟GTID相關的資訊
-
--result-file
- 執行 客戶端 上輸出檔案的路徑
- 輸出結果中的
INSERT
語句會包含多個value對,為了後續如果使用這個檔案寫入資料,執行會更快- 如果要一個
INSERT
語句只插入一行資料的話,增加引數--skip-extended-insert
- 如果要一個
應用到db2
$ mysql -h$host -P$port -u$user db2 -e "source /client_tmp/t.sql"
-
source
是一個客戶端命令,開啟檔案,預設以 分號 結尾讀取一條條的SQL語句 - 將SQL語句傳送到服務端執行,
slowlog
和binlog
都會記錄這些語句
匯出CSV
mysql> SYSTEM cat cat /usr/local/etc/my.cnf cat: cat: No such file or directory # Default Homebrew MySQL server config [mysqld] # Only allow connections from localhost bind-address = 127.0.0.1 slow_query_log = 1 long_query_time = 0 secure-file-priv = "/tmp" mysql> SELECT @@secure_file_priv; +--------------------+ | @@secure_file_priv | +--------------------+ | /tmp/| +--------------------+ mysql> SELECT * FROM db1.t WHERE a>900 INTO OUTFILE '/tmp/t.csv'; Query OK, 100 rows affected (0.01 sec) mysql> SYSTEM du -sh /tmp/t.csv 4.0K/tmp/t.csv
- secure-file-priv
-
secure-file-priv=""
,表示不限制檔案生成的位置,不安全 -
secure-file-priv="/XXX"
,要求生成的檔案只能存放在指定的目錄或其子目錄 -
secure-file-priv=NULL
,表示禁止在這個MySQL例項上執行SELECT...INTO OUTFILE
-
-
SELECT...INTO OUTFILE
語句- 將結果儲存在 服務端
- 不會覆蓋檔案
- 原則上一個資料行對應文字檔案的一行
- 不會生成表結構檔案
- mysqldump提供
--tab
引數,可以同時匯出 表結構定義檔案 和 csv資料檔案
- mysqldump提供
LOAD DATA
LOAD DATA INFILE '/tmp/t.csv' INTO TABLE db2.t;
- 開啟檔案
/tmp/t.csv
- 以製表符
\t
作為 欄位間的分隔符 ,以換行符\n
作為 記錄間的分隔符 ,進行資料讀取
- 以製表符
- 啟動事務
- 判斷每一行的 欄位數 和
db.t
是否相同- 如果不相同,則直接報錯, 回滾事務
- 如果相同,則構造這一行,呼叫InnoDB引擎的介面,寫入到表中
- 重複步驟3,直到
/tmp/t.csv
整個檔案讀入完成, 提交事務
主備同步
binlog_format=STATEMENT
- 主庫執行完成後,將
/tmp/t.csv
檔案的內容都 直接寫到binlog檔案 中 - 往binlog檔案寫入
-
LOAD DATA LOCAL INFILE '/tmp/SQL_LOAD_MB-1-0' INTO TABLE db2.t;
-
- 把binlog傳到備庫
- 備庫的應用日誌執行緒在執行這個事務日誌時
- 先把binlog中的t.csv檔案的內容讀出來,寫到本地臨時目錄
/tmp/SQL_LOAD_MB-1-0
- 再執行
LOAD DATA LOCAL
語句,往備庫的db2.t插入跟主庫相同的資料-
LOCAL
,表示執行這條命令的 客戶端 本地檔案(這裡的客戶端即備庫本身)
-
- 先把binlog中的t.csv檔案的內容讀出來,寫到本地臨時目錄
LOCAL
-
LOAD DATA
- 讀取的是 服務端 檔案,檔案必須在
secure_file_priv
指定的目錄或其子目錄
- 讀取的是 服務端 檔案,檔案必須在
-
LOAD DATA LOCAL
- 讀取的是 客戶端 檔案,只需要MySQL客戶端有 訪問這個檔案的許可權 即可
- 此時,MySQL客戶端會 先把本地檔案傳給服務端 ,然後再執行流程
物理拷貝
- 直接把db1.t的frm檔案和ibd檔案拷貝到db2目錄下,是不行的
- 因為一個InnoDB表,除了包含這兩個物理檔案外,還需要 在資料字典中註冊
- MySQL 5.6引入了 可傳輸表空間 ,可以通過匯出+匯入表空間的方式,實現物理拷貝表
執行步驟
- 假設在db1庫下,複製一個跟表t相同的表r
- 執行
CREATE TABLE r LIKE t;
,建立一個 相同表結構 的空表 - 執行
ALTER TABLE r DISCARD TABLESPACE;
,此時r.ibd
檔案會被刪除 - 執行
FLUSH TABLE t FOR EXPORT;
- 此時在db1目錄下會生成
t.cfg
檔案 - 整個
db1.t
處於 只讀 狀態,直到執行UNLOCK TABLES
- 此時在db1目錄下會生成
- 在db1目錄下執行
cp t.cfg r.cfg
和cp t.ibd r.ibd
- 執行
UNLOCK TABLES;
,此時t.cfg
檔案會被刪除 - 執行
ALTER TABLE r IMPORT TABLESPACE;
- 將這個
r.ibd
檔案作為表r新的表空間 - 由於這個檔案的內容與
t.ibd
是相同的,因此表r中資料與表t相同 - 為了讓檔案裡的表空間id和資料字典中的一致,會修改
r.ibd
的表空間id- 而 表空間id 存在於 每一個數據頁
- 如果是一個很大的檔案,每個資料頁都需要修改,
IMPORT
語句會需要點時間 - 但相對於邏輯拷貝的方法,
IMPORT
語句的耗時還是非常短的
- 將這個
mysql> CREATE TABLE r LIKE t; Query OK, 0 rows affected (0.41 sec) mysql> SYSTEM ls /usr/local/var/mysql/db1 r.ibdt.ibd -- 刪除r.ibd mysql> ALTER TABLE r DISCARD TABLESPACE; mysql> SYSTEM ls /usr/local/var/mysql/db1 t.ibd -- 生成t.cfg,t處於只讀狀態 mysql> FLUSH TABLE t FOR EXPORT; mysql> SYSTEM ls /usr/local/var/mysql/db1 t.cfgt.ibd mysql> UNLOCK TABLES; Query OK, 0 rows affected (0.00 sec) mysql> ALTER TABLE r IMPORT TABLESPACE; Query OK, 0 rows affected (0.23 sec) mysql> SELECT COUNT(*) from r; +----------+ | COUNT(*) | +----------+ |1000 | +----------+
小結
- 物理拷貝速度最快 ,尤其對於大表來說
- 必須 全表拷貝
- 需要 到伺服器上拷貝資料
- 不支援跨引擎使用 ,源表和目標表都是使用InnoDB引擎
- mysqldump生成包含
INSERT
語句的方法,加上where
過濾,可以只匯出 部分資料- 不支援類似join等複雜的寫法
- 邏輯拷貝,支援 跨引擎 使用
-
SELECT...INTO OUTFILE
最靈活,支援 所有的SQL語法- 每次只能匯出一張表的資料,而且表結構需要另外的語句單獨備份
- 邏輯拷貝,支援 跨引擎 使用
參考資料
《MySQL實戰45講》
轉載請註明出處:http://zhongmingmao.me/2019/03/16/mysql-copy-table/
訪問原文「MySQL -- 拷貝表」獲取最佳閱讀體驗並參與討論