[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

相关文章
相关标签/搜索