本文主要研究一下在對jdbc進行大數據量讀寫相關異常的防護措施mysql
一次性select大量的數據到內存,最容易出現的是OOM的異常,這個時候能夠從時間和數據大小兩個維度進行限制
對於普通的功能,分頁操做是必須的,也是解決這個問題最簡單的方法,在相關功能實現的時候,要對生產的數據量進行提早預估,肯定好相應的分頁數據量。
jdbc能夠設置statement的maxRows,用來限制該statment可以拉取的全部數據的最大值,超過則丟棄。不一樣的數據的jdbc driver實現可能不同,好比pg的jdbc driver是會將maxRows和fetchSize作比較,取最小的值作爲limit參數值來去查詢。這個參數若是要對不一樣的sql來作通用設置,可能不是太好設置,稍微有點野蠻和暴力,可能某些某些查詢出來的數據的列數很少也佔用不了太多內存。須要單獨設置。可是如今實際功能實現上不多直接使用jdbc,而是使用jpa或mybatis,所以具體就須要看jpa或mybatis有沒有暴露這個參數值給你設置。可是對於通用的sql服務來講,很是有必要設置下maxRows,好比不超過2w等,來進行兜底的防範。react
jdbc提供fetchSize參數來設置每次查詢按fetchSize分批獲取。不一樣的數據庫的jdbc driver實現不同。sql
好比mysql須要url設置useCursorFetch=true,且設置了statement的fetchSize,這樣才真正的批量fetch,不然是全量拉取數據。在fetch模式下,executeQuery方法不會去獲取第一批數據,而是在resultSet的next方法中實現。好比pg的話在executeQuery方法默認會拉取第一批fetchSize的數據並返回,以後resultSet的next()方法根據須要再去fetch數據庫
使用fetchSize來避免OOM的話有個限制條件,就是須要本身在遍歷resultSet的過程當中邊遍歷數據,邊處理數據。若是不是邊遍歷邊處理,仍是把結果集循環添加到list中返回,在不是reactive模式的編程範式下,這個fetchSize也就失去效果了,由於最後你仍是在內存中堆積全部的數據集再去處理,所以終究會有OOM的風險編程
限制時間的話,有多個維度:segmentfault
這個是jdbc中最底層的鏈接socket的timeout參數設定,能夠用來防止數據庫因爲網絡緣由或自身問題重啓致使鏈接阻塞,這個是很是有必要設置的,通常是在鏈接url中設置好比mysqltomcat
jdbc:mysql://localhost:3306/ag_admin?useUnicode=true&characterEncoding=UTF8&connectTimeout=60000&socketTimeout=60000
好比pg,pg的單位與mysql不一樣,mysql是毫秒,而pg是秒
jdbc:postgresql://localhost/test?user=fred&password=secret&&connectTimeout=60&socketTimeout=60
可是如今通常使用的是數據庫鏈接池,所以這個不設置,經過設置鏈接池相關參數也是能夠。
這個主要是設置statement的executeQuery的執行超時時間,即從client端發出查詢指令到接收到第一批數據的超時時間,一般是經過timer來實現的。可是這個在不一樣的數據庫的jdbc driver的實現上有所不一樣,好比在fetch模式下mysql的executeQuery不會獲取第一批數據,而pg則會順帶拉取第一批數據再返回。這個參數只有在不是fetch模式下,即一次性查詢全部數據,才相對符合語義。若是是fetch模式,該超時時間限制不了後續幾批數據的拉取超時,他們只能取決於connection的socketTimeout參數。服務器
mybatis能夠經過defaultStatementTimeout參數來設置該值
jpa能夠經過query hit來設置網絡
@QueryHints(@QueryHint(name = org.hibernate.jpa.QueryHints.SPEC_HINT_TIMEOUT, value = "1000"/*ms**/)) List<DemoUser> findAll();
jdbc template能夠經過參數來設置
@Bean(name = "pgJdbcTemplate") public JdbcTemplate pgJdbcTemplate( @Qualifier("pgDataSource") DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.setQueryTimeout(10*1000); jdbcTemplate.setMaxRows(10*1000); return jdbcTemplate; }
在現實的編程中實現某個業務功能可能在一個事務中調用了不少個statement的查詢,transaction能夠以事務爲單位來限制這批操做的超時間。能夠設置全局的超時時間mybatis
@Bean @Qualifier("pgTransactionManager") PlatformTransactionManager pgTransactionManager() { JpaTransactionManager transactionManager = new JpaTransactionManager(pgEntityManagerFactory().getObject()); transactionManager.setDefaultTimeout(60 /*seconds*/); return transactionManager; }
也能夠在transactional註解中單獨設置,好比
@Transactional(timeout=5) /**5 seconds*/ public List<DemoUser> findAll();
在使用鏈接池來進行數據庫操做的時候,通常的鏈接池都會提供鏈接檢測的功能,好比在borrow的時候驗證下鏈接是不是ok的另外還提供對鏈接佔用的超時suspect和abandon操做,來檢測鏈接泄露,若是上面那些操做都沒有設置或(默認)設置的值太大不合理,那麼這個檢測就是除了socketTimeout外的兜底操做了。若是鏈接被借出超過指定時間未歸還,則斷定爲鏈接泄露,則會強制abandon,即close掉鏈接,很是暴力,但也很是有用,防止線程阻塞在數據庫操做最後致使服務504或502
相似fetchSize,對於大量數據的插入或更新操做,jdbc提供了batch方法,用來批量操做。所以對於大規模的數據操做時要注意內存中堆積的數據量,記得分批釋放調用。比較適合使用原生的jdbc來操做,jpa的save方法仍是如今內存中對接了大量對象,在flush的時候才執行批量和釋放。
對於jdbc的大量數據讀寫操做,要額外注意內存中對象的堆積,防止OOM。另外對於數據庫操做的超時時間也要額外注意設置,防止服務器線程阻塞致使沒法提供服務。
操做 | 類別 | 參數 | 備註 |
---|---|---|---|
讀 | 數量 | pageSize | 分頁查詢 |
讀 | 數量 | maxRows | 限制一次或分fetch查詢的全部數據量上限 |
讀 | 數量 | fetchSize | 限制statement的query及result的next每次分批查詢的大小 |
讀 | 時間 | connection socketTimeout | 底層socket鏈接的讀超時 |
讀 | 時間 | statement queryTimeout | 限制statement的query超時 |
讀 | 時間 | transaction timeout | 限制事務執行的超時時間 |
讀 | 時間 | connection remove abandon timeout | 限制鏈接借用超時時間 |
寫 | 數量 | batch execute | 分批執行 |