複製表的方法,將數據從一張表複製到另一張表
創建一個表db1.t,並插入1000行數據,同時創建一個相同結構的表db2.t
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中。
1.insert...select語句
2.mysqldump方法
使用mysqldump命令將數據導出成一組INSERT語句:
mysqldump -h$host -P$port -u$user --add-locks --no-create-info --single-transaction --set-gtid-purged=OFF db1 t --where="a>900" --result-file=/client_tmp/t.sql
把結果輸出到臨時文件。
這條命令中,主要參數含義如下:
-
–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指定了輸出文件的路徑,其中client表示生成的文件是在客戶端機器上的。
通過這條mysqldump命令生成的t.sql文件中就包含了如圖1所示的INSERT語句。
圖1 mysqldump輸出文件的部分結果
可以看到,一條INSERT語句裏面會包含多個value對,這是爲了後續用這個文件來寫入數據的時候,執行速度可以更快。
如果你希望生成的文件中一條INSERT語句只插入一行數據的話,可以在執行mysqldump命令時,加上參數–skip-extended-insert。
然後,你可以通過下面這條命令,將這些INSERT語句放到db2庫裏去執行。
mysql -h127.0.0.1 -P13000 -uroot db2 -e "source /client_tmp/t.sql"
需要說明的是,source並不是一條SQL語句,而是一個客戶端命令。mysql客戶端執行這個命令的流程是這樣的:
-
打開文件,默認以分號爲結尾讀取一條條的SQL語句;
-
將SQL語句發送到服務端執行。
也就是說,服務端執行的並不是這個“source t.sql"語句,而是INSERT語句。所以,不論是在慢查詢日誌(slow log),還是在binlog,記錄的都是這些要被真正執行的INSERT語句。
3.導出CSV文件
另一種方法是直接將結果導出成.csv文件。MySQL提供了下面的語法,用來將查詢結果導出到服務端本地目錄:
select * from db1.t where a>900 into outfile '/server_tmp/t.csv';
我們在使用這條語句時,需要注意如下幾點。
-
這條語句會將結果保存在服務端。如果你執行命令的客戶端和MySQL服務端不在同一個機器上,客戶端機器的臨時目錄下是不會生成t.csv文件的。
-
into outfile指定了文件的生成位置(/server_tmp/),這個位置必須受參數secure_file_priv的限制。參數secure_file_priv的可選值和作用分別是:
- 如果設置爲empty,表示不限制文件生成的位置,這是不安全的設置;
- 如果設置爲一個表示路徑的字符串,就要求生成的文件只能放在這個指定的目錄,或者它的子目錄;
- 如果設置爲NULL,就表示禁止在這個MySQL實例上執行select … into outfile 操作。
-
這條命令不會幫你覆蓋文件,因此你需要確保/server_tmp/t.csv這個文件不存在,否則執行語句時就會因爲有同名文件的存在而報錯。
-
這條命令生成的文本文件中,原則上一個數據行對應文本文件的一行。但是,如果字段中包含換行符,在生成的文本中也會有換行符。不過類似換行符、製表符這類符號,前面都會跟上“\”這個轉義符,這樣就可以跟字段之間、數據行之間的分隔符區分開。
得到.csv導出文件後,你就可以用下面的load data命令將數據導入到目標表db2.t中。
load data infile '/server_tmp/t.csv' into table db2.t;
這條語句的執行流程如下所示。
-
打開文件/server_tmp/t.csv,以製表符(\t)作爲字段間的分隔符,以換行符(\n)作爲記錄之間的分隔符,進行數據讀取;
-
啓動事務。
-
判斷每一行的字段數與表db2.t是否相同:
- 若不相同,則直接報錯,事務回滾;
- 若相同,則構造成一行,調用InnoDB引擎接口,寫入到表中。
-
重複步驟3,直到/server_tmp/t.csv整個文件讀入完成,提交事務。
你可能有一個疑問,如果binlog_format=statement,這個load語句記錄到binlog裏以後,怎麼在備庫重放呢?
由於/server_tmp/t.csv文件只保存在主庫所在的主機上,如果只是把這條語句原文寫到binlog中,在備庫執行的時候,備庫的本地機器上沒有這個文件,就會導致主備同步停止。
所以,這條語句執行的完整流程,其實是下面這樣的。
-
主庫執行完成後,將/server_tmp/t.csv文件的內容直接寫到binlog文件中。
-
往binlog文件中寫入語句load data local infile ‘/tmp/SQL_LOAD_MB-1-0’ INTO TABLE `db2`.`t`。
-
把這個binlog日誌傳到備庫。
-
備庫的apply線程在執行這個事務日誌時:
a. 先將binlog中t.csv文件的內容讀出來,寫入到本地臨時目錄/tmp/SQL_LOAD_MB-1-0 中;
b. 再執行load data語句,往備庫的db2.t表中插入跟主庫相同的數據。
執行流程如圖2所示:
圖2 load data的同步流程
注意,這裏備庫執行的load data語句裏面,多了一個“local”。它的意思是“將執行這條命令的客戶端所在機器的本地文件/tmp/SQL_LOAD_MB-1-0的內容,加載到目標表db2.t中”。
也就是說,load data命令有兩種用法:
-
不加“local”,是讀取服務端的文件,這個文件必須在secure_file_priv指定的目錄或子目錄下;
-
加上“local”,讀取的是客戶端的文件,只要mysql客戶端有訪問這個文件的權限即可。這時候,MySQL客戶端會先把本地文件傳給服務端,然後執行上述的load data流程。
另外需要注意的是,select …into outfile方法不會生成表結構文件, 所以我們導數據時還需要單獨的命令得到表結構定義。mysqldump提供了一個–tab參數,可以同時導出表結構定義文件和csv數據文件。這條命令的使用方法如下:
mysqldump -h$host -P$port -u$user ---single-transaction --set-gtid-purged=OFF db1 t --where="a>900" --tab=$secure_file_priv
這條命令會在$secure_file_priv定義的目錄下,創建一個t.sql文件保存建表語句,同時創建一個t.txt文件保存CSV數據。
4.物理拷貝方法
前面我們提到的mysqldump方法和導出CSV文件的方法,都是邏輯導數據的方法,也就是將數據從表db1.t中讀出來,生成文本,然後再寫入目標表db2.t中。
因爲,一個InnoDB表,除了包含這兩個物理文件外,還需要在數據字典中註冊。直接拷貝這兩個文件的話,因爲數據字典中沒有db2.t這個表,系統是不會識別和接受它們的。
不過,在MySQL 5.6版本引入了可傳輸表空間(transportable tablespace)的方法,可以通過導出+導入表空間的方式,實現物理拷貝表的功能。
假設我們現在的目標是在db1庫下,複製一個跟表t相同的表r,具體的執行步驟如下:
-
執行 create table r like t,創建一個相同表結構的空表;
-
執行alter table r discard tablespace,這時候r.ibd文件會被刪除;
-
執行flush table t for export,這時候db1目錄下會生成一個t.cfg文件;
-
在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相同的數據。
至此,拷貝表數據的操作就完成了。這個流程的執行過程圖如下:
圖3 物理拷貝表
關於拷貝表的這個流程,有以下幾個注意點:
-
在第3步執行完flsuh table命令之後,db1.t整個表處於只讀狀態,直到執行unlock tables命令後才釋放讀鎖;
-
在執行import tablespace的時候,爲了讓文件裏的表空間id和數據字典中的一致,會修改t.ibd的表空間id。而這個表空間id存在於每一個數據頁中。因此,如果是一個很大的文件(比如TB級別),每個數據頁都需要修改,所以你會看到這個import語句的執行是需要一些時間的。當然,如果是相比於邏輯導入的方法,import語句的耗時是非常短的。
幾種方法優缺點
-
物理拷貝的方式速度最快,尤其對於大表拷貝來說是最快的方法。如果出現誤刪表的情況,用備份恢復出誤刪之前的臨時庫,然後再把臨時庫中的表拷貝到生產庫上,是恢復數據最快的方法。但是,這種方法的使用也有一定的侷限性:
- 必須是全表拷貝,不能只拷貝部分數據;
- 需要到服務器上拷貝數據,在用戶無法登錄數據庫主機的場景下無法使用;
- 由於是通過拷貝物理文件實現的,源表和目標表都是使用InnoDB引擎時才能使用。
-
用mysqldump生成包含INSERT語句文件的方法,可以在where參數增加過濾條件,來實現只導出部分數據。這個方式的不足之一是,不能使用join這種比較複雜的where條件寫法。
-
用select … into outfile的方法是最靈活的,支持所有的SQL寫法。但,這個方法的缺點之一就是,每次只能導出一張表的數據,而且表結構也需要另外的語句單獨備份。
後兩種方式都是邏輯備份方式,是可以跨引擎使用的。