javajava打印程序执行时间中批量执行sql时为什么 批量过大时,整体性能降低

比特客户端
您的位置:
详解大数据
详解大数据
详解大数据
详解大数据
Java在处理大数据的时候一些小技巧
关键字:Java
  众所周知,java在处理数据量比较大的时候,加载到内存必然会导致内存溢出,而在一些数据处理中我们不得不去处理海量数据,在做数据处理中,我们常见的手段是,压缩,并行,临时文件等方法;
  例如,我们要将(不论是什么数据库)的数据导出到一个文件,一般是Excel或文本格式的CSV;对于Excel来讲,对于POI和JXL的接口,你很多时候没有办法去控制内存什么时候向写入,很恶心,而且这些API在内存构造的对象大小将比数据原有的大小要大很多倍数,所以你不得不去拆分Excel,还好,POI开始意识到这个问题,在3.8.4的版本后,开始提供cache的行数,提供了SXSSFWorkbook的接口,可以设置在内存中的行数,不过可惜的是,他当你超过这个行数,每添加一行,它就将相对行数前面的一行写入磁盘(如你设置2000行的话,当你写第20001行的时候,他会将第一行写入磁盘),其实这个时候他些的临时文件,以至于不消耗内存,不过这样你会发现,刷磁盘的频率会非常高,我们的确不想这样,因为我们想让他达到一个范围一次性将数据刷如磁盘,比如一次刷1M之类的做法,可惜现在还没有这种API,很痛苦,我自己做过测试,通过写小的Excel比使用目前提供刷磁盘的API来写大文件,效率要高一些,而且这样如果访问的人稍微多一些磁盘IO可能会扛不住,因为IO资源是非常有限的,所以还是拆文件才是上策;而当我们写CSV,也就是文本类型的文件,我们很多时候是可以自己控制的,不过你不要用CSV自己提供的API,也是不太可控的,CSV本身就是文本文件,你按照文本格式写入即可被CSV识别出来;如何写入呢?下面来说说。。。
  在处理数据层面,如从数据库中读取数据,生成本地文件,写代码为了方便,我们未必要1M怎么来处理,这个交给底层的驱动程序去拆分,对于我们的程序来讲我们认为它是连续写即可;我们比如想将一个1000W数据的数据库表,导出到文件;此时,你要么进行分页,当然用三层包装即可,用limit,不过分页每次都会新的查询,而且随着翻页,会越来越慢,其实我们想拿到一个句柄,然后向下游动,编译一部分数据(如10000行)将写文件一次(写文件细节不多说了,这个是最基本的),需要注意的时候每次buffer的数据,在用outputstream写入的时候,最好flush一下,将缓冲区清空下;接下来,执行一个没有where条件的SQL,会不会将内存撑爆?是的,这个问题我们值得去思考下,通过API发现可以对SQL进行一些操作,例如,通过:PreparedStatement statement = connection.prepareStatement(sql),这是默认得到的预编译,还可以通过设置:
  PreparedStatement statement = connection.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
  来设置游标的方式,以至于游标不是将数据直接cache到本地内存,然后通过设置statement.setFetchSize(200);设置游标每次遍历的大小;OK,这个其实我用过,oracle用了和没用没区别,因为oracle的jdbc API默认就是不会将数据cache到java的内存中的,而mysql里头设置根本无效,我上面说了一堆废话,呵呵,我只是想说,java提供的标准API也未必有效,很多时候要看厂商的实现机制,还有这个设置是很多网上说有效的,但是这纯属抄袭;对于oracle上面说了不用关心,他本身就不是cache到内存,所以java内存不会导致什么问题,如果是mysql,首先必须使用5以上的版本,然后在连接参数上加上useCursorFetch=true这个参数,至于游标大小可以通过连接参数上加上:defaultFetchSize=1000来设置,例如:
  jdbc:mysql://xxx.xxx.xxx.xxx:3306/abc?zeroDateTimeconvertToNull&useCursorFetch=true&defaultFetchSize=1000& /span&
  上次被这个问题纠结了很久(mysql的数据老导致程序内存膨胀,并行2个直接系统就宕了),还去看了很多源码才发现奇迹竟然在这里,最后经过mysql文档的确认,然后进行测试,并行多个,而且数据量都是500W以上的,都不会导致内存膨胀,GC一切正常,这个问题终于完结了。
  我们再聊聊其他的,数据拆分和合并,当数据文件多的时候我们想合并,当文件太大想要拆分,合并和拆分的过程也会遇到类似的问题,还好,这个在我们可控制的范围内,如果文件中的数据最终是可以组织的,那么在拆分和合并的时候,此时就不要按照数据逻辑行数来做了,因为行数最终你需要解释数据本身来判定,但是只是做拆分是没有必要的,你需要的是做二进制处理,在这个二进制处理过程,你要注意了,和平时read文件不要使用一样的方式,平时大多对一个文件读取只是用一次read操作,如果对于大文件内存肯定直接挂掉了,不用多说,你此时因该每次读取一个可控范围的数据,read方法提供了重载的offset和length的范围,这个在循环过程中自己可以计算出来,写入大文件和上面一样,不要读取到一定程序就要通过写入流flush到磁盘;其实对于小数据量的处理在现代的NIO技术的中也有用到,例如多个终端同时请求一个大文件下载,例如视频下载吧,在常规的情况下,如果用java的容器来处理,一般会发生两种情况:
  其一为内存溢出,因为每个请求都要加载一个文件大小的内存甚至于更多,因为java包装的时候会产生很多其他的内存开销,如果使用二进制会产生得少一些,而且在经过输入输出流的过程中还会经历几次内存拷贝,当然如果有你类似nginx之类的,那么你可以通过send_file模式发送出去,但是如果你要用程序来处理的时候,内存除非你足够大,但是java内存再大也会有GC的时候,如果你内存真的很大,GC的时候死定了,当然这个地方也可以考虑自己通过直接内存的调用和释放来实现,不过要求剩余的物理内存也足够大才行,那么足够大是多大呢?这个不好说,要看文件本身的大小和访问的频率;
  其二为假如内存足够大,无限制大,那么此时的限制就是线程,传统的IO模型是线程是一个请求一个线程,这个线程从主线程从线程池中分配后,就开始工作,经过你的Context包装、Filter、拦截器、业务代码各个层次和业务逻辑、访问数据库、访问文件、渲染结果等等,其实整个过程线程都是被挂住的,所以这部分资源非常有限,而且如果是大文件操作是属于IO密集型的操作,大量的CPU时间是空余的,方法最直接当然是增加线程数来控制,当然内存足够大也有足够的空间来申请线程池,不过一般来讲一个进程的线程池一般会受到限制也不建议太多的,而在有限的系统资源下,要提高性能,我们开始有了new IO技术,也就是NIO技术,新版的里面又有了AIO技术,NIO只能算是异步IO,但是在中间读写过程仍然是阻塞的(也就是在真正的读写过程,但是不会去关心中途的响应),还未做到真正的异步IO,在监听connect的时候他是不需要很多线程参与的,有单独的线程去处理,连接也又传统的socket变成了selector,对于不需要进行数据处理的是无需分配线程处理的;而AIO通过了一种所谓的回调注册来完成,当然还需要的支持,当会掉的时候会去分配线程,目前还不是很成熟,性能最多和NIO吃平,不过随着技术发展,AIO必然会超越NIO,目前V8虚拟机引擎所驱动的node.js就是类似的模式,有关这种技术不是本文的说明重点;
  将上面两者结合起来就是要解决大文件,还要并行度,最土的方法是将文件每次请求的大小降低到一定程度,如8K(这个大小是经过测试后网络传输较为适宜的大小,本地读取文件并不需要这么小),如果再做深入一些,可以做一定程度的cache,将多个请求的一样的文件,cache在内存或分布式缓存中,你不用将整个文件cache在内存中,将近期使用的cache几秒左右即可,或你可以采用一些热点的算法来配合;类似下载的断点传送中(不过迅雷的网络协议不太一样),它在处理下载数据的时候未必是连续的,只要最终能合并即可,在端可以反过来,谁正好需要这块的数据,就给它就可以;才用NIO后,可以支持很大的连接和并发,本地通过NIO做socket连接测试,100个终端同时请求一个线程的服务器,正常的WEB应用是第一个文件没有发送完成,第二个请求要么等待,要么超时,要么直接拒绝得不到连接,改成NIO后此时100个请求都能连接上服务器端,服务端只需要1个线程来处理数据就可以,将很多数据传递给这些连接请求资源,每次读取一部分数据传递出去,不过可以计算的是,在总体长连接传输过程中总体效率并不会提升,只是相对相应和所开销的内存得到量化控制,这就是技术的魅力,也许不要太多的算法,不过你得懂他。
  类似的数据处理还有很多,有些时候还会将就效率问题,比如在HBase的文件拆分和合并过程中,要不影响线上业务是比较难的事情,很多问题值得我们去研究场景,因为不同的场景有不同的方法去解决,但是大同小异,明白思想和方法,明白内存和体系架构,明白你所面临的是沈阳的场景,只是细节上改变可以带来惊人的效果。
