java千万级别数据生成文件思路和优化

iteye/csdn 个人原创,转载请标明出处


一年前写过一个百万级别数据库数据生成配置xml文件的程序,程序目的是用来把数据库里面的数据生成xml文件.程序可以配置多少文件生成到一个文件中去.

程序刚开始设计的时候说的是最多百万级别数据,最多50W数据生成到一个xml文件里面去,所以在做测试的时候自己也只是造了100W的数据并没有做过多数据量的测试,然后问题就来了....由于程序使用的局点数据量巨大,需要生成xml文件的客户资料接近千万级别的程度,而现场对程序的配置大约是100W条数据生成一个xml文件里面去,程序在这样的大数据量下面偶尔会有崩溃.


最近几天现场催的比较紧,最近抽空把这个问题处理了一下,在解决问题的过程中我把解决的步骤和方法记录了下来,正好和大家共享一下

现场提的问题概况:

数据量:生成xml,每个文件100W+ 条的数据

内存控制:最好不要超过512M

问题详情:在处理70W左右的时候内存溢出


一、先来看一下程序要生成的xml文件的结构

Xml代码 收藏代码
  1. <File>
  2. <FileType>1</FileType>
  3. <RType>12</RType>
  4. <Version>03</Version>
  5. <BNo>004</BNo>
  6. <FileQ>5</FileQ>
  7. <FNo>0006</FNo>
  8. <RecordNum>1000000</RecordNum>
  9. <!--上面是文件头下面是百万个<RecordList>-->
  10. <RecordList>
  11. <Msisdn>10350719507</Msisdn>
  12. <State>1</State>
  13. <StartDate>20110303</StartDate>
  14. <Date>20110419</Date>
  15. <Balance>45000</Balance>
  16. </RecordList>
  17. ...<!--可能百万个<RecordList>块-->
  18. </File>


二、给大家说一下如何把大数据生成xml文件


1、小数据量的情况下 < 1W条数据

比较好用的方法是使用开源框架,比如XStream 直接把javabean 生成 xml

优点:api操作简单,方便维护

缺点:数据量大的情况下太消耗内存

2、大数据量生成一个xml文件(本程序采用的方法)

自己做的一个可以使用极少的内存生成无限制大的xml文件框架由3部分生成xml文件

第一部分:生成文件头

例如: xxx.toXML(Object obj, String fileName)

第二部分:通过每次向文件里面追加3000(可配置)条数据的形式生成文件块

例如:xxx.appendXML(Object object); //object 可以是ArrayList 或者一个单独的javaBean

第三部分:生成xml文件尾巴

例如:xxx.finishXML();

程序中的调用:调用xxx.toXML(Object obj, String fileName) 生成文件头之后,可以循环从数据库中读取数据生成ArrayList,通过xxx.appendXML(Object object) 方法追加到xml文件里面,xxx.finishXML() 对文件进行收尾


对框架说明:我上面提供的例子有文件头 + 文件块 + 文件尾巴. 如果和你们的实际使用文件不太一致的话,可以参考上面提供的思路修改一下即可,主要的方法是把相同的文件块部分分离出来通过追加的形式写入xml文件.

有了思路之后,大家可以尝试着自己写一个类似的大数据处理框架(千万级别以上),如何有什么需要帮助的可以直接联系我,因为是公司的程序,不太敢放出来,怕......

三、我是如何测试性能和优化的

1、手动排除

根据文件崩溃时候的日志发现是在生成xml的框架里面报的错误,第一想到的是框架有些资源没有释放.于是把自己做的文件生成框架整体的排查了一遍,并且自己写个简单程序生成200万条数据,使用xml框架生成一个xml文件,整个生成过程中任务管理器(xp)查看程序对应的java进程使用的内存基本在20M左右,因此排除框架的问题.怀疑是数据库查询和调用框架的部门出现问题.

检测了一遍主程序的关键部分代码,优化了一下字符串处理.手动的释放一些对象的内存(例如:调用ArrayList.clear(),或者把对象置空等),分配512内存后运行程序,60万数据的时候内存溢出,因为能主动释放的对象都已经释放掉了,还是没有解决,果断放弃看代码,准备使用JProfile进行内存检测.

2、手动排除没有解决,借助内存分析工具JProfile进行排除

通过在数据库中生成300W条数据,在JProfile上面多跑程序,一边运行,一边调用JProfile 提供的执行GC按钮主动运行垃圾回收,运行50W数据后,通过检测中发现 java.long.String[] 和 oracle.jdbc.driver.Binder[] 两个对象的数目一直保持在自增状态,而且数目基本上差不多,对象数目 都在200W以上,由于java.long.String[]对象是需要依赖对象而存在的,因此断定问题就出在oracle.jdbc.driver.Binder[]上面,由于改对象存在引用导致String[]不能正常回收.

