MySQL表數據遷移自動化


一.本文所涉及的內容(Contents)

  1. 本文所涉及的內容(Contents)
  2. 背景(Contexts)
  3. 設計思路(Design)
  4. 遷移自動化特點(Points)
  5. 實現代碼(SQL Codes)
  6. 參考文獻(References)

二.背景(Contexts)

  之前我寫過關於SQL Server的數據遷移自動化的文章:SQL Server 數據庫遷移偏方,在上篇文章中設計了一張臨時表,這個臨時表記錄搬遷的配置信息,用一個存儲過程讀取這張表進行數據的遷移,再由一個Job進行迭代調用這個存儲過程。

  在這次MySQL的實戰中,我的數據庫已經做了4個分片,分布在不同的4台機器上,每台機器上的數據量有1.7億(1.7*4=6.8億),占用空間260G(260*4=1040G),這次遷移的目的就是刪除掉一些歷史記錄,減輕數據庫壓力,有人說這為什么不使用表分區呢?這跟我們的業務邏輯有關造成無法使用表分區,至於為什么,參考閱讀:MySQL表分區實戰,其中最重要就是唯一索引的問題,擴展閱讀:MySQL當批量插入遇上唯一索引,這篇文章需要了解MySQL的定時器的一些知識:MySQL定時器Events

  本文與SQL Server 數據庫遷移偏方最大的不同就是MySQL的Events不是串行執行的,當作業調用的存儲過程還沒有執行完畢,但又到了調度的時間,MySQL不會等待上次作業完成之后再調度,所以會造成重復調用讀取到相同的數據;而SQL Server並不存在上面的問題。

三.設計思路(Design)

1. 創建一個臨時表TempBlog_Log,這個表用於保存每次轉移數據的ID起始值和結束值,以及搬遷的開始時間和結束時間;(這個ID是我們要遷移表的主鍵,自增字段,唯一標識)

2. 創建一個存儲過程InsertData(),這個存儲過程用於在TempBlog_Log表中插入記錄,創建這個存儲過程是因為MySQL跟SQL Server有些不同,MySQL不支持匿名存儲過程,SQL Server直接執行SQL就可以了,無需為這些SQL再創建一個存儲過程,這就是匿名存儲過程了;

3. 創建一個存儲過程MoveBlogData(),這個存儲過程用於在TempBlog_Log表中讀取記錄,再批量把BlogA數據轉移到BlogB中;這個是核心邏輯,解決了定時器重復調度的問題,詳情見代碼的解釋;

4. 創建一個定時器e_Blog, 這個定時器定時調用存儲過程MoveBlogData(),但是這里存在重復調度的問題,只能通過存儲過程MoveBlogData()進行控制。

四.遷移自動化特點(Points)

1. 該設計適應於大數據的遷移;

2. 可以最小化宕機時間(在轉移的過程中BlogA還是一直在進數據的,只是在最后一部分數據的時候需要短時間的停入庫操作);

3. 可以防止MySQL定時器重復執行所帶來的問題;

4. 可以實時監控數據轉移的進度;

5. 數據遷移可能需要持續好幾天的時間,它能保證BlogB的數據會無限的接近BlogA的數據;

五.實現代碼(SQL Codes)

(一) 創建臨時表TempBlog_Log

-- 創建表
CREATE TABLE TempBlog_Log(
    BeginId INT NOT NULL,
    EndId INT NOT NULL,
    IsDone BIT DEFAULT b'0' NOT NULL,
    BeginTime DATETIME DEFAULT NULL,
    EndTime DATETIME DEFAULT NULL,
PRIMARY KEY(BeginId) 
);

下面就對表結構進行字段解釋:

1) BeginId、EndId都是ServerA遷移表的主鍵值,BeginId表示一次數據遷移的起始值,EndId表示一次數據遷移的結束值,兩個值的差就是這次數據轉移的數據量;

2) IsDone 表示是否已經成功轉移數據;

