最近在寫一個java的爬蟲程序時,遇到了一個大量數據進行插入更新和大量數據循環查詢的問題,所以查了一下一般的調優的方式,下面主要介紹我采取的調優措施。
一 、調優思路
先說說我采取方式的調優的思路,這樣便於理解我的選取的調優策略。
思路分析
首先我們都知道計算機存儲空間分為:寄存器、高速緩存、內存、交換區(外部存儲虛擬化)、硬盤以及其他的外部存儲。而且我們都知道從寄存器開始到硬盤讀寫速度是從快到慢依次遞減。我們訪問數據庫,一般是通過運行的代碼去訪問數據庫,運行起來的代碼所需要的數據一般會放在內存或者是在高速緩存中,而數據庫數據存放在哪?很多人會說應該存放在電腦硬盤中,但是這個只回答對了一半。個人開發,代碼和數據庫在同一個電腦上,但是如果是團隊開發喃?明顯存放在個人的電腦上不合適,一般會存放在團隊開發的服務器上硬盤上。團隊開發時,將服務器硬盤上的數據讀到自己開發電腦的內存中(自己開發測試時)或者上線后從一個數據庫服務器硬盤讀到上線服務器內存(數據庫和程序不在一個服務器上),加上數據表查詢和查詢交互的一些准備(包括一些初始化)所需要的時間將會很多。
最簡朴的sql插入、更新和查詢一般程序一條一條的鏈接數據庫進行操作,這樣耗費的時間非常恐怖。
由此引出我們調優的想法,減少與數據庫交互的次數,將多條查詢,多條插入,多條更新合並為交互一次,也就是批操作。這樣會減少很多時間。多次處理的操作交給java程序在內存中進行處理,內存中處理的速度要快上很多。
二、插入的優化(批插入)
將插入語句進行拼接,多條插入語句拼接成一條插入語句,與數據庫交互一次執行一次。
使用insert into tableName values(),(),(),()語句進行拼接然后再一次性插入。
如果字符串太長,則需要配置下MYSQL,在mysql 命令行中運行 :set global max_allowed_packet = 2*1024*1024*10
我插入1000條的數據耗時為毫秒級別,效率提高很多。
1、下面是代碼可以便於理解:
$sql= "insert into twenty_million (value) values"; for($i=0;$i<2000000;$i++){ $sql.="('50'),"; }; $sql = substr($sql,0,strlen($sql)-1); $connect_mysql->query($sql);
2、我是用java寫的代碼,用的是spring帶的JdbcDaoSupport類寫的dao層,所以粘一下代碼
public void batchInsert(List<SpdrGoldEtfPostions> spdrGoldEtfPostionsList) { int size = spdrGoldEtfPostionsList.size(); String sql = "insert into " + TABLE_NAME + "(" + COLUMN_WITHOUT_ID + ") values"; StringBuffer sbf = new StringBuffer(sql); for (int i = 0; i < size - 1; i++) { sbf.append("('").append(spdrGoldEtfPostionsList.get(i).getSpdrEftId()).append("','") .append(spdrGoldEtfPostionsList.get(i).getSpdrEftDate()) .append("',"); sbf.append(spdrGoldEtfPostionsList.get(i).getTotalNetAssetValue()).append("),"); } sbf.append("('").append(spdrGoldEtfPostionsList.get(size - 1).getSpdrEftId()).append("','") .append(spdrGoldEtfPostionsList.get(size - 1).getSpdrEftDate()) .append("',"); sbf.append(spdrGoldEtfPostionsList.get(size - 1).getTotalNetAssetValue()).append(")"); sql = sbf.toString(); this.getJdbcTemplate().update(sql); }
三、更新優化(批更新)
將更新語句進行拼接,多條更新語句拼接成一條更新語句,與數據庫交互一次執行一次。
1、下面是sql語句的批更新語句,提供便於理解
UPDATE book SET Author = CASE id WHEN 1 THEN '黃飛鴻' WHEN 2 THEN '方世玉' WHEN 3 THEN '洪熙官' END WHERE id IN (1,2,3)
2、下面java寫的spring帶的JdbcDaoSupport類寫的dao層的批更新語句
public void batchUpdateBySpdrEftDate(List<SpdrGoldEtfPostions> spdrGoldEtfList) { int size = spdrGoldEtfList.size(); String sql = "UPDATE " + TABLE_NAME + " set total_net_asset_value = CASE spdr_eft_date\n"; StringBuffer sbf = new StringBuffer(sql); for (int i = 0; i < size; i++) { sbf.append("WHEN ").append(spdrGoldEtfList.get(i).getSpdrEftDate()).append(" THEN ") .append(spdrGoldEtfList.get(i).getTotalNetAssetValue()).append("\n"); } sbf.append("END\n").append("WHERE spdr_eft_date IN("); for (int i = 0; i < size - 1; i++) { sbf.append(spdrGoldEtfList.get(i).getSpdrEftDate()).append(","); } sbf.append(spdrGoldEtfList.get(size - 1).getSpdrEftDate()).append(")"); sql = sbf.toString(); this.getJdbcTemplate().update(sql); }
四、查詢優化(批量查詢)
將所有的查詢都合並為一條查詢語句,然后返回一個集合,然后處理集合(最好返回的集合是有序的,這樣處理起來比較的方便,在sql語句中可以用order by 或者group by進行排序分類,順便多說一句,使用order by 和group by 的字段最好建立索引,這樣速度更快)
1、首先寫一下sql語句,便於大家理解
select * from tableName where id in (1,2,3,4) order by id
2、下面java寫的spring帶的JdbcDaoSupport類寫的dao層的批查詢語句
public List<SpdrGoldEtfPostions> batchSelectBySpdrEtfDate(String[] spdrEtfDateArray) { String sql = "select * from " + TABLE_NAME; StringBuffer sbf = new StringBuffer(sql); sbf.append(" where spdr_eft_date IN("); for (int i = 0; i < spdrEtfDateArray.length - 1; i++) { sbf.append(spdrEtfDateArray[i]).append(","); } sbf.append(spdrEtfDateArray[spdrEtfDateArray.length - 1]).append(")").append(" ORDER BY spdr_eft_date"); sql = sbf.toString(); List<SpdrGoldEtfPostions> items = this.getJdbcTemplate().query(sql, rowMapper()); return items; }
當然批量查詢你可以改變where后面的限定語句,也可以實現批量查詢,如where id <100 and id>10(這里id<100寫在前面也是優化的思路,這天語句在執行時,會先將范圍控制在100以內,然后在從99給數據中進行查詢限定,這也是優化,所以說,很多小細節都能體現優化),類似這類的也可以實現批量查詢,根據需要改變限定條件實現批量查詢。
五、刪除的優化(批量刪除)
其實看完了批量查詢的話,就可以得到一些關於sql批量刪除的想法了,無非是限定條件上動點手腳。
1、先給一下sql語句便於理解
delete from tableName where id in(1,2,3,4,5,6)
2、下面java寫的spring帶的JdbcDaoSupport類寫的dao層的批刪除語句
public void batchDeleteBySpdrEtfDate(String[] spdrEtfDateArray) { String sql = "delete from " + TABLE_NAME; StringBuffer sbf = new StringBuffer(sql); sbf.append(" where spdr_eft_date IN("); for (int i = 0; i < spdrEtfDateArray.length - 1; i++) { sbf.append(spdrEtfDateArray[i]).append(","); } sbf.append(spdrEtfDateArray[spdrEtfDateArray.length - 1]).append(")"); sql = sbf.toString(); this.getJdbcTemplate().update(sql); }
和查詢同樣道理的,可以通過設定where后面的限定,來實現其他的類批刪除。
六、總結
1、首先,數據量較大的sql優化,采取的是批處理操作,減少與數據庫的交互次數。
2、批處理的sql語句交給java程序去拼接,如果數據量較大時,可以考慮使用StringBuilder代替String,如果考慮線程安全可以考慮StringBuffer(或者其他安全的字符串處理類)拼接。
3、批查詢的時候獲取的集合數據建議排序,獲取有序數據,這樣便於后續java程序的處理。
4、一般的ORM框架都是用的sql語句,而一些sql語句的小的細節都能優化,使用時需要日積月累,平時應該時刻有優化意識。
5、使用過hibernate應該都知道,hibernate有緩存功能,一級二級緩存,這個思路符合我這篇博客優化思路,可以提一下,然后提供繼續優化的思路,對於一些經常操作的數據可以設置高速緩存。
6、在使用sql語句的時候對於經常需要進行order by和group by的字段(列)建立索引,sql查詢避免進行全表掃描,這些在寫sql語句時需要注意。
