從畢業到如今第一次接觸到超過30萬條數據導入MySQL的場景(有點low),就是在順豐公司接入我司EMM產品時須要將AD中的員工數據導入MySQL中,所以樓主負責的模塊connector就派上了用場。在樓主的努力下,線上數據同步代碼經歷了從最初的將近16個小時(而且還出現其餘問題這些問題,等後面慢慢細說),到最終25分鐘的性能優化。css
打個廣告,樓主本身造的輪子,感興趣的請點github.com/haifeiWu/li…mysql
樓主負責的connector模塊以前經歷過的最大的數據量也僅僅是幾千條,固然面對幾千條數據代碼也是跑的及其的快,沒有啥影響,然而當第一次在順豐的正式環境上線時,因爲數據量比較大,樓主的代碼又是串行執行的,事務保持的時間就至關長,也就所以出現了下面的錯誤信息:git
Lock wait timeout exceeded; try restarting transaction
複製代碼
這裏來講一下報這個錯的解決方案,查看MySQL是否有鎖github
show OPEN TABLES where In_use > 0;
複製代碼
另外,在information_schema下面有三張表:INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS(解決問題方法),經過這三張表,能夠更簡單地監控當前的事務並分析可能存在的問題。web
比較經常使用的列:sql
kill 進程ID
,發生上面錯誤的根本緣由在業務邏輯代碼對數據庫的操做無視了大數據量的狀況,好比當數據量比較大時就會出現剛剛修改完這條記錄,接着再次修改就會出現上述出現的問題。數據庫
因爲數據量比較大,首先想到的方法是拿到數據後將數據分拆成n份,由多個線程併發執行數導入的操做。由此引出多線程的問題,在處理多線程問題時共享的數據結構像Map,List應該採用jdk提供的current包下的數據結構,另外在涉及到操做數據庫的地方應該加鎖,樓主用的是jdk提供的ReentrantLock。使用jdk自帶的 jvisualvm
,進行代碼的監控進而找到最佳線程數,下面是監控的數據, 性能優化
下面是線程池的建立代碼bash
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
singleThreadPool.execute(()-> System.out.println(Thread.currentThread().getName()));
singleThreadPool.shutdown();
複製代碼
果不其然,在使用多線程以後,數據插入效率由原來的十幾個小時降到了三個小時,而後並無達到咱們的預期效果,咱們繼續。session
/druid
的攔截<!-- druid 數據庫監控 -->
<filter>
<filter-name>DruidWebStatFilter</filter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
<init-param>
<param-name>exclusions</param-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,*.jsp,/druid/*,/download/*</param-value>
</init-param>
<init-param>
<param-name>sessionStatMaxCount</param-name>
<param-value>2000</param-value>
</init-param>
<init-param>
<param-name>sessionStatEnable</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>principalSessionName</param-name>
<param-value>session_user_key</param-value>
</init-param>
<init-param>
<param-name>profileEnable</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DruidWebStatFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
<!--<init-param>
<param-name>allow</param-name>
<param-value>*.*.*.*</param-value>
</init-param>-->
<init-param>
<!-- 容許清空統計數據 -->
<param-name>resetEnable</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<!-- 用戶名 -->
<param-name>loginUsername</param-name>
<param-value>druid</param-value>
</init-param>
<init-param>
<!-- 密碼 -->
<param-name>loginPassword</param-name>
<param-value>druid</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
複製代碼
<!-- 數據源配置, 使用 BoneCP 數據庫鏈接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 數據源驅動類可不寫,Druid默認會自動根據URL識別DriverClass -->
<property name="driverClassName" value="${jdbc.driver}" />
<!-- 基本屬性 url、user、password -->
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${jdbc.pool.init}" />
<property name="minIdle" value="${jdbc.pool.minIdle}" />
<property name="maxActive" value="${jdbc.pool.maxActive}" />
<!-- 配置獲取鏈接等待超時的時間 -->
<property name="maxWait" value="60000" />
<!-- 配置間隔多久才進行一次檢測,檢測須要關閉的空閒鏈接,單位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一個鏈接在池中最小生存的時間,單位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="${jdbc.testSql}" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="true" />
<property name="testOnReturn" value="false" />
<!-- 配置監控統計攔截的filters -->
<property name="filters" value="stat" />
</bean>
複製代碼
經過命令jstat -gc pid
來查看程序的gc狀況,下面是樓主程序數據同步完成以後的gc狀況
這裏寫代碼片S0C、S1C、S0U、S1U:Survivor 0/1區容量(Capacity)和使用量(Used)
EC、EU:Eden區容量和使用量
OC、OU:年老代容量和使用量
PC、PU:永久代容量和使用量
YGC、YGT:年輕代GC次數和GC耗時
FGC、FGCT:Full GC次數和Full GC耗時
複製代碼
經過上面的優化過程,樓主的connector的數據同居效率也由小時級降低到了分鐘級,同步30+萬的時間能夠在25分鐘內完成。經此一役,樓主算是收穫滿滿。抱着虔誠的心態學習,不浮不躁,快樂成長。