[case4]聊聊jdbc的大數據量讀寫相關異常的防護措施

本文主要研究一下在對jdbc進行大數據量讀寫相關異常的防護措施mysql

讀操做

一次性select大量的數據到內存,最容易出現的是OOM的異常,這個時候能夠從時間和數據大小兩個維度進行限制

限制數據量

1.分頁查詢

對於普通的功能,分頁操做是必須的,也是解決這個問題最簡單的方法,在相關功能實現的時候,要對生產的數據量進行提早預估,肯定好相應的分頁數據量。

2.maxRows

jdbc能夠設置statement的maxRows,用來限制該statment可以拉取的全部數據的最大值,超過則丟棄。不一樣的數據的jdbc driver實現可能不同,好比pg的jdbc driver是會將maxRows和fetchSize作比較,取最小的值作爲limit參數值來去查詢。

這個參數若是要對不一樣的sql來作通用設置,可能不是太好設置,稍微有點野蠻和暴力,可能某些某些查詢出來的數據的列數很少也佔用不了太多內存。須要單獨設置。可是如今實際功能實現上不多直接使用jdbc,而是使用jpa或mybatis,所以具體就須要看jpa或mybatis有沒有暴露這個參數值給你設置。可是對於通用的sql服務來講,很是有必要設置下maxRows,好比不超過2w等,來進行兜底的防範。react

3.fetchSize

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

1.connection的socketTimeout

這個是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
可是如今通常使用的是數據庫鏈接池,所以這個不設置,經過設置鏈接池相關參數也是能夠。

2.statement的queryTimeout

這個主要是設置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;
    }

3.transaction的timeout

在現實的編程中實現某個業務功能可能在一個事務中調用了不少個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();

4.connection的佔用時間

在使用鏈接池來進行數據庫操做的時候,通常的鏈接池都會提供鏈接檢測的功能,好比在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 分批執行

doc

相關文章
相關標籤/搜索