3) BeginTime表示轉移的開始時間,EndTime表示轉移的結束時間,這兩個字段設置缺省值為NULL很關鍵,是后面進行判斷是否重復執行的依據;

 

(二) 創建存儲過程InsertData()

-- 存儲過程
DELIMITER $$
USE `DataBaseName`$$
DROP PROCEDURE IF EXISTS `InsertData`$$

CREATE DEFINER=`root`@`%` PROCEDURE `InsertData`()
BEGIN
    DECLARE ids_begin,ids_end,ids_increment INT;
    SET ids_begin=130000000;-- 需要轉移開始Id值
    SET ids_end=210000000;-- 需要轉移結束Id值
    SET ids_increment=200000;-- 每次轉移的Id量
    WHILE ids_begin < ids_end DO 
        INSERT INTO TempBlog_Log(BeginId,EndId) VALUES(ids_begin,ids_begin+ids_increment);
        SET ids_begin = ids_begin + ids_increment;
    END WHILE; 
END$$
    
DELIMITER ;

MySQL中不支持匿名存儲過程,所以為了在臨時表TempBlog_Log插入記錄,只能創建一個存儲過程了,如果你還沒寫過MySQL的存儲過程,那么這是一個很好的例子。

1) 為了能在存儲過程中使用MySQL的分隔符“;”,DELIMITER $$表示你以“$$”作為分隔符,你也可以使用“//”;

2) 定義變量時,你需要把所有的變量定義完了,之后再進行賦值,不然會報錯,這跟SQL Server是有區別的;

3) WHILE條件后面需要加DO,而且要以END WHILE;作為結束標記;

4) 作為存儲過程的結束,再次出現“$$”表示已經結束,跟上一個“$$”形成一個整體、過程,並重新設置“;”為分隔符;

5) 執行CALL InsertData();調用上面的存儲過程,插入數據,調用完畢的結果如下圖Figure1所示:

clip_image001

(Figure1:轉移前狀態)

 

(三) 創建保留數據的新表BlogB

  做完上面的准備工作,接下來就是創建與BlogA相同結構的BlogB表了,有些不同的就是不需要在BlogB創建太多的索引,只需要存儲兩個索引就可以了,一個是ID的聚集索引,一個是唯一索引(在批量插入的時候需要判重);

  上面索引是根據我業務上的需求決定的,你需要視情況而定;

 

(四) 創建存儲過程MoveBlogData()

DELIMITER $$
USE `DataBaseName`$$
DROP PROCEDURE IF EXISTS `MoveBlogData`$$

CREATE DEFINER=`root`@`localhost` PROCEDURE `MoveBlogData`()
BEGIN
    DECLARE blog_ids_begin INT;-- Id起始值
    DECLARE blog_ids_end INT;-- Id結束值
    DECLARE blog_ids_max INT;-- BlogA表現在的最大值
    DECLARE blog_begintime INT;-- 執行開始時間
    DECLARE blog_endtime INT;-- 執行結束時間
    -- 查詢TempBlog_Log表還沒有done的記錄
    SELECT BeginId,EndId,BeginTime,EndTime INTO blog_ids_begin,blog_ids_end,blog_begintime,blog_endtime FROM TempBlog_Log WHERE IsDone = 0 ORDER BY BeginId LIMIT 0,1;
    
    -- 防止了定時器的重復執行
    IF(blog_begintime IS NULL AND blog_endtime IS NULL) THEN
        -- 設置當前最大的Id值
        SELECT MAX(ids) INTO blog_ids_max FROM BlogA;
        -- 防止轉移超過當前最大值的Id數據
        IF(blog_ids_begin != 0 AND blog_ids_end != 0 AND blog_ids_max >= blog_ids_end) THEN
            -- 更新執行開始時間
            UPDATE TempBlog_Log SET BeginTime = NOW() WHERE BeginId = blog_ids_begin;
            -- 插入Id段數據,忽略重復值
            INSERT IGNORE INTO BlogB (ID,AuthorID,Content,QUOTE,QuoteID,Author,TIME,Url,ImageUrl,Transmits,Comments,HASH,Site,AuthorUID,TYPE,HotTopic,AddOn,QuoteAuthorID,IDs)
            SELECT ID,AuthorID,Content,QUOTE,QuoteID,Author,TIME,Url,ImageUrl,Transmits,Comments,HASH,Site,AuthorUID,TYPE,HotTopic,AddOn,QuoteAuthorID,IDs
                FROM BlogA WHERE IDs >= blog_ids_begin AND IDs < blog_ids_end;
            -- 更新執行結束時間
            UPDATE TempBlog_Log SET IsDone = 1,EndTime = NOW() WHERE BeginId = blog_ids_begin;
        END IF;
    END IF;