[ 责任编辑:之极 ]
去年,手机江湖里的竞争格局还是…
甲骨文的云战略已经完成第一阶段…
软件信息化周刊
比特软件信息化周刊提供以数据库、操作系统和管理软件为重点的全面软件信息化产业热点、应用方案推荐、实用技巧分享等。以最新的软件资讯,最新的软件技巧,最新的软件与服务业内动态来为IT用户找到软捷径。
商务办公周刊
比特商务周刊是一个及行业资讯、深度分析、企业导购等为一体的综合性周刊。其中,与中国计量科学研究院合力打造的比特实验室可以为商业用户提供最权威的采购指南。是企业用户不可缺少的智选周刊!
比特网络周刊向企业网管员以及网络技术和产品使用者提供关于网络产业动态、技术热点、组网、建网、网络管理、网络运维等最新技术和实用技巧,帮助网管答疑解惑,成为网管好帮手。
服务器周刊
比特服务器周刊作为比特网的重点频道之一,主要关注x86服务器,RISC架构服务器以及高性能计算机行业的产品及发展动态。通过最独到的编辑观点和业界动态分析,让您第一时间了解服务器行业的趋势。
比特存储周刊长期以来,为读者提供企业存储领域高质量的原创内容,及时、全面的资讯、技术、方案以及案例文章,力求成为业界领先的存储媒体。比特存储周刊始终致力于用户的企业信息化建设、存储业务、数据保护与容灾构建以及数据管理部署等方面服务。
比特安全周刊通过专业的信息安全内容建设,为企业级用户打造最具商业价值的信息沟通平台,并为安全厂商提供多层面、多维度的媒体宣传手段。与其他同类网站信息安全内容相比,比特安全周刊运作模式更加独立,对信息安全界的动态新闻更新更快。
新闻中心热点推荐
新闻中心以独特视角精选一周内最具影响力的行业重大事件或圈内精彩故事,为企业级用户打造重点突出,可读性强,商业价值高的信息共享平台;同时为互联网、IT业界及通信厂商提供一条精准快捷,渗透力强,覆盖面广的媒体传播途径。
云计算周刊
比特云计算周刊关注云计算产业热点技术应用与趋势发展,全方位报道云计算领域最新动态。为用户与企业架设起沟通交流平台。包括IaaS、PaaS、SaaS各种不同的服务类型以及相关的安全与管理内容介绍。
CIO俱乐部周刊
比特CIO俱乐部周刊以大量高端CIO沙龙或专题研讨会以及对明星CIO的深入采访为依托,汇聚中国500强CIO的集体智慧。旨为中国杰出的CIO提供一个良好的互融互通 、促进交流的平台,并持续提供丰富的资讯和服务,探讨信息化建设,推动中国信息化发展引领CIO未来职业发展。
IT专家新闻邮件长期以来,以定向、分众、整合的商业模式,为企业IT专业人士以及IT系统采购决策者提供高质量的原创内容,包括IT新闻、评论、专家答疑、技巧和白皮书。此外,IT专家网还为读者提供包括咨询、社区、论坛、线下会议、读者沙龙等多种服务。
X周刊是一份IT人的技术娱乐周刊,给用户实时传递I最新T资讯、IT段子、技术技巧、畅销书籍,同时用户还能参与我们推荐的互动游戏,给广大的IT技术人士忙碌工作之余带来轻松休闲一刻。
微信扫一扫
关注Chinabytejava&sqlplus&批量执行sql文件
String cmd = "sqlplus user/pwd@fiss223 @C:/TD/init.sql";
Process process = Runtime.getRuntime().exec(cmd);
InputStream stderr = process.getInputStream();
FileOutputStream fs = new FileOutputStream("C:/TD/a.log");
byte[] buffer = new byte[1444];
int byteread = 0;
while ((byteread = stderr.read(buffer)) != -1) {
fs.write(buffer, 0, byteread);
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。&>&java批量执行SQL
java批量执行SQL
上传大小:387B
java批量执行SQL
java批量执行SQL.txt
综合评分:3.4(31位用户评分)
所需积分:
下载个数:206
{%username%}回复{%com_username%}{%time%}\
/*点击出现回复框*/
$(".respond_btn").on("click", function (e) {
$(this).parents(".rightLi").children(".respond_box").show();
e.stopPropagation();
$(".cancel_res").on("click", function (e) {
$(this).parents(".res_b").siblings(".res_area").val("");
$(this).parents(".respond_box").hide();
e.stopPropagation();
/*删除评论*/
$(".del_comment_c").on("click", function (e) {
var id = $(e.target).attr("id");
$.getJSON('/index.php/comment/do_invalid/' + id,
function (data) {
if (data.succ == 1) {
$(e.target).parents(".conLi").remove();
alert(data.msg);
$(".res_btn").click(function (e) {
var q = $("#form1").serializeArray();
console.log(q);
var res_area_r = $.trim($(".res_area_r").val());
if (res_area_r == '') {
$(".res_text").css({color: "red"});
$.post("/index.php/comment/do_comment_reply/", q,
function (data) {
if (data.succ == 1) {
var $target,
evt = e || window.
$target = $(evt.target || evt.srcElement);
var $dd = $target.parents('dd');
var $wrapReply = $dd.find('.respond_box');
console.log($wrapReply);
var mess = $(".res_area_r").val();
var str = str.replace(/{%header%}/g, data.header)
.replace(/{%href%}/g, 'http://' + window.location.host + '/user/' + data.username)
.replace(/{%username%}/g, data.username)
.replace(/{%com_username%}/g, _username)
.replace(/{%time%}/g, data.time)
.replace(/{%id%}/g, data.id)
.replace(/{%mess%}/g, mess);
$dd.after(str);
$(".respond_box").hide();
$(".res_area_r").val("");
$(".res_area").val("");
$wrapReply.hide();
alert(data.msg);
}, "json");
/*删除回复*/
$(".rightLi").on("click",'.del_comment_r', function (e) {
var id = $(e.target).attr("id");
$.getJSON('/index.php/comment/do_comment_del/' + id,
function (data) {
if (data.succ == 1) {
$(e.target).parent().parent().parent().parent().parent().remove();
$(e.target).parents('.res_list').remove()
alert(data.msg);
//填充回复
function KeyP(v) {
$(".res_area_r").val($.trim($(".res_area").val()));
评论共有10条
很有参考价值,是基础试用者能使用的
基本没什么参考价值
还可以, 要是丰富就好了
功能可以实现,但个人觉得可以用记数器~
比较基础的东西,
对sql要求过于严格
对我 没有起到作用
虽然不是完全符合我的要求,但是还是可以直到借鉴作用。
对sql要求过于严格
很基础,但不是我想要的功能。谢谢
审核通过送C币
MATLAb Robotic Toolbox 合集
创建者:kroc_kroc
Genero Studio 2.40.11
创建者:herozzr
创建者:qq_
上传者其他资源上传者专辑
4个网页颜色选择器
JDK_API_1_6_zh_CN
Tortoise SVN与subeclipse版本对应关系
Linux+C编程实战
OpenCL规范中文版
开发技术热门标签
VIP会员动态
前端开发重难点
17年软考最新真题及解析
物联网全栈开发专题
二十大技术领域优质资源
spring mvc+mybatis+mysql+maven+bootstrap 整合实现增删查改简单实例.zip
CSDN&VIP年卡&4000万程序员的必选
java批量执行SQL
会员到期时间:剩余下载个数:
积分不足!
资源所需积分
当前拥有积分
您可以选择
程序员的必选
绿色安全资源
资源所需积分
当前拥有积分
VIP年卡全年1200个资源免积分下载
你当前的下载分为234。
你还不是VIP会员
开通VIP会员权限,免积分下载
你下载资源过于频繁,请输入验证码
你下载资源过于频繁,请输入验证码
您因违反CSDN下载频道规则而被锁定帐户,如有疑问,请联络:!
若举报审核通过,可奖励20下载分
被举报人:
举报的资源分:
请选择类型
资源无法下载
资源无法使用
标题与实际内容不符
含有危害国家安全内容
含有反动色情等内容
含广告内容
版权问题,侵犯个人或公司的版权
*详细原因:JAVA程序性能分析及调优浅析 -
- ITeye博客
博客分类:
搬掉绊脚石,将内容不断靠近用户!
keep it simple, stupid!
关键词:CPU时间占比、当前执行的SQL语句、执行时间过长的方法、代码屏蔽
1. 性能分析本质
寻找系统的性能瓶颈(木桶理论/短板效应),并处理系统的性能瓶颈
2. 性能分析主要指标
负载、响应和服务器CPU\MEM\IO等的使用率
3. 性能分析主要工具
LoadRunner、VisualVM、MySql 客户端工具(或类似工具)和Linux命令(或监控工具)
4. 性能分析及处理思路
避免代码里面的循环数据库查询(梳理业务,基本都可以实现为非循环方式)
避免代码里面的循环数据库更新处理(插入、更新等),尽量采用批量方式
注意合并类似的SQL语句(主要是谓词,即查询条件大致相同),将某些计算放置到程序中,减少对数据库的查询等操作
避免生产新的、耗时和耗内存的对象,即消耗内存,又消耗CPU
比如获取方法调用栈轨迹,有人采用new Throwable().getStackTrace() 方式来获取,就比较有问题了,该对象的生成相对来说很耗资源,测试某个长事务过程中,CPU占比达整个的4%,实际上可以采用Thread.currentThrread.getStackTrace() 来处理该需求
使用private、final和static方法,即使代码结构合理,也能运行更有效率
采用必要的缓存(主要针对不易变化的,非实时性要求的数据,比如字典表,排名等)
前台能处理的,尽量放到前台,以充分利用客户电脑,节约带宽及服务器CPU\MEM等资源
业务是否可以简化?
简化之后是不是可以去掉不必要的代码?是不是可以实现更简单,逻辑更清晰?
对代码的调整很多其实也是对业务的精炼!
4.3. SQL(MySql)
1、开启慢查询(捕获慢查询SQL语句)
在my.cnf文件[mysqld]后面,添加如下:
slow_query_log
slow_query_log_file=/export/servers/mysql/mysql_slow.log
long_query_time=0.1
2、show full processlist
压力测试期间,捕获当前执行SQL语句,重点关注state列和info列
IdUserHostdbCommand TimeStateInfo318236root…db_name7085sending dataSELECT …
state列正常情况下应该都是空(应为执行都很快),但假如你看到了sending data, statistics等,很不幸(如果很多),这往往伴随着CPU时间占比很高,这个时候往往表明你需要调整该SQL语句,或者相关调用代码或者其余处理措施(当然,有时候是负载实在太高了!)
Info列则是当前执行的SQL语句,show full processlist中的full的作用就是你可以查看完整的SQL语句
3、解释相关SQL语句
Explain/相关MySql客户端工具
4、show index from tbname/alter table tbname add index idx_idxname(colname)
根据explain和show index等,对相关sql语句进行索引优化
1、数据库服务器配置,重点关注以下配置(具体参数配置标准请google):
max_connections=1000
key_buffer_size = 16M :主要针对MyISAM存储引擎
table_open_cache=10000
innodb_buffer_pool_size = 10g :主要针对InnoDB存储引擎
query_cache_size=32m
可通过SHOW GLOBAL STATUS LIKE 'qcache%';查看查询缓存的使用情况,单交易压力
测试表征意义不大,要在混合场景稳定性测试等环境中观察
2、应用服务器配置(内存、线程池)
请参考TOMCAT6性能优化文档&
3、数据库连接池
根据实际需要配置、调整,(一般小于线程池数目,可设置为其一半)
5. 瓶颈定位方式
5.1. 基于单交易压力测试
单交易压力测试等,最好排除相关影响因数,比如你要测试一个添加,但你需要先进入一个列表,才能处理添加,这个时候,你可能需要在脚本里面将这个列表的代码去掉,避免该处代码对添加事务的影响,使添加事务更纯净,更易于定位问题
5.2. 监控相关服务器资源使用情况
重点关注CPU时间占比,内存等使用情况,可通过top、vmstat等命令监控
比如此次性能分析过程中数据库库服务器CPU占比很容易很高(往往解决了一个又来了另一个),这时可以通过数据库客户端工具摘取当前执行SQL语句,并通过工具分析(或者自己explain),查看索引使用情况,如果没有索引或不存在有效索引,则添加相关索引,并且注意调用该语句的代码是否是循环处理的,如果是循环处理,请提炼至循环外层
5.3. 通过VisualVM进行CPU、内存使用采样及垃圾回收监控
比如通过CPU采用,分析相关方法执行CPU占比,定位执行时间过长的方法,进行相关优化。
5.4. 通过代码屏蔽方式定位
可以通过屏蔽代码的方式以定位瓶颈点或者确认瓶颈点
注意事项:
1、避免压力测试脚本问题及其与环境、配置问题
&&& 比如压力测试脚本不纯净,服务器对某一IP过多访问存在限制、线程池配置太小,数据库连接池配置太小,文件打开数超过系统限制等
2、避免问题定位错误
&& 某次压测发现CPU占比很高(90%),进行过一定的分析,发现数据库服务器网络负载在
&& 200多M(浏览器压测情况下没有启用缓存,页面图片量比较大),开始认为是网络带宽问题,但实际上,把页面的代码都注释掉(基本空页面),带宽很低,但CPU并没有降低,最后通过添加MySql查询缓存解决该问题!
&& 某次压测发现数据库服务器A的CPU占比很低,但就是TPS很低(正常情况下,数据库没有压力,适当的迸发环境下TPS一般应比较高),用工具连接数据库服务器,执行show processlist 发现state基本为空,很正常,查询相关配置参数,也没做更改。之后想起来还有另外一台数据库服务器B,会不会是B服务器导致的了?在B服务器上top一看,CPU占比90%,show full processlsit数据库一看,state很多sending data,info列重复很多一个查询语句,explain该语句,发现该语句缺少相应的索引!添加相关索引即TPS恢复正常
浏览: 33194 次
来自: 北京
太感谢你了,这个问题困扰我很久了,每次都弹出来。而且还影响ec ...
佩服.用到了.感谢
博主大才,好毅力
牛逼,佩服你Java编程中“为了性能”尽量要做到的一些地方 -
- ITeye博客
/dzly/archive/.html
下面是参考网络资源总结的一些在Java编程中尽可能要做到的一些地方。
1. 尽量在合适的场合使用单例
使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:
第一,控制资源的使用,通过线程同步来控制资源的并发访问;
第二,控制实例的产生,以达到节约资源的目的;
第三,控制数据共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信。2. 尽量避免随意使用静态变量
要知道,当某个对象被定义为stataic变量所引用,那么gc通常是不会回收这个对象所占有的内存,如
此时静态变量b的生命周期与A类同步,如果A类不会卸载,那么b对象会常驻内存,直到程序终止。
3. 尽量避免过多过常的创建Java对象
尽量避免在经常调用的方法,循环中new对象,由于系统不仅要花费时间来创建对象,而且还要花时间对这些对象进行垃圾回收和处理,在我们可以控制的范围内,最大限度的重用对象,最好能用基本的数据类型或数组来替代对象。
4. 尽量使用final修饰符
带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String。为
String类指定final防止了使用者覆盖length()方法。另外,如果一个类是final的,则该类所有方法都是final的。Java编译器
会寻找机会内联(inline)所有的final方法(这和具体的编译器实现有关)。此举能够使性能平均提高50%。
5. 尽量使用局部变量
调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。
6. 尽量处理好包装类型和基本类型两者的使用场所
虽然包装类型和基本类型在使用过程中是可以相互转换,但它们两者所产生的内存区域是完全不同的,基本类型数据产生和处理都在栈中处理,包装类型是对象,是在堆中产生实例。
在集合类对象,有对象方面需要的处理适用包装类型,其他的处理提倡使用基本类型。
7. 慎用synchronized,尽量减小synchronize的方法
都知道,实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。synchronize方法被调用时,直接会
把当前对象锁
了,在方法执行完之前其他线程无法调用当前对象的其他方法。所以synchronize的方法尽量小,并且应尽量使用方法同步代替代码块同步。
8. 尽量使用StringBuilder和StringBuffer进行字符串连接
这个就不多讲了。
9. 尽量不要使用finalize方法
实际上,将资源清理放在finalize方法中完成是非常不好的选择,由于GC的工作量很大,尤其是回收Young代内存时,大都会引起应用程序暂停,所以再选择使用finalize方法进行资源清理,会导致GC负担更大,程序运行效率更差。
10. 尽量使用基本数据类型代替对象
String str = "hello";
上面这种方式会创建一个“hello”字符串,而且JVM的字符缓存池还会缓存这个字符串;
String str = new String("hello");
此时程序除创建字符串外,str所引用的String对象底层还包含一个char[]数组,这个char[]数组依次存放了h,e,l,l,o
11. 单线程应尽量使用HashMap、ArrayList
HashTable、Vector等使用了同步机制,降低了性能。
12. 尽量合理的创建HashMap
当你要创建一个比较大的hashMap时,充分利用另一个构造函数
public HashMap(int initialCapacity, float loadFactor)
避免HashMap多次进行了hash重构,扩容是一件很耗费性能的事,在默认中initialCapacity只有16,而
loadFactor是 0.75,需要多大的容量,你最好能准确的估计你所需要的最佳大小,同样的Hashtable,Vectors也是一样的道理。
13. 尽量减少对变量的重复计算
for(int i=0;i&list.size();i++)
for(int i=0,len=list.size();i&i++)
并且在循环中应该避免使用复杂的表达式,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。
14. 尽量避免不必要的创建
A a = new A();
if(i==1){list.add(a);}
A a = new A();
list.add(a);}
15. 尽量在finally块中释放资源
程序中使用到的资源应当被释放,以避免资源泄漏。这最好在finally块中去做。不管程序执行的结果如何,finally块总是会执行的,以确保资源的正确关闭。
16. 尽量使用移位来代替'a/b'的操作
"/"是一个代价很高的操作,使用移位的操作将会更快和更有效
int num = a / 4;
int num = a / 8;
int num = a && 2;
int num = a && 3;
但注意的是使用移位应添加注释,因为移位操作不直观,比较难理解
17.尽量使用移位来代替'a*b'的操作
同样的,对于'*'操作,使用移位的操作将会更快和更有效
int num = a * 4;
int num = a * 8;
int num = a && 2;
int num = a && 3;
18. 尽量确定StringBuffer的容量
StringBuffer
的构造器会创建一个默认大小(通常是16)的字符数组。在使用中,如果超出这个大小,就会重新分配内存,创建一个更大的数组,并将原先的数组复制过来,再
丢弃旧的数组。在大多数情况下,你可以在创建 StringBuffer的时候指定大小,这样就避免了在容量不够的时候自动增长,以提高性能。
如:StringBuffer buffer = new StringBuffer(1000);
19. 尽量早释放无用对象的引用
大部分时,方法局部引用变量所引用的对象 会随着方法结束而变成垃圾,因此,大部分时候程序无需将局部,引用变量显式设为null。
Public void
Object obj = new
上面这个就没必要了,随着方法test()的执行完成,程序中obj引用变量的作用域就结束了。但是如果是改成下面:
Public void
Object obj = new
执行耗时,耗内存操作;或调用耗时,耗内存的方法
这时候就有必要将obj赋值为null,可以尽早的释放对Object对象的引用。
20. 尽量避免使用二维数组
二维数据占用的内存空间比一维数组多得多,大概10倍以上。
21. 尽量避免使用split
除非是必须的,否则应该避免使用split,split由于支持正则表达式,所以效率比较低,如果是频繁的几十,几百万的调用将会耗费大量资
源,如果确实需
要频繁的调用split,可以考虑使用apache的StringUtils.split(string,char),频繁split的可以缓存结果。
22. ArrayList & LinkedList
个是线性表,一个是链表,一句话,随机查询尽量使用ArrayList,ArrayList优于LinkedList,LinkedList还要移动指
针,添加删除的操作LinkedList优于ArrayList,ArrayList还要移动数据,不过这是理论性分析,事实未必如此,重要的是理解好2
者得数据结构,对症下药。
23. 尽量使用System.arraycopy ()代替通过来循环复制数组
System.arraycopy() 要比通过循环来复制数组快的多
24. 尽量缓存经常使用的对象
尽可能将经常使用的对象进行缓存,可以使用数组,或HashMap的容器来进行缓存,但这种方式可能导致系统占用过多的缓存,性能下降,推荐可以使用一些第三方的开源工具,如EhCache,Oscache进行缓存,他们基本都实现了FIFO/FLU等缓存算法。
25. 尽量避免非常大的内存分配
有时候问题不是由当时的堆状态造成的,而是因为分配失败造成的。分配的内存块都必须是连续的,而随着堆越来越满,找到较大的连续块越来越困难。
26. 慎用异常
当创建一个异常时,需要收集一个栈跟踪(stack
track),这个栈跟踪用于描述异常是在何处创建的。构建这些栈跟踪时需要为运行时栈做一份快照,正是这一部分开销很大。当需要创建一个
Exception 时,JVM
不得不说:先别动,我想就您现在的样子存一份快照,所以暂时停止入栈和出栈操作。栈跟踪不只包含运行时栈中的一两个元素,而是包含这个栈中的每一个元素。
如 果您创建一个 Exception ,就得付出代价。好在捕获异常开销不大,因此可以使用 try-catch
将核心内容包起来。从技术上讲,您甚至可以随意地抛出异常,而不用花费很大的代价。招致性能损失的并不是 throw
操作——尽管在没有预先创建异常的情况下就抛出异常是有点不寻常。真正要花代价的是创建异常。幸运的是,好的编程习惯已教会我们,不应该不管三七二十一就
抛出异常。异常是为异常的情况而设计的,使用时也应该牢记这一原则。
15:40 人生e旅途_JAVA 阅读(20) 评论(0)
Student.java
cn.itcast.
java.util.HashS
java.util.S
javax.persistence.CascadeT
javax.persistence.C
javax.persistence.E
javax.persistence.GeneratedV
javax.persistence.Id;
javax.persistence.JoinC
javax.persistence.JoinT
javax.persistence.ManyToM
Set&Teacher& teachers = new
HashSet&Teacher&();
@Id @GeneratedValue
id作为实体标识符
并且采用数据库的id自增长方式生成主键值
Integer getId() {
setId(Integer id) {
@Column(length = 10, nullable = false
String getName() {
setName(String name) {
@ManyToMany(cascade = CascadeType.REFRESH)
@JoinTable(name = "student_teacher", inverseJoinColumns = @JoinColumn(name = "teacher_id"),
joinColumns = @JoinColumn(name = "student_id"))
Set&Teacher& getTeachers() {
假如不对关联表里的字段做任何设定,那么表里面的字段默认由JPA的实现产品来帮我们自动生成。
inverseJoinColumns:inverse中文是反转的意思,但是觉得太恶心了,在JPA里,可以理解为被维护端
inverseJoinColumns被维护端外键的定义
@JoinColumn:外键,中间表哪个外键名称跟teacher表的主键关联
joinColumns:关系维护端的定义
setTeachers(Set&Teacher& teachers) {
.teachers =
Teacher.java
cn.itcast.
java.util.HashS
java.util.S
javax.persistence.CascadeT
javax.persistence.C
javax.persistence.E
javax.persistence.GeneratedV
javax.persistence.Id;
javax.persistence.ManyToM
Set&Student& students=new
HashSet&Student&();
@Id @GeneratedValue
id作为实体标识符
并且采用数据库的id自增长方式生成主键值
Integer getId() {
setId(Integer id) {
@Column(length = 10, nullable = false
String getName() {
setName(String name) {
@ManyToMany(cascade=CascadeType.REFRESH,mappedBy="teachers")
Set&Student& getStudents() {
CascadeType.PERSIST:级联保存不要,学生没来之前,老师就已经在了
CascadeType.MERGE:级联更新不要,把学生的信息改了,没必要修改相应的老师的信息,压根就没这业务需求
CascadeType.REMOVE:级联删除更不要,如果双方都设了级联删除,加入删除学生,会删除相应的老师,被删除的老师又
跟学生发生千丝万缕的关系,又把一批学生删掉.....没完没了...最终的结果可能是数据里面所有的记录都被清掉
所以在多对多关系中,级联删除通常是用不上的
这里只需设置级联涮新CascadeType.PERSIST就可以了,事实上refresh方法也很少使用
mappedBy: 通过这个属性来说明老师是关系被维护端
fetch: 加载行为默认是延迟加载(懒加载),凭Many。 这里不需要设置
setStudents(Set&Student& students) {
.students =
ManyToManyTest.java
javax.persistence.EntityM
javax.persistence.EntityManagerF
javax.persistence.P
org.junit.BeforeC
org.junit.T
ManyToManyTest {
@BeforeClass
setUpBeforeClass() throws
Exception {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast");
factory.close();
双向多对多关系是一种对等关系,既然是对等关系,也就是说我们可以人为决定谁是关系维护端,谁是关系被维护端,这里选学生做关系维护端。那么以后就只能通过学生来维护老师跟学生的关系。
老师A id是1
学生B id是1
那通过什么东西把他们的关系确立起来呢?采用什么来存放他们的关联关系呢?是中间表(关联表)。
学生A和老师B建立起关系,首先要找到关系维护端,是学生,就要通过学生这个关系维护端,学生A.getTeachers().add(Teacher);这样就能把老师跟学生的关系确立起来了。确立起来后,反应在中间表里面就是insert into...一条语句
如果学生A要把老师B开掉,那就要解除关系,也是通过关系维护端学生A,反映在面向对象的操作就是 学生A.getTeachers().remove(Teacher);执行这句代码的时候,在底层
它会对中间表做delete from...语句的操作。
我们都是通过关系维护端来进行操作的,以后在双向关系中一定要找准谁是关系维护端,谁是关系被维护端
@JoinTable的注解有:看图,
15:30 人生e旅途_JAVA 阅读(61) 评论(0)
目录结构,看图:
Student.java
cn.itcast.
java.util.HashS
java.util.S
javax.persistence.CascadeT
javax.persistence.C
javax.persistence.E
javax.persistence.GeneratedV
javax.persistence.Id;
javax.persistence.JoinC
javax.persistence.JoinT
javax.persistence.ManyToM
Set&Teacher& teachers = new
HashSet&Teacher&();
Student() {
Student(String name) {
@GeneratedValue
id作为实体标识符 并且采用数据库的id自增长方式生成主键值
Integer getId() {
setId(Integer id) {
@Column(length = 10, nullable = false
String getName() {
setName(String name) {
@ManyToMany(cascade = CascadeType.REFRESH)
@JoinTable(name = "student_teacher", inverseJoinColumns = @JoinColumn(name = "teacher_id"), joinColumns = @JoinColumn(name = "student_id"))
Set&Teacher& getTeachers() {
假如不对关联表里的字段做任何设定,那么表里面的字段默认由JPA的实现产品来帮我们自动生成。
inverseJoinColumns:inverse中文是反转的意思,但是觉得太恶心了,在JPA里,可以理解为被维护端
inverseJoinColumns被维护端外键的定义 @JoinColumn:外键,中间表哪个外键名称跟teacher表的主键关联
joinColumns:关系维护端的定义
setTeachers(Set&Teacher& teachers) {
.teachers =
addTeacher(Teacher teacher) {
.teachers.add(teacher);
removeTeacher(Teacher teacher) {
.teachers.contains(teacher)){
凭什么判断teacher在集合teachers中呢?是根据teacher的id
这就有必要重写Teacher.java的hasCode和equals方法,通过这两个方法来判断对象是否相等
.teachers.remove(teacher);
Teacher.java
cn.itcast.
java.util.HashS
java.util.S
javax.persistence.CascadeT
javax.persistence.C
javax.persistence.E
javax.persistence.GeneratedV
javax.persistence.Id;
javax.persistence.ManyToM
Set&Student& students = new
HashSet&Student&();
Teacher() {
Teacher(String name) {
@GeneratedValue
id作为实体标识符 并且采用数据库的id自增长方式生成主键值
Integer getId() {
setId(Integer id) {
@Column(length = 10, nullable = false
String getName() {
setName(String name) {
@ManyToMany(cascade = CascadeType.REFRESH, mappedBy = "teachers")
Set&Student& getStudents() {
cascade: CascadeType.PERSIST:级联保存不要,学生没来之前,老师就已经在了
CascadeType.MERGE:级联更新不要,把学生的信息改了,没必要修改相应的老师的信息,压根就没这业务需求
CascadeType.REMOVE:级联删除更不要,如果双方都设了级联删除,加入删除学生,会删除相应的老师,被删除的老师又
跟学生发生千丝万缕的关系,又把一批学生删掉.....没完没了...最终的结果可能是数据里面所有的记录都被清掉
所以在多对多关系中,级联删除通常是用不上的 这里只需设置级联涮新CascadeType.PERSIST就可以了,事实上refresh方法也很少使用
mappedBy: 通过这个属性来说明老师是关系被维护端 fetch: 加载行为默认是延迟加载(懒加载),凭Many。 这里不需要设置
setStudents(Set&Student& students) {
.students =
hashCode() {
prime = 31;
result = 1;
result = prime * result + ((id == null
) ? 0 : id.hashCode());
判断的依据是,如果id不为null的话,就返回id的哈希码
equals(Object obj) {
(obj == null
(getClass() != obj.getClass())
Teacher other = (Teacher)
(id == null
(other.id != null
(!id.equals(other.id))
ManyToManyTest.java
javax.persistence.EntityM
javax.persistence.EntityManagerF
javax.persistence.P
org.junit.BeforeC
org.junit.T
cn.itcast.bean.S
cn.itcast.bean.T
ManyToManyTest {
@BeforeClass
setUpBeforeClass() throws
Exception {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
em.persist(new
Student("小张同学"));
em.persist(new
Teacher("李勇老师"));
em.getTransaction().commit();
em.close();
factory.close();
* 建立学生跟老师的关系
buildTS() {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Student student = em.find(Student.class
首先要得到学生,因为学生是关系维护端,通过关系维护端来建立关系
student.addTeacher(em.getReference(Teacher.class
这方法在业务意义上,就代表建立跟老师的关系
所谓建立跟老师的关系,无非就是把老师加进集合里面去。
建立关系,体现在JDBC上面,就是添加一条记录进中间表
em.getTransaction().commit();
em.close();
factory.close();
* 解除学生跟老师的关系
deleteTS() {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Student student = em.find(Student.class
首先要得到学生,因为学生是关系维护端,通过关系维护端来建立关系
student.removeTeacher(em.getReference(Teacher.class
这方法在业务意义上,就代表解除跟老师的关系
所谓解除跟老师的关系,无非就是把老师从集合里面删去
解除关系,体现在JDBC上面,就是在中间表删除一条记录
em.getTransaction().commit();
em.close();
factory.close();
* 删除老师,老师已经跟学生建立起了关系(错误写法)
deleteTeacher1() {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
em.remove(em.getReference(Teacher.class
并不需要发生数据装载行为,只需要一个托管状态的实体,所以用getReference可以提供性能
em.getTransaction().commit();
em.close();
factory.close();
该方法会出错,因为中间表中已经有记录了,会抛出以下错误
Caused by: java.sql.BatchUpdateException:
Cannot delete or update a parent row: a foreign key constraint fails
(`itcast/student_teacher`, CONSTRAINT `FKD4E389DE1D49449D` FOREIGN KEY (`teacher_id`)
REFERENCES `teacher` (`id`))
关系被维护端没有权力更新外键,所以不会删除中间表的记录
* 删除老师,老师已经跟学生建立起了关系(正确写法)
deleteTeacher2() {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Student student = em.find(Student.class
Teacher teacher = em.getReference(Teacher.class
并不需要发生数据装载行为,只需要一个托管状态的实体,所以用getReference可以提供性能
student.removeTeacher(teacher);
em.remove(teacher);
em.getTransaction().commit();
em.close();
factory.close();
* 删除学生,老师已经跟学生建立起了关系
deleteStudent() {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Student student = em.getReference(Student.class
em.remove(student);//
这样是可以删除学生的,尽管目前是有关系,中间表有关联记录,有外键约束
但是我们现在要删除的是关系维护端,关系维护端是有权力对外键进行增删改查操作的
删除的时候,Hibernate会判断student是关系维护端,然后去中间表把关联记录先删除掉
再删掉student对象
em.getTransaction().commit();
em.close();
factory.close();
15:23 人生e旅途_JAVA 阅读(108) 评论(0)
1. Hibernate优化-引言
Hibernate是最流行的对象关系映射(ORM)引擎之一,它提供了数据持久化和查询服务。
在你的项目中引入Hibernate并让它跑起来是很容易的。但是,要让它跑得好却是需要很多时间和经验的。
通过我们的使用Hibernate 3.3.1和Oracle 9i的能源项目中的一些例子,本文涵盖了很多Hibernate调优技术。其中还提供了一些掌握Hibernate调优技术所必需的数据库知识。
我们假设读者对Hibernate有一个基本的了解。如果一个调优方法在Hibernate 参考文档(下文简称HRD)或其他调优文章中有详细描述,我们仅提供一个对该文档的引用并从不同角度对其做简单说明。我们关注于那些行之有效,但又缺乏文档的调优方法。
2. Hibernate优化-Hibernate性能调优
调优是一个迭代的、持续进行的过程,涉及软件开发生命周期(SDLC)的所有阶段。在一个典型的使用Hibernate进行持久化的Java EE应用程序中,调优会涉及以下几个方面:
业务规则调优
Hibernate调优
Java GC调优
应用程序容器调优
底层系统调优,包括数据库和OS。
没有一套精心设计的方案就去进行以上调优是非常耗时的,而且很可能收效甚微。好的调优方法的重要部分是为调优内容划分优先级。可以用Pareto定律(又称“80/20法则”)来解释这一点,即通常80%的应用程序性能改善源自头20%的性能问题[5]。
相比基于磁盘和网络的访问,基于内存和CPU的访问能提供更低的延迟和更高的吞吐量。这种基于IO的Hibernate调优与底层系统IO部分的调优应该优先于基于CPU和内存的底层系统GC、CPU和内存部分的调优。
我们调优了一个选择电流的HQL查询,把它从30秒降到了1秒以内。如果我们在垃圾回收方面下功夫,可能收效甚微——也许只有几毫秒或者最多几秒,相比HQL的改进,GC方面的改善可以忽略不计。
好的调优方法的另一个重要部分是决定何时优化[4]。
积极优化的提倡者主张开始时就进行调优,例如在业务规则和设计阶段,在整个SDLC都持续进行优化,因为他们认为后期改变业务规则和重新设计代价太大。
另一派人提倡在SDLC末期进行调优,因为他们抱怨前期调优经常会让设计和编码变得复杂。他们经常引用Donald Knuth的名言“过早优化是万恶之源” [6]。
为了平衡调优和编码需要一些权衡。根据笔者的经验,适当的前期调优能带来更明智的设计和细致的编码。很多项目就失败在应用程序调优上,因为上面提到的“过早优化”阶段在被引用时脱离了上下文,而且相应的调优不是被推迟得太晚就是投入资源过少。
但是,要做很多前期调优也不太可能,因为没有经过剖析,你并不能确定应用程序的瓶颈究竟在何处,应用程序一般都是这样演化的。
对我们的多线程企业级应用程序的剖析也表现出大多数应用程序平均只有20-50%的CPU使用率。剩余的CPU开销只是在等待数据库和网络相关的IO。
基于上述分析,我们得出这样一个结论,结合业务规则和设计的Hibernate调优在Pareto定律中20%的那个部分,相应的它们的优先级更高。
一种比较实际的做法是:
识别出主要瓶颈,可以预见其中多数是Hibernate、业务规则和设计方面的(其数量视你的调优目标而定;但三到五个是不错的开端)。
修改应用程序以便消除这些瓶颈。
测试应用程序,然后重复步骤1,直到达到你的调优目标为止。
你能在Jack Shirazi的《Java Perfo
e Tuning》 [7]一书中找到更多关于性能调优阶段的常见建议。
下面的章节中,我们会按照调优的大致顺序(列在前面的通常影响最大)去解释一些特定的调优技术。
3. Hibernate优化-监控和剖析
没有对Hibernate应用程序的有效监控和剖析,你无法得知性能瓶颈以及何处需要调优。
3.1.1 监控SQL生成
尽管使用Hibernate的主要目的是将你从直接使用SQL的痛苦中解救出来,为了对应用程序进行调优,你必须知道Hibernate生成了哪些 SQL。JoeSplosky在他的《The Law of Leaky Abs
actions》一文中详细描述了这个问题。
你可以在log4j中将org.hibernate.SQL包的日志级别设为DEBUG,这样便能看到生成的所有SQL。你还可以将其他包的日志级别设为DEBUG,甚至TRACE来定位一些性能问题。
3.1.2 查看Hibernate统计
如果开启hibernate.generate.
istics,Hibernate
会导出实体、集合、会话、二级缓存、查询和会话工厂的统计信息,这对通过SessionFactory.getStatistics()进行的调优很有帮
助。为了简单起见,Hibernate还可以使用MBean“org.hibernate.jmx.StatisticsService”通过JMX来导
出统计信息。你可以在这个网站找到配置范例 。
3.1.3 剖析
一个好的剖析工具不仅有利于Hibernate调优,还能为应用程序的其他部分带来好处。然而,大多数商业工具(例如JProbe [10])都很昂贵。幸运的是Sun/Oracle的JDK1.6自带了一个名为“Java Vi
alVM” [11]的调试接口。虽然比起那些商业竞争对手,它还相当基础,但它提供了很多调试和调优信息。
4. Hibernate优化-调优技术
4.1 业务规则与设计调优
尽管业务规则和设计调优并不属于Hibernate调优的范畴,但此处的决定对后面Hibernate的调优有很大影响。因此我们特意指出一些与Hibernate调优有关的点。
在业务需求收集与调优过程中,你需要知道:
数据获取特性包括引用数据(reference data)、只读数据、读分组(read group)、读取大小、搜索条件以及数据分组和聚合。
数据修改特性包括数据变更、变更组、变更大小、无效修改补偿、数据库(所有变更都在一个数据库中或在多个数据库中)、变更频率和并发性,以及变更响应和吞吐量要求。
数据关系,例如关联(association)、泛化(generalization)、实现(realization)和依赖(dependency)。
于业务需求,你会得到一个最优设计,其中决定了应用程序类型(是OLTP还是数据仓库,亦或者与其中某一种比较接近)和分层结构(将持久层和服务
层分离还是合并),创建领域对象(通常是POJO),决定数据聚合的地方(在数据库中进行聚合能利用强大的数据库功能,节省网络带宽;但是除了像
COUNT、SUM、AVG、MIN和MAX这样的标准聚合,其他的聚合通常不具有移植性。在应用服务器上进行聚合允许你应用更复杂的业务逻辑;但你需要
先在应用程序中载入详细的数据)。
分析员需要查看一个取自大数据表的电流ISO(Independent
Operator)聚合列表。最开始他们想要显示大多数字段,尽管数据库能在1分钟内做出响应,应用程序也要花30分钟将1百万行数据加载到前端UI。经
过重新分析,分析员保留了14个字段。因为去掉了很多可选的高聚合度字段,从剩下的字段中进行聚合分组返回的数据要少很多,而且大多数情况下的数据加载时
间也缩小到了可接受的范围内。
过24个“非标准”(shaped,表示每小时都可以有自己的电量和价格;如果所有24小时的电量和价格相同,我们称之为“标准”)小时会修改小时电流交易,其中包括2个属性:每小时电量和价格。起初我们使用Hibernate的select-before-up
特性,就是更新24行数据需要24次选择。因为我们只需要2个属性,而且如果不修改电量或价格的话也没有业务规则禁止无效修改,我们就关闭了select-before-update特性,避免了24次选择。
4.2继承映射调优
尽管继承映射是领域对象的一部分,出于它的重要性我们将它单独出来。HRD [1]中的第9章“继承映射”已经说得很清楚了,所以我们将关注SQL生成和针对每个策略的调优建议。
以下是HRD中范例的类图:
hibernate性能优化
4.2.1 每个类层次一张表
只需要一张表,一条多态查询生成的SQL大概是这样的:
id, payment_type, amount, currency, rtn, credit_card_type from
针对具体子类(例如CashPayment)的查询生成的SQL是这样的:
id, amount, currency from
payment where
payment_type=
这样做的优点包括只有一张表、查询简单以及容易与其他表进行关联。第二个查询中不需要包含其他子类中的属性。所有这些特性让该策略的性能调优要比其他策略容易得多。这种方法通常比较适合数据仓库系统,因为所有数据都在一张表里,不需要做表连接。
要的缺点整个类层次中的所有属性都挤在一张大表里,如果有很多子类特有的属性,数据库中就会有太多字段的取值为null,这为当前基于行的数据库
(使用基于列的DBMS的数据仓库处理这个会更好些)的SQL调优增加了难度。除非进行分区,否则唯一的数据表会成为热点,OLTP系统通常在这方面都不
4.2.2每个子类一张表
需要4张表,多态查询生成的SQL如下:
id, payment_type, amount, currency, rtn, credit_card type,
c.payment_id is
ck.payment_id is
cc.payment_id is
payment p left
cash_payment c on
c.payment_id left
cheque_payment ck on
ck.payment_id left
credit_payment cc on
cc.payment_
针对具体子类(例如CashPayment)的查询生成的SQL是这样的:
id, payment_type, amount, currency
payment p left
cash_payment c on
c.payment_
针对具体子类(例如CashPayment)的查询生成的SQL是这样的:
id, payment_type, amount, currency
payment p left
cash_payment c on
c.payment_
优点包括数据表比较紧凑(没有不需要的可空字段),数据跨三个子类的表进行分区,容易使用超类的表与其他表进行关联。紧凑的数据表可以针对
基于行的 数据库做存储块优化,让SQL执行得更好。数据分区增加了数据修改的并发性(除了超类,没有热点),OLTP系统通常会更好些。
同样的,第二个查询不需要包含其他子类的属性。
缺点是在所有策略中它使用的表和表连接最多,SQL语句稍显复杂(看看Hibernate动态鉴别器的长CASE子句)。相比单张表,数据库要花更多时间调优数据表连接,数据仓库在使用该策略时通常不太理想。
因为不能跨超类和子类的字段来建立复合索引,如果需要按这些列进行查询,性能会受影响。任何子类数据的修改都涉及两张表:超类的表和子类的表。
4.2.3每个具体类一张表
涉及三张或更多的表,多态查询生成的SQL是这样的:
p.id, p.amount, p.currency, p.rtn, p. credit_card_type, p.clazz
id, amount, currency, null
credit_card type,
clazz from
cash_payment union
id, amount, null
currency, rtn,null
credit_card type,
clazz from
cheque_payment union
id, amount, null
currency, null
rtn,credit_card type,
clazz from
credit_payment)
针对具体子类(例如CashPayment)的查询生成的SQL是这样的:
id, payment_type, amount, currency from
优点和上面的“每个子类一张表”策略相似。因为超类通常是抽象的,所以具体的三张表是必须的[开头处说的3张或更多的表是必须的],任何子类的数据修改只涉及一张表,运行起来更快。
缺点是SQL(from子句和union all子查询)太复杂。但是大多数数据库对此类SQL的调优都很好。
如果一个类想和Payment超类关联,数据库无法使用引用完整性(referential integrity)来实现它;必须使用触发器来实现它。这对数据库性能有些影响。
4.2.4使用隐式多态实现每个具体类一张表
只需要三张表。对于Payment的多态查询生成三条独立的SQL语句,每个对应一个子类。Hibernate引擎通过Java反射找出Payment的所有三个子类。
具体子类的查询只生成该子类的SQL。这些SQL语句都很简单,这里就不再阐述了。
它的优点和上节类似:紧凑数据表、跨三个具体子类的数据分区以及对子类任意数据的修改都只涉及一张表。
缺点是用三条独立的SQL语句代替了一条联合SQL,这会带来更多网络IO。Java反射也需要时间。假设如果你有一大堆领域对象,你从最上层的Object类进行隐式选择查询,那该需要多长时间啊!
根据你的映射策略制定合理的选择查询并非易事;这需要你仔细调优业务需求,基于特定的数据场景制定合理的设计决策。
以下是一些建议:
设计细粒度的类层次和粗粒度的数据表。细粒度的数据表意味着更多数据表连接,相应的查询也会更复杂。
如非必要,不要使用多态查询。正如上文所示,对具体类的查询只选择需要的数据,没有不必要的表连接和联合。
“每个类层次一张表”对有高并发、简单查询并且没有共享列的OLTP系统来说是个不错的选择。如果你想用数据库的引用完整性来做关联,那它也是个合适的选择。
“每个具体类一张表”对有高并发、复杂查询并且没有共享列的OLTP系统来说是个不错的选择。当然你不得不牺牲超类与其他类之间的关联。
采用混合策略,例如“每个类层次一张表”中嵌入“每个子类一张表”,这样可以利用不同策略的优势。随着你项目的进化,如果你要反复重新映射,那你可能也会采用该策略。
“使用隐式多态实现每个具体类一张表”这种做法并不推荐,因为其配置过于繁缛、使用“any”元素的复杂关联语法和隐式查询的潜在危险性。
下面是一个交易描述应用程序的部分领域类图:
hibernate性能优化
开始时,项目只有GasDeal和少数用户,它使用“每个类层次一张表”。
OilDeal和ElectricityDeal是后期产生更多业务需求后加入的。没有改变映射策略。但是ElectricityDeal有太多自己的属性,因此有很多电相关的可空字段加入了Deal表。因为用户量也在增长,数据修改变得越来越慢。
重新设计时我们使用了两张单独的表,分别针对气/油和电相关的属性。新的映射混合了“每个类层次一张表”和“每个子类一张表”。我们还重新设计了查询,以便允许针对具体交易子类进行选择,消除不必要的列和表连接。
4.3 领域对象调优
基于4.1节中对业务规则和设计的调优,你得到了一个用POJO来表示的领域对象的类图。我们建议:
4.3.1 POJO调优
从读写数据中将类似引用这样的只读数据和以读为主的数据分离出来。
只读数据的二级缓存是最有效的,其次是以读为主的数据的非严格读写。将只读POJO标识为不可更改的(immutable)也是一个调优点。如果一个服务层方法只处理只读数据,可以将它的事务标为只读,这是优化Hibernate和底层JDBC驱动的一个方法。
细粒度的POJO和粗粒度的数据表。
基于数据的修改并发量和频率等内容来分解大的POJO。尽管你可以定义一个粒度非常细的对象模型,但粒度过细的表会导致大量表连接,这对数据仓库来说是不能接受的。
优先使用非final的类。
Hibernate只会针对非final的类使用CGLIB代理来实现延时关联获取。如果被关联的类是final的,Hibernate会一次加载所有内容,这对性能会有影响。
使用业务键为分离(detached)实例实现equa
()和hashCode()方法。
在多层系统中,经常可以在分离对象上使用乐观锁来提升系统并发性,达到更高的性能。
定义一个版本或时间戳属性。
乐观锁需要这个字段来实现长对话(应用程序事务)[译注:session译为会话,conversion译为对话,以示区别]。
优先使用组合POJO。
你的前端UI经常需要来自多个不同POJO的数据。你应该向UI传递一个组合POJO而不是独立的POJO以获得更好的网络性能。
有两种方式在服务层构建组合POJO。一种是在开始时加3.2载所有需要的独立POJO,随后抽取需要的属性放入组合POJO;另一种是使用HQL投影,直接从数据库中选择需要的属性。
如果其他地方也要查找这些独立POJO,可以把它们放进二级缓存以便共享,这时第一种方式更好;其他情况下第二种方式更好。
4.3.2 POJO之间关联的调优
如果可以用one-to-one、one-to-many或many-to-one的关联,就不要使用many-to-many。
many-to-many关联需要额外的映射表。
尽管你的Java代码只需要处理两端的POJO,但查询时,数据库需要额外地关联映射表,修改时需要额外的删除和插入。
单向关联优先于双向关联。
由于many-to-many的特性,在双向关联的一端加载对象会触发另一端的加载,这会进一步触发原始端加载更多的数据,等等。
one-to-many和many-to-one的双向关联也是类似的,当你从多端(子实体)定位到一端(父实体)。
这样的来回加载很耗时,而且可能也不是你所期望的。
不要为了关联而定义关联;只在你需要一起加载它们时才这么做,这应该由你的业务规则和设计来决定(见范例5)。
另外,你要么不定义任何关联,要么在子POJO中定义一个值类型的属性来表示父POJO的ID(另一个方向也是类似的)。
如果集合排序逻辑能由底层数据库实现,就使用“order-by”属性来代替“
”,因为通常数据库在这方面做得比你好。
集合可以是值类型的(元素或组合元素),也可以是实体引用类型的(one-to-many或many-to-many关联)。对引用类型集合的调优主要是调优获取策略。对于值类型集合的调优,HRD [1]中的20.5节“理解集合性能”已经做了很好的阐述。
获取策略调优。请见4.7节的范例5。
们有一个名为ElectricityDeals的核心POJO用于描述电的交易。从业务角度来看,它有很多many-to-one关联,例如和
Portfolio、Strategy和Trader等的关联。因为引用数据十分稳定,它们被缓存在前端,能基于其ID属性快速定位到它们。
为了有好的加载性能,ElectricityDeal只映射元数据,即那些引用POJO的值类型ID属性,因为在需要时,可以在前端通过portfolioKey从缓存中快速查找Portfolio:
property name=
"portfolioKey" column
"PORTFOLIO_ID" type=
这种隐式关联避免了数据库表连接和额外的字段选择,降低了数据传输的大小。
4.4 连接池调优
由于创建物理数据库连接非常耗时,你应该始终使用连接池,而且应该始终使用生产级连接池而非Hibernate内置的基本连接池算法。
常会为Hibernate提供一个有连接池功能的数据源。Apache
DBCP的BasicDataSource[13]是一个流行的开源生产级数据源。大多数数据库厂商也实现了自己的兼容JDBC
3.0的连接池。举例来说,你也可以使用Oracle ReaAppli
ion Cluster [15]提供的JDBC连接池[14]以获得连接的
和失败转移。
不用多说,你在网上能找到很多关于连接池调优的技术,因此我们只讨论那些大多数连接池所共有的通用调优参数:
最小池大小:连接池中可保持的最小连接数。
最大池大小:连接池中可以分配的最大连接数。
如果应用程序有高并发,而最大池大小又太小,连接池就会经常等待。相反,如果最小池大小太大,又会分配不需要的连接。
最大空闲时间:连接池中的连接被物理关闭前能保持空闲的最大时间。
最大等待时间:连接池等待连接返回的最大时间。该参数可以预防失控事务(runaway transaction)。
验证查询:在将连接返回给调用方前用于验证连接的SQL查询。这是因为一些数据库被配置为会杀掉长时间空闲的连接,网络或数据库相关的异常也可能会杀死连接。为了减少此类开销,连接池在空闲时会运行该验证。
4.5事务和并发的调优
短数据库事务对任何高性能、高可扩展性的应用程序来说都是必不可少的。你使用表示对话请求的会话来处理单个工作单元,以此来处理事务。
考虑到工作单元的范围和事务边界的划分,有3中模式:
每次操作一个会话。每次数据库调用需要一个新会话和事务。因为真实的业务事务通常包含多个此类操作和大量小事务,这一般会引起更多数据库活动(主要是数据库每次提交需要将变更刷新到磁盘上),影响应用程序性能。这是一种反模式,不该使用它。
使用分离对象,每次请求一个会话。每次客户端请求有一个新会话和一个事务,使用Hibernate的“当前会话”特性将两者关联起来。
在一个多层系统中,用户通常会发起长对话(或应用程序事务)。大多数时间我们使用Hibernate的自动版本和分离对象来实现乐观并发控制和高性能。
带扩展(或长)会话的每次对话一会话。在一个也许会跨多个事务的长对话中保持会话开启。尽管这能把你从重新关联中解脱出来,但会话可能会内存溢出,在高并发系统中可能会有旧数据。
你还应该注意以下几点。
果不需要JTA就用本地事务,因为JTA需要更多资源,比本地事务更慢。就算你有多个数据源,除非有跨多个数据库的事务,否则也不需要
JTA。在最后的一个场景下,可以考虑在每个数据源中使用本地事务,使用一种类似“Last Resource Commit
Optimization”[16]的技术(见下面的范例6)。
如果不涉及数据变更,将事务标记为只读的,就像4.3.1节提到的那样。
总是设置默认事务超时。保证在没有响应返回给用户时,没有行为不当的事务会完全占有资源。这对本地事务也同样有效。
如果Hibernate不是独占数据库用户,乐观锁会失效,除非创建数据库触发器为其他应用程序对相同数据的变更增加版本字段值。
我们的应用程序有多个在大多数情况下只和数据库“A”打交道的服务层方法;它们偶尔也会从数据库“B”中获取只读数据。因为数据库“B”只提供只读数据,我们对这些方法在这两个数据库上仍然使用本地事务。
服务层上有一个方法设计在两个数据库上执行数据变更。以下是伪代码:
Make sure a local transaction
@Transactional
(readOnly=
false, propagation=
Propagation.REQUIRED)
void saveIsoBids() {
it participates in
the above annotated local transaction
insertBidsInDatabaseA();
it runs in
its own local transaction
insertBidRequestsInDatabaseB(); //
must be the last operation
因为insertBidRequestsInDatabaseB()是saveIsoBids ()中的最后一个方法,所以只有下面的场景会造成数据不一致:
在saveIsoBids()执行返回时,数据库“A”的本地事务提交失败。
但是,就算saveIsoBids()使用JTA,在两阶段提交(2PC)的第二个提交阶段失败的时候,你还是会碰到数据不一致。因此如果你能处理好上述的数据不一致性,而且不想为了一个或少数几个方法引入JTA的复杂性,你应该使用本地事务。
Jiao是SunGard Consulting Services的技术主管。过去10年中他一直是专业软件开发者,他的专长包括Java
SE、Java EE、Oracle和应用程序调优。他最近的关注点是高性能计算,包括内存数据网格、并行计算和网格计算。
Stewart Clark是SunGard Consulting Services的负责人。过去15年中他一直是专业软件开发者和项目经理,他的专长包括Java核心编程、Oracle和能源交易。
摘自:infoq
英文地址:q.com/articles/hibernate_tuning
/architecture/21258.html
浏览: 20428 次
来自: 武汉
你好,为什么每次注释掉serviceRegistryDao b ...
看似不错,得多看源码

我要回帖

更多关于 java执行sql 的文章

 

随机推荐