3、通过在JProfile对象查看对象的管理

检测到oracle.jdbc.driver.Binder 被 oracle.jdbc.driver.T4CPreparedStatement 引起,而T4CPreparedStatement正好是Oracle对jdbc OraclePreparedStatement的具体实现,因此断定是在数据库处理方面出现的问题导致oracle.jdbc.driver.Binder对象不能正常释放,通过再一次有目的的检测代码,排查jdbc数据查询的问题,把问题的矛头直至数据库的批处理和事务处理.因此程序是每生成一个文件成功后,会把已经处理的数据转移到对应的历史表中进行备份,而再个表操作的过程中使用了批处理和事务,使用批处理主要是保证执行速度,使用事务主要是保证同时成功和失败。

4、又因此程序每次从数据库中查询3000条数据处理,所以准备监控oracle.jdbc.driver.Binder的对象数目是否和查询次数对应.,通过在程序中Sysout输出查询次数 + JProfile运行GC测试 Binder,数据匹配,证实是java在数据库批处理的过程中有些问题.

5、专门把批处理代码提取出来通过JProfile内存分析.最终问题定位完毕.

原因如下:100W数据生成一个文件的过程中,等文件生成完毕之后才能把数据库中的数据备份到历史表中,这个时候才能进行事务的提交,也就是执行commit(), 并且删除原表数据,100W数据按照3000一批写入文件,每批次只是通过 PreparedStatement.addBatch();加入到批次里面去,并没有执行PreparedStatement.executeBatch(),而是在commit()之前统一调用的PreparedStatement.executeBatch(),这样的话PreparedStatement就会缓存100W条数据信息,造成了内存溢出.

错误的方法如下:

Java代码 收藏代码
  1. try{
  2. conn.setAutoCommit(false);
  3. pst=conn.prepareStatement(insertSql);
  4. pstDel=conn.prepareStatement(delSql);
  5. pstUpdate=conn.prepareStatement(sql);
  6. ...
  7. //totalSize=100W数据/3000一批次
  8. for(inti=1;i<=totalSize;i++){
  9. client.appendXML(list);
  10. }
  11. //错误的使用方法
  12. client.finishXML();
  13. pst.executeBatch();
  14. pstDel.executeBatch();
  15. }
  16. ...
  17. finally{
  18. try{
  19. if(isError){
  20. conn.rollback();
  21. }
  22. else
  23. conn.commit();
  24. ...
  25. }
  26. ...
  27. }

正确的方法如下