END$$

DELIMITER ;

這個存儲過程是整個搬遷數據的核心代碼,之所以說是核心,是因為它把比較多的細節考慮進去,基本上實現自動化的目的。

1) 代碼中IF(blog_begintime IS NULL AND blog_endtime IS NULL) 防止了定時器的重復執行,兩個值都為NULL的時候表示這個Id段的數據還沒有被轉移,這樣就可以跳過,不執行下面的邏輯;

2) 查詢BlogA的最大值可以防止轉移超過當前BlogA最大值的Id數據,只有當blog_ids_max>=blog_ids_end才符合轉移的條件;

3) 在MySQL中對唯一索引約束的數據操作有很多的關鍵字支持,INSERT IGNORE INTO就是在批量插入過程中只插入沒有的數據,忽略重復的數據;更多唯一索引的信息:MySQL當批量插入遇上唯一索引

4) 查詢中FROM BlogA WHERE IDs >= blog_ids_begin AND IDs < blog_ids_end;需要注意IDs值的閉合關系,不然造成重復數據或者丟失數據;

 

(五) 創建定時器e_Blog

DELIMITER $$

CREATE DEFINER=`root`@`localhost` EVENT `e_blog` 
ON SCHEDULE EVERY 30 SECOND 
STARTS '2012-12-07 14:58:53' 
ON COMPLETION PRESERVE DISABLE 
DO CALL MoveBlogData()$$

DELIMITER ;

這定時器e_Blog的作用是在每隔30 SECOND調用一次存儲過程MoveBlogData(),至於有沒轉移數據那就是存儲過程判斷了,跟定時器的調度頻率完全沒有關系,更多關於定時器的信息:MySQL定時器Events

 

(六) 監控數據轉移的狀態

當定時器啟動后,可以查看TempBlog_Log表監控調度的進度:

clip_image002

(Figure2:轉移中狀態)

Figure2表示正在轉移Id>=225200000到Id<225400000這20W的數據;

你也可以通過下面的SQL進行統計:

SELECT IsDone,COUNT(1) FROM tempblog_log 
GROUP BY IsDone ORDER BY IsDone DESC;

 

(七) 創建索引

  創建保留數據的新表BlogB的時候不要創建不必要的索引,等轉移完數據之后再創建回相關的索引;這樣做的目的是在插入數據的時候不需要對索引進行維護,並且到轉移完之后再創建索引可以讓索引更加沒有索引碎片;

 

(八) 禁用定時器

  當TempBlog_Log表不再更新的時候,我們就可以禁用定時器了。因為BlogA表是一直在進數據的,所以當TempBlog_Log不再更新就說明數據已經基本轉移完畢了(新增的數據量小於20W),這個時候就可以禁用定時器了。

 

(九) 轉移最后數據

  首先停止對BlogA表的入庫操作,通過SQL轉移最后一部分的數據到BlogB中,轉移完之后修改表名就大功告成了。

六.參考文獻(References)

SQL Server 數據庫遷移偏方

MYSQL插入處理重復鍵值的幾種方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM