您的位置  > 互联网

实例讲述批量处理海量数据的方法:绕过API

从性能角度来看,大量的批处理实际上是非常不可取的,并且浪费了大量的内存。 从其机制来看,它首先找出符合条件的数据,放入内存中,然后进行操作。 实际使用中,表现很不理想。 在笔者的实际使用中,使用下面第三种优化方案的数据是:向数据库插入一条数据大约需要30分钟。 哈哈,我晕了。 (我10分钟插入数据(字段比较小))

总结起来,解决性能问题有以下三种方法:

1:绕过API,直接通过JDBC API来做。 该方法具有较好的性能。 也是最快的。

2:使用存储过程。

3:我们仍然使用API​​进行常规批处理,可以修改。 当我们发现到一定数量后,我们可以在完成操作后及时删除数据,.flush();。 驱逐(XX对象集); 这也可以节省一些性能损失。 这个“一定量”需要根据实际情况进行定量参考。 一般在30-60左右,但效果还是不太理想。

1:绕过API,直接通过JDBC API来做。 这种方法性能较好,也是最快的。 (以更新操作为例)

Transaction tx=session.beginTransaction(); //注意用的是hibernate事务处理边界
Connection conn=session.connection();
PreparedStatement stmt=conn.preparedStatement("update CUSTOMER as C set C.sarlary=c.sarlary+1 where c.sarlary>1000");
stmt.excuteUpdate();
tx.commit(); //注意用的是hibernate事务处理边界

在这个小程序中,直接调用JDBC的API来访问数据库,效率非常高,避免了先查询加载到内存,再执行操作带来的性能问题。

2:使用存储过程。 但考虑到安装和程序部署的方便性,不推荐这种方法。 (以更新操作为例)

如果底层数据库(例如)支持存储过程,也可以通过存储过程执行批量更新。 存储过程直接在数据库中运行并且速度更快。 可以在数据库中定义一个名为()的存储过程。 代码如下:

复制代码代码如下:

或 (p_age in ) as begin 设置 AGE=AGE+1 其中 AGE>p_age;end;

上面的存储过程有一个参数p_age,代表客户的年龄。 应用程序可以通过以下方式调用存储过程:

tx = session.beginTransaction();
Connection con=session.connection();
String procedure = "{call batchUpdateCustomer(?) }";
CallableStatement cstmt = con.prepareCall(procedure);
cstmt.setInt(1,0); //把年龄参数设为0
cstmt.executeUpdate();
tx.commit();

从上面的程序可以看出,应用程序也必须绕过API,直接通过JDBC API调用存储过程。

3:我们仍然使用API​​进行常规批处理,可以修改。 当我们发现到一定数量后,我们可以在完成操作后及时删除数据,.flush();。 驱逐(XX对象集); 这也可以节省一些性能损失。 这个“一定量”需要根据实际情况进行定量参考……

(以保存操作为例)

业务逻辑是:我们要向数据库中插入10万条数据

tx=session.beginTransaction();
for(int i=0;i<100000;i++)
{
Customer custom=new Customer();
custom.setName("user"+i);
session.save(custom);
if(i%50==0) // 以每50个数据作为一个处理单元,也就是我上面说的"一定的量",这个量是要酌情考虑的
{
session.flush();
session.clear();
}
}

这样可以使系统保持在一个稳定的范围内...

在项目开发过程中,由于项目需求,我们经常需要向数据库中插入大量的数据。 数量级包括数万、数十万、数百万、甚至数千万。 当这个数据量用于插入操作时,可能会出现异常。 一个常见的异常是(内存溢出异常)。

首先我们简单回顾一下插入操作的机制。 为了维护它的内部缓存,当我们进行插入操作时,所有要操作的对象都会被放到它自己的内部缓存中进行管理。

说到缓存,有内部缓存和二级缓存。 由于这两种缓存的管理机制不同,对于二级缓存,我们可以配置其大小,但对于内部缓存,我们采取“自由放任”的态度,对其容量没有限制。 。 现在我们找到了症结所在。 当我们插入海量数据的时候,生成的对象会被包含在内部缓存中(内部缓存是缓存在内存中的),所以你的系统内存会被一点一滴地吃掉。 ,如果最后系统被“炸”也是情理之中的事情。

让我们思考一下如何更好地处理这个问题? 使用它必须处理一些开发条件。 当然,有些项目更灵活,你可以寻求其他方法。

笔者在此推荐两种方法:

(1):优化,程序采用分段插入的方式,及时清理缓存。

(2):绕过API,直接通过JDBC API进行批量插入。 这种方法性能最好,速度也最快。

对于上面的方法1,基本思路是:优化,设置.jdbc。 配置文件中的参数指定每次提交SQL的次数; 程序采用分段插入的方式及时清除缓存(实现异步写-,允许显式批量处理写操作),即每插入一定量的数据,就及时从内部清除缓存以释放占用的内存。

设置.jdbc。 参数请参考下面的配置。

 ……
50……
 

配置.jdbc的原因。 参数是尽可能少地读取数据库。 .jdbc 越大。 参数值越大,读取数据库的次数越少,速度越快。 从上面的配置可以看出,是等到程序累计到50条SQL才批量提交。

作者还认为.jdbc。 参数值不能设置得尽可能大。 从性能角度来看,这仍然值得商榷。 这个要考虑实际情况,酌情设置。 一般来说,设置30或50即可满足需要。

在程序实现方面,作者以插入10000条数据为例,如

Session session=HibernateUtil.currentSession();
Transatcion tx=session.beginTransaction();
for(int i=0;i<10000;i++)
{
Student st=new Student();
st.setName("feifei");
session.save(st);
if(i%50==0) //以每50个数据作为一个处理单元
{
session.flush(); //保持与数据库数据的同步
session.clear(); //清除内部缓存的全部数据,及时释放出占用的内存
}
}
tx.commit();
……

在一定的数据规模下,这种方式可以将系统内存资源维持在一个相对稳定的范围内。

注:前面提到了二级缓存,这里笔者有必要再提一下。 如果启用了二级缓存,从机械上来说,为了维护二级缓存,当我们进行插入、更新、删除操作时,就会将相应的数据填充到二级缓存中。 性能会有很大的损失,所以作者建议在批处理时禁用二级缓存。

对于方法2,采用传统的JDBC批处理,利用JDBC API进行处理。

部分方法请参考java批处理自执行SQL

看着上面的代码,你是不是总觉得哪里有问题? 是的,你没发现吗? 这还是传统的JDBC编程,一点味道都没有。

上述代码可以修改如下:

Transaction tx=session.beginTransaction(); //使用Hibernate事务处理
Connection conn=session.connection();
PrepareStatement stmt=conn.prepareStatement("insert into T_STUDENT(name) values(?)");
for(int j=0;j++;j<200){
for(int i=0;i++;j<50)
{
stmt.setString(1,"feifei");
}
}
stmt.executeUpdate();
tx.commit(); //使用 Hibernate事务处理边界
……

这个变化非常有趣。 经笔者测试,使用JDBC API进行批量处理的性能比使用API​​提高了近10倍。 毫无疑问,JDBC在性能上是优越的。

批量更新和删除时,对于批量更新操作,找到符合要求的数据,然后进行更新操作。 批量删除也是同样的道理。 首先找出符合条件的数据,然后进行删除操作。

这有两个很大的缺点:

(1):占用大量内存。

(2):处理海量数据时,执行/语句数量巨大,一条/语句只能操作一个对象。 可想而知,如此频繁的数据库操作,性能会很低。

发布后引入了bulk /,用于批量更新/删除操作。 原理是通过一条HQL语句来完成批量更新/删除操作,与JDBC批量更新/删除操作非常相似。 在性能方面,相比批量更新/删除有很大的提升。

Transaction tx=session.beginSession();
String HQL="delete STUDENT";
Query query=session.createQuery(HQL);
int size=query.executeUpdate();
tx.commit();
……

控制台只输出一条删除语句:from,执行语句较少,性能与使用JDBC相差无几。 这是提高性能的好方法。 当然,为了有更好的性能,笔者建议使用JDBC进行批量更新和删除操作。 方法和基础知识点与上面批量插入方法2基本相同,这里不再赘述。

作者这里提供了另一种方法,就是从数据库端考虑提高性能,在程序端调用存储过程。 存储过程在数据库端运行并且速度更快。 以批量更新为例,给出参考代码。

首先创建一个存储过程,名称为:

create or replace produre batchUpdateStudent(a in number) as
begin
update STUDENT set AGE=AGE+1 where AGE>a;
end;

调用代码如下:

Transaction tx=session.beginSession();
Connection conn=session.connection();
String pd="……{call batchUpdateStudent(?)}";
CallableStatement cstmt=conn.PrepareCall(pd);
cstmt.setInt(1,20); //把年龄这个参数设为20
tx.commit();

观察上面的代码,它也是绕过了API,使用JDBC API来调用存储过程,利用了事务边界。 存储过程无疑是提高批处理性能的好方法。 它们直接运行在数据库端,一定程度上将批处理的压力转移到数据库上。

后记

本文所讨论的批处理操作都是以提高性能为基础的,它们只提供了提高性能的一个小方面。

无论采用什么方法来提高性能,都必须根据实际情况来考虑。 为用户提供高效、稳定、满足其需求的系统是重中之重。

希望这篇文章对大家编程有所帮助。