MySQL兩千萬資料優化&遷移

sibenx發表於2016-08-31

最近有一張2000W條記錄的資料表需要優化和遷移。2000W資料對於MySQL來說很尷尬,因為合理的建立索引速度還是挺快的,再怎麼優化速度也得不到多大提升。不過這些資料有大量的冗餘欄位和錯誤資訊,極不方便做統計和分析。所以我需要建立一張新表,把舊錶中的資料一條一條取出來優化後放回新表;

一. 清除冗餘資料,優化欄位結構

2000W資料中,能作為查詢條件的欄位我們是預知的。所以將這部分資料單獨建立新的欄位,對於有規則的資料合理改變欄位結構,比如身份證就是varchar(18)。對於不重要的資料我們合併後存在一個結構為text的欄位。

對於一些有關聯的資料我們需要計算,常見的比如身份證種能獲取到準確的性別,出生地、生日、年齡。

二. 資料遷移

我們從資料庫中取出一條舊資料,再通過計算處理後得到想要的新資料,最後將新資料插入新表。不過在獲取新資料時遇到如下問題。

  1. 資料量太大,無法一次獲取(2000W資料扔到記憶體挺可怕的);

    我們可以通過MySQL的limit語法分批獲取。比如每次獲取50000,SQL語句如下:

    select * from table_name limit 15000000,50000;

    通過這種方法能解決資料量太大的問題,但是隨著limit的第一個引數越來越大,查詢速度會慢的嚇人(上面這條SQL執行會花35秒)。時間就是生命,於是我們開始優化SQL語句,優化後變成下面這樣:

    select * from table_name order by id desc limit 5000000,50000;

    可通過二分法拆分2000W資料,當執行到1000W資料時,將資料倒序。優化後SQL執行效率顯著提升,從35秒降到9秒;

    不過還是很慢,時間就是生命……還好我們有自增ID(建立資料表第一條定律,一定要有自增欄位),優化後的SQl如下:

    1. select * from table_name where id>15000000 and id<15050000;
    2. select * from table_name where id>15000000 limit 50000; 

    為了直觀演示,我寫了兩條功能一樣的SQL。相比第一條,第二條的limit會導致SQL的索引命中變差,效率同樣也會下降。第一條SQL的執行時間是2毫秒,第二條執行時間5毫秒(我取的平均值)。每次資料的查詢速度直接從35秒降到2毫秒……

  2. 資料量太大並且資料無法預估,某些特殊資料會導致資料匯入失敗;

    我們有三種方案去將新資料存入新表,分別如下:

    1. 一條一條插入資料;

    開始肯定會想這種方案一定不行,因為每次插入都會有一次資料庫IO操作。但是該方案有個好處是能及時發現有問題的資料,修改後再繼續執行; 在Oracle中使用『繫結變數』能帶來效能提升,正好MySQL也提供了『繫結變數』的功能。於是在不改變邏輯的情況下,嘗試優化資料儲存速度。程式碼如下:

    public function actionTest(array $data)
    {
        $mysqli = new mysqli("192.168.1.106", "username", "password", "test");
        $sql = "insert into table_name(name,identity) values (?,?)";
    
        $stmt = $connection->prepare($sql);
        $name = "";
        $identity = "";
        //使用繫結變數
        $stmt->bind_param("si", $name, $identity);
        foreach($data as $val)
        {
            $name = $val[name];
            $identity = $val[card_id];
            //執行
            $stmt->execute();
        }
        $stmt->close();
    }

    最後效果不怎麼好,MySQL的『繫結變數』並沒帶來明顯的速度提升,不過能有效的防止SQL隱碼攻擊;

    1. 一次插入50000條資料;

    這是我最後選中的方案,一是能及時發現有問題的資料,二是匯入資料非常穩定。就像支援斷點續傳一樣,每一步都能看到效果。在執行指令碼時,也能同步開始寫分析邏輯;

    1. 組裝成SQL檔案,最後統一匯入;

    組裝一個大的SQL檔案,最後通過MySQL自帶的工具匯入也是極好的。但如果有一條SQL有問題,你可能需要重跑一次指令碼。因為在9G大小的文字檔案中修改一個符號是很痛苦的事情……

三. 總結

通過各種優化,最後將指令碼執行時間縮短到了20分鐘內。優化後資料質量得到了較高保證,下次將嘗試2億資料的優化&遷移……

PS:原文地址 http://blog.it2048.cn/article_2000w-data.html 之後我會陸續把自己部落格遷移到雲棲社群,希望大家關注!


相關文章