工作中需要讀取很多大數據量(1000w條)的文件並寫入到mysql表里,涉及到的技術點主要是數據庫的addbatch及水平分表。
數據庫的寫入場景包括:一條一條的寫入和批量寫入,這里數據庫的批量增加使用mybatis框架完成。
水平分表的意思是本來我們要將1000w的數據寫入到一張表里,但為了考慮未來表容量的擴展,及表的性能要求,將本來寫入一張表轉換成寫入多張表。
我在這里沒有使用一些框架(Cobar Client,Shardbatis,mybatis-shards),而是采用Hash分表來實現的。
首先是Mybatis批量insert
說一下核心部分,整個工程參考我的github,運行的main方法在org/xiongmaotailang/mybatis/batchinsert/DbUtil.java中,涉及到的腳本是sql.txt
需要的數據表示例,包括4個字段。
CREATE TABLE `newspvuv` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `pv` bigint(11) DEFAULT NULL, `uv` bigint(11) DEFAULT NULL, `time` varchar(15) NOT NULL, PRIMARY KEY (`id`) )
接下來看看批處理的mapper.xml文件(工程中org\xiongmaotailang\mybatis\batchinsert\mappers\DataMapper.xml),批量插入方法的定義
<mapper namespace="org.xiongmaotailang.mybatis.batchinsert.mappers.DataMapper"> <insert id="insertPVUV"> insert into ${table}(pv,uv,time) values(#{pv},#{uv},#{time}) </insert> <insert id="batchInsertPVUV" parameterType="java.util.List"> insert into ${table}(pv,uv,time) values <foreach collection="list" item="item" index="index" separator=","> (#{item.pv,jdbcType=INTEGER},#{item.uv,jdbcType=INTEGER},#{item.time,jdbcType=CHAR} ) </foreach> </insert> </mapper>
id="insertPVUV"是一條一條的寫入的配置、id="batchInsertPVUV"是批量寫入的配置。
對上邊二個配置的main方法在DbUtil中。
public static void main(String[] args) throws InterruptedException { testInsert(); testBatchInsert(); } private static void testInsert() { long start=System.currentTimeMillis(); for (int i = 0; i < 1000; i++) { addPvUv(12,i,"123"); } System.out.println("insert 1000 row :"+(System.currentTimeMillis()-start)+"ms"); } private static void testBatchInsert() { long start=System.currentTimeMillis(); List<NewsPvUv> list=new ArrayList<NewsPvUv>(); for (int i = 0; i < 1000; i++) { NewsPvUv n = new NewsPvUv(12, i, "123"); list.add(n); } addPvUv(list); System.out.println("batch insert 1000 row :"+(System.currentTimeMillis()-start)+"ms"); }
上邊對比了對1000條數據的二種寫入方式,在我筆記本的測試結果如下圖,可見批量寫入的性能高效。
水平分表
整個工程參考我的github
方法一:使用MD5哈希
// 使用md5做hash水平分表 public static String getTable(String mark, String prefix, int num) { if (num == 0) return prefix; String temp = md5(mark).substring(0, 2); int hexdec = Integer.parseInt(temp, 16);// 16轉成10進制 int index = hexdec % num + 1; return prefix + "_" + index; } // 提供和php->md5一樣的功能 private static String md5(String txt) { try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(txt.getBytes("GBK")); // 問題主要出在這里,Java的字符串是unicode編碼,不受源碼文件的編碼影響;而PHP的編碼是和源碼文件的編碼一致,受源碼編碼影響。 StringBuffer buf = new StringBuffer(); for (byte b : md.digest()) { buf.append(String.format("%02x", b & 0xff)); } return buf.toString(); } catch (Exception e) { e.printStackTrace(); return null; } }
方法二:使用移位
// 使用移位分表 /* * 如果我們預估我們系統的用戶是100億,單張表的最優數據量是100萬, * 那么我們就需要將UID移動20來確保每個表是100萬的數據,保留用戶表(user_xxxx)四位來擴展1萬張表 */ public static String getTable1(int uid, String prefix) { return prefix + "_" + String.format("%04d",(uid>>20)); }