MySQL實戰45講學習筆記----複製表

複製表的方法,將數據從一張表複製到另一張表

創建一個表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

把結果輸出到臨時文件。

這條命令中,主要參數含義如下:

  1. –single-transaction的作用是,在導出數據的時候不需要對錶db1.t加表鎖,而是使用START TRANSACTION WITH CONSISTENT SNAPSHOT的方法;

  2. –add-locks設置爲0,表示在輸出的文件結果裏,不增加" LOCK TABLES t WRITE;" ;

  3. –no-create-info的意思是,不需要導出表結構;

  4. –set-gtid-purged=off表示的是,不輸出跟GTID相關的信息;

  5. –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客戶端執行這個命令的流程是這樣的:

  1. 打開文件,默認以分號爲結尾讀取一條條的SQL語句;

  2. 將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';

我們在使用這條語句時,需要注意如下幾點。

  1. 這條語句會將結果保存在服務端。如果你執行命令的客戶端和MySQL服務端不在同一個機器上,客戶端機器的臨時目錄下是不會生成t.csv文件的。

  2. into outfile指定了文件的生成位置(/server_tmp/),這個位置必須受參數secure_file_priv的限制。參數secure_file_priv的可選值和作用分別是:

    • 如果設置爲empty,表示不限制文件生成的位置,這是不安全的設置;
    • 如果設置爲一個表示路徑的字符串,就要求生成的文件只能放在這個指定的目錄,或者它的子目錄;
    • 如果設置爲NULL,就表示禁止在這個MySQL實例上執行select … into outfile 操作。
  3. 這條命令不會幫你覆蓋文件,因此你需要確保/server_tmp/t.csv這個文件不存在,否則執行語句時就會因爲有同名文件的存在而報錯。

  4. 這條命令生成的文本文件中,原則上一個數據行對應文本文件的一行。但是,如果字段中包含換行符,在生成的文本中也會有換行符。不過類似換行符、製表符這類符號,前面都會跟上“\”這個轉義符,這樣就可以跟字段之間、數據行之間的分隔符區分開。

得到.csv導出文件後,你就可以用下面的load data命令將數據導入到目標表db2.t中。

load data infile '/server_tmp/t.csv' into table db2.t;

這條語句的執行流程如下所示。

  1. 打開文件/server_tmp/t.csv,以製表符(\t)作爲字段間的分隔符,以換行符(\n)作爲記錄之間的分隔符,進行數據讀取;

  2. 啓動事務。

  3. 判斷每一行的字段數與表db2.t是否相同:

    • 若不相同,則直接報錯,事務回滾;
    • 若相同,則構造成一行,調用InnoDB引擎接口,寫入到表中。
  4. 重複步驟3,直到/server_tmp/t.csv整個文件讀入完成,提交事務。

你可能有一個疑問,如果binlog_format=statement,這個load語句記錄到binlog裏以後,怎麼在備庫重放呢?

由於/server_tmp/t.csv文件只保存在主庫所在的主機上,如果只是把這條語句原文寫到binlog中,在備庫執行的時候,備庫的本地機器上沒有這個文件,就會導致主備同步停止。

所以,這條語句執行的完整流程,其實是下面這樣的。

  1. 主庫執行完成後,將/server_tmp/t.csv文件的內容直接寫到binlog文件中。

  2. 往binlog文件中寫入語句load data local infile ‘/tmp/SQL_LOAD_MB-1-0’ INTO TABLE `db2`.`t`。

  3. 把這個binlog日誌傳到備庫。

  4. 備庫的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命令有兩種用法

  1. 不加“local”,是讀取服務端的文件,這個文件必須在secure_file_priv指定的目錄或子目錄下;

  2. 加上“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,具體的執行步驟如下:

  1. 執行 create table r like t,創建一個相同表結構的空表;

  2. 執行alter table r discard tablespace,這時候r.ibd文件會被刪除;

  3. 執行flush table t for export,這時候db1目錄下會生成一個t.cfg文件;

  4. 在db1目錄下執行cp t.cfg r.cfg; cp t.ibd r.ibd;這兩個命令;

  5. 執行unlock tables,這時候t.cfg文件會被刪除;

  6. 執行alter table r import tablespace,將這個r.ibd文件作爲表r的新的表空間,由於這個文件的數據內容和t.ibd是相同的,所以表r中就有了和表t相同的數據。

至此,拷貝表數據的操作就完成了。這個流程的執行過程圖如下:

圖3 物理拷貝表

關於拷貝表的這個流程,有以下幾個注意點:

  1. 在第3步執行完flsuh table命令之後,db1.t整個表處於只讀狀態,直到執行unlock tables命令後才釋放讀鎖;

  2. 在執行import tablespace的時候,爲了讓文件裏的表空間id和數據字典中的一致,會修改t.ibd的表空間id。而這個表空間id存在於每一個數據頁中。因此,如果是一個很大的文件(比如TB級別),每個數據頁都需要修改,所以你會看到這個import語句的執行是需要一些時間的。當然,如果是相比於邏輯導入的方法,import語句的耗時是非常短的。

幾種方法優缺點

  1. 物理拷貝的方式速度最快,尤其對於大表拷貝來說是最快的方法。如果出現誤刪表的情況,用備份恢復出誤刪之前的臨時庫,然後再把臨時庫中的表拷貝到生產庫上,是恢復數據最快的方法。但是,這種方法的使用也有一定的侷限性:

    • 必須是全表拷貝,不能只拷貝部分數據;
    • 需要到服務器上拷貝數據,在用戶無法登錄數據庫主機的場景下無法使用;
    • 由於是通過拷貝物理文件實現的,源表和目標表都是使用InnoDB引擎時才能使用。
  2. 用mysqldump生成包含INSERT語句文件的方法,可以在where參數增加過濾條件,來實現只導出部分數據。這個方式的不足之一是,不能使用join這種比較複雜的where條件寫法。

  3. 用select … into outfile的方法是最靈活的,支持所有的SQL寫法。但,這個方法的缺點之一就是,每次只能導出一張表的數據,而且表結構也需要另外的語句單獨備份。

後兩種方式都是邏輯備份方式,是可以跨引擎使用的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章