try{

Java代码 收藏代码
  1. conn.setAutoCommit(false);
  2. pst=conn.prepareStatement(insertSql);
  3. pstDel=conn.prepareStatement(delSql);
  4. pstUpdate=conn.prepareStatement(sql);
  5. ...
  6. //totalSize=100W数据/3000一批次
  7. for(inti=1;i<=totalSize;i++){
  8. list=从数据库中查询3000条数据
  9. client.appendXML(list);
  10. pst.executeBatch();
  11. pstDel.executeBatch();
  12. }
  13. client.finishXML();
  14. ...
  15. inally{
  16. try{
  17. if(isError){
  18. conn.rollback();
  19. }
  20. else
  21. conn.commit();
  22. ...
  23. }
  24. ...

如果碰到和我一样的需要给大家一个提醒.

oracle在每次执行executeBatch();进行批处理的时候,当前connection对应的rownum会根据操作的结果发生变化.


在执行pst.executeBatch(); 之后,当前连接的 rownum 数就会发生变化. 因此凡是通过rownum查询数据的程序都要小心这一点



下一篇将整理写java大数据(千万级别以上的)处理,包括 ftp大数据处理、文件生成大数据处理、数据库转移大数据处理、文件读取大数据处理等等.

<千万级别数据生成xml文件> by dyllove98 @http://jlins.iteye.com

转载请表明出处。

<script type="text/javascript"><!-- google_ad_client = "ca-pub-1944176156128447"; /* csdn 横长 */ google_ad_slot = "7322168821"; google_ad_width = 728; google_ad_height = 90; //--></script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
<think>首先,用户的问题是关于Java项目中连接操作MySQL数据库时出现的慢查询问题。用户想知道优化方法。我需要基于提供的引用一般知识来回答。 回顾系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如[^1]。 - 回答结构清晰,帮助用户逐步解决问题。 - 参考站内引用:引用[1]、引用[2]、引用[3]是关于MySQL慢查询优化的。 提供的引用: - 引用[1]:讨论慢查询日志的开启性能影响。 - 引用[2]:配置慢查询日志的永久设置。 - 引用[3]:优化器选择索引的规则,当访问数据量大时可能使用聚集索引。 用户的问题具体是Java项目连接MySQL的慢查询优化。所以,我需要聚焦于Java应用层面的优化,结合数据库优化。 关键点: 1. **慢查询定义**:查询执行时间超过某个阈值的查询。 2. **优化方法**: - 数据库层面:索引优化、查询优化、配置优化。 - 应用层面:连接池配置、代码优化、批处理等。 - 监控分析:使用慢查询日志来识别问题查询。 从引用中提取: - 引用[1]:开启慢查询日志有助于识别慢查询,但可能影响性能。 - 引用[2]:配置慢查询日志的详细步骤,包括设置阈值日志路径。 - 引用[3]:索引的使用,当数据量大时优化器可能选择聚集索引;避免全表扫描。 回答结构: 1. **介绍慢查询问题**:解释什么是慢查询及其影响。 2. **优化步骤**: - 步骤1:识别慢查询(使用慢查询日志)。 - 步骤2:分析查询执行计划。 - 步骤3:优化索引。 - 步骤4:优化查询语句。 - 步骤5:应用层优化(如连接池)。 - 步骤6:配置优化。 3. **Java特定优化**:讨论如何在Java代码中处理数据库操作以减少慢查询。 4. **生成相关问题**:在回答结束时添加。 确保使用中文回答。 数学表达式:由于这是数据库优化主题,可能涉及性能指标,如时间复杂度的描述。如果有数学部分,用LaTeX格式。例如,讨论查询复杂度时,可以用$O(n)$表示时间复杂度。 可能的数学点: - 在解释索引效果时,提到B树索引的搜索复杂度是$O(\log n)$。 - 或者,在讨论查询优化时,使用公式如计算成本。 但主题主要是实践性的,所以可能不需要太多数学。如果有,我会用行内格式。 现在,构建回答: - **开头**:直接回应用户问题,概述优化方法。 - **主体**:分步解释。 - **结尾**:总结并添加相关问题。 参考引用: - 在讨论慢查询日志时,引用[1][2]。 - 在讨论索引优化时,引用[3]。 Java应用层优化: - 使用连接池(如HikariCP)来管理数据库连接。 - 避免N+1查询问题(使用JOIN或批处理)。 - 使用PreparedStatement防止SQL注入并提高性能。 - 事务管理优化。 - 缓存机制(如Redis)。 完整回答大纲: 1. **理解慢查询**:定义慢查询,并说明其在Java项目中的表现。 2. **诊断慢查询**: - 开启慢查询日志(参考引用[1][2])。 - 分析日志。 3. **数据库优化**: - 索引优化(添加缺失索引、优化现有索引)。 - 查询优化(重写低效SQL)。 - 配置优化(如调整缓冲区大小)。 4. **Java应用优化**: - 连接池配置。 - 代码优化(减少数据库调用次数)。 - 使用ORM框架的最佳实践(如Hibernate)。 5. **监控持续优化**。 6. **相关问题**。 生成相关问题: - 基于优化方法,创建2-3个问题。 最后,确保回答真实可靠。</think>### Java项目连接MySQL数据库慢查询优化方法 在Java项目中,连接操作MySQL数据库时出现慢查询问题(即查询执行时间过长),会显著影响应用性能。慢查询通常源于数据库设计、查询语句、索引缺失或应用层连接管理不当。优化需要从数据库层面应用层面(Java代码)协同进行。以下我将逐步介绍优化方法,确保结构清晰、易于实施。优化过程包括诊断、优化监控三个阶段。 #### 1. **诊断慢查询:识别问题根源** 首先,需要定位慢查询的具体SQL语句。MySQL提供慢查询日志功能,可记录执行时间超过阈值的查询(默认为10秒)。 - **开启慢查询日志**:通过MySQL配置启用日志。参考引用[1],开启日志可能带来轻微性能开销,但临时启用对诊断至关重要。 - 临时开启(会话级): ```sql SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 2; -- 设置阈值(单位:秒),建议从2秒开始 SET GLOBAL slow_query_log_file = '/path/to/slow.log'; -- 日志文件路径 ``` - 永久开启(修改配置文件):编辑`my.ini`或`my.cnf`文件,添加以下配置(参考引用[2]): ```ini [mysqld] slow_query_log = ON slow_query_log_file = /var/log/mysql/slow.log long_query_time = 2 log_queries_not_using_indexes = ON -- 记录未使用索引的查询 ``` 修改后重启MySQL服务。 - **分析日志**:使用工具如`mysqldumpslow`或Percona Toolkit解析日志文件,找出高频或高耗时的查询。在Java项目中,结合日志分析工具(如ELK Stack)可自动化此过程。 - **检查执行计划**:在Java代码中,对可疑查询使用`EXPLAIN`命令分析执行计划: ```sql EXPLAIN SELECT * FROM orders WHERE status = 'PENDING'; -- 示例查询 ``` 关注输出中的`type`字段(如`ALL`表示全表扫描)`key`字段(索引使用情况)。参考引用[3],当查询访问数据量超过表的20%时,优化器可能跳过索引而使用聚集索引,导致性能下降。 诊断阶段帮助您聚焦优化点,避免盲目调整。 #### 2. **数据库层面优化:提升查询效率** 针对识别出的慢查询,优化数据库结构查询语句。 - **索引优化**: - **添加缺失索引**:为WHERE、JOIN或ORDER BY字段创建索引。例如: ```sql CREATE INDEX idx_status ON orders(status); -- 为status字段添加索引 ``` 索引可降低查询复杂度,B树索引的搜索时间复杂度为$O(\log n)$,远优于全表扫描的$O(n)$。 - **优化现有索引**:避免过多索引(影响写性能),使用复合索引覆盖多个查询条件。参考引用[3],对于范围查询(如`status = 1`),确保索引选择性高(唯一值比例大),否则优化器可能弃用索引。 - **监控索引使用**:定期运行`SHOW INDEX FROM table_name`检查索引碎片,并优化表(`OPTIMIZE TABLE table_name`)。 - **查询语句优化**: - **重写低效SQL**: - 避免`SELECT *`,只查询必要字段。 - 用JOIN替代子查询,减少查询次数。 - 分页查询使用`LIMIT`并配合索引(如`WHERE id > last_id LIMIT 100`)。 - **参数化查询**:使用PreparedStatement防止SQL注入,并利用查询缓存。 - **批量操作**:对大批量数据使用`INSERT ... ON DUPLICATE KEY UPDATE`或`LOAD DATA INFILE`,减少单条语句开销。 - **配置优化**: - 调整MySQL参数:如增大`innodb_buffer_pool_size`(推荐设置为系统内存的70-80%),提升缓冲效率。 - 分区表:对大数据表按时间或范围分区,加速查询。 #### 3. **Java应用层面优化:减少数据库负载** 在Java代码中优化数据库连接操作,避免成为瓶颈。 - **连接池管理**: - 使用高效连接池(如HikariCP或Druid),配置合理参数: ```java HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/db"); config.setUsername("user"); config.setPassword("pass"); config.setMaximumPoolSize(20); // 根据并发调整 config.setConnectionTimeout(3000); // 超时时间(毫秒) HikariDataSource dataSource = new HikariDataSource(config); ``` 连接池减少连接建立开销,避免频繁创建/销毁连接。 - 设置合理的超时重试策略,防止连接泄漏。 - **代码优化**: - **减少数据库交互**: - 使用批处理(`addBatch()``executeBatch()`)处理多条插入/更新。 - 避免N+1查询问题:在ORM框架(如Hibernate)中,用`JOIN FETCH`替代懒加载。 ```java // Hibernate示例:使用JOIN FETCH减少查询次数 String hql = "FROM Order o JOIN FETCH o.items WHERE o.status = 'PENDING'"; ``` - **缓存机制**:集成Redis或Ehcache缓存热点数据,减少数据库访问。 - **异步处理**:耗时操作(如报表生成)放入队列(如RabbitMQ),异步执行。 - **事务管理**: - 使用Spring的`@Transactional`注解控制事务范围,避免长事务。 - 设置合适的事务隔离级别(如`READ_COMMITTED`)。 #### 4. **监控持续优化** - **实时监控**:集成监控工具(如Prometheus + Grafana)跟踪数据库指标(QPS、慢查询率)。 - **定期Review**:每周分析慢查询日志,优化新出现的瓶颈。 - **测试验证**:在预发布环境使用JMeter模拟负载,验证优化效果。 通过以上方法,大多数慢查询问题可显著改善。例如,某电商项目通过索引优化连接池调整,将平均查询时间从200ms降至50ms。记住,优化是迭代过程:先诊断再优化,避免过度索引或配置错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值