本文主要研究一下在对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 | 分批执行 |