最近的項目中需要將幾個dump文件(文本格式、1~2G)的記錄導入到mysql數據庫中,由於數據量比較大(幾百萬、上千萬條記錄),有插入記錄,也有更新記錄的,導致插入\更新速度比較慢。
一開始,將單條sql語句交給mysql執行,測試下來,最快一次也要一個半小時。於是想辦法改進之。
(1)針對插入記錄,使用sql語句一次插入多條記錄。實例:
INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(4,5,6),(7,8,9);
具體參見sql語法:
http://dev.mysql.com/doc/refman/5.0/en/insert.html
在程序中,使用循環拼接sql語句,然后一次性丟給myql_query()處理即可。
需要注意的是,sql語句不是越長越好,太長了可能會超出mysql限制(聽同事介紹,mysql的限制是63M)。
我在實測中,我限制sql長度在10M:
1 for(...) 2 { 3 m_strSQL += ...; 4 5 if (m_strSQL.length() > 10485760) //10M 6 { 7 m_pMysqlStatement->execute(m_strSQL.substr(0, m_strSQL.length() - 1)); 8 m_strSQL= "INSERT INTO location (locationid, name, alternatename) VALUES "; 9 } 10 11 }
測試結果為:
8267787條記錄,耗時754秒(數據庫在局域網內的不同機器上),基本滿足要求。
(2)對於更新記錄,由於update語法不支持一次更新多條記錄,無法使用類似“INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(4,5,6),(7,8,9);”的形式進行更新,只能拼接多條sql語句一起更新:
update location set languages = 'zh' where locationid = '12344';update location set postalcode = '14343' where locationid = '3455';
但是這樣一來,問題出現了:
(I)在連接數據庫時需要啟用multi-query支持,允許一次執行多條sql語句。參見:http://www.cnblogs.com/chutianyao/archive/2011/11/07/2239464.html
(II)還需要處理resultset的釋放問題,否則mysql會報錯:"Commands out of sync; you can't run this command now"
(III)針對update語句,雖然並沒有resultset返回,但仍然需要釋放。而由於未知原因(可能是sql語句太長?),釋放resultset非常耗時,最終算下來得不償失。
綜合以上情況,同事提出了另一種解決方法:針對更新記錄,仍然使用insert語句,不過限制主鍵重復時,更新字段。如下:
INSERT INTO location (locationid, languages) VALUES('13243', 'zh'),VALUES('13244', 'en') ON DUPLICATE KEY UPDATE languages=VALUES(languages)
但是這樣又引出了另外一個問題:在一條sql語句中只能更新某些指定的字段,而程序中可能會有不同的更新條件。比如:上一條記錄只更新languages字段,而下一條記錄則只更新postalcode字段,這樣是沒辦法蟹島一條sql語句中的。
解決辦法是:針對這兩個不同的字段,使用不同的sql語句,保證每個sql語句只更新某個字段:
1 for(...) 2 { 3 m_strSQL_Language +=... 4 m_strSQL_Postcode +=... 5 if (m_strSQL_Language.length() > 10485760) //10M 6 { 7 m_strSQL_Language.erase(m_strSQL_Language.length() - 1); 8 m_strSQL_Language += " ON DUPLICATE KEY UPDATE languages=VALUES(languages)"; 9 m_pMysqlStatement->execute(m_strSQL_Language); 10 11 m_strSQL_Language = "INSERT INTO location (locationid, languages) VALUES "; 12 } 13 14 if (m_strSQL_Postcode.length() > 10485760) //10M 15 { 16 m_strSQL_Postcode.erase(m_strSQL_Alternate_Postcode.length() - 1); 17 m_strSQL_Postcode += " ON DUPLICATE KEY UPDATE postalcode=VALUES(postalcode)"; 18 m_pMysqlStatement->execute(m_strSQL_Postcode); 19 20 m_strSQL_Postcode = "INSERT INTO location (locationid, postalcode) VALUES "; 21 } 22 }
問題得到了完美解決。