性能优化攻略

最后更新于:2022-04-01 07:00:02

# 性能优化攻略 为什么程序总是那么慢?它现在到底在干什么?时间都花到哪去了?也许,你经常会抱怨这些问题。如果是这样,那说明你的程序出了性能问题。和功能性问题相比,性能问题在有些情况下,可能并不算太大的问题,将就将就,也就过去了。但是,严重的性能问题会导致程序瘫痪、假死,甚至崩溃。 ### 看懂程序的性能 **a.执行速度**   程序的反应是否迅速,响应时间是否足够短。 **b.内存分配**   内存分配是否合理,是否过多地消耗内存或者存在泄露。 **c.启动时间**    程序从运行到可以正常处理业务需要花费多少时间。 **d.负载承受能力**   当系统压力上升时,系统的执行速度、响应时间的上升曲线是否平缓。 ### 性能的参考指标 **a.执行时间**    一段代码从开始运行到运行结束,所使用的时间。 **b.CPU时间**    函数或者线程占用CPU的时间。 **c.内存分配**    程序在运行时占用的内存空间。 **d.磁盘吞吐量**    描述I/O的使用情况。 **e.网络吞吐量**    描述网络的使用情况。 **f.响应时间**    系统对某用户行为或者事件做出响应的时间。响应时间越短,性能越好。 ### 木桶原理与性能瓶颈 木桶原理又称“短板理论”,其核心思想是:一只木桶盛水的多少,并不取决于桶壁上最高的那块木块,而是取决于桶壁上最短的那块。 根据木桶原理,系统的最终性能瓶颈取决于系统中性能表现最差的组件。因此,为了提升系统整体性能,必须对系统中表现最差的组件进行优化,而不是对系统中表现良好的组件进行优化。 根据应用的特点不同,任何计算机资源都有可能成为系统瓶颈。其中,最有可能成为系统瓶颈的计算资源如下: **a.磁盘I/O** 由于磁盘I/O读写的速度要比内存慢很多,程序在运行过程中,如果需要等待磁盘I/O完成,那么低效的I/O操作会拖累整个系统。 **b.网络操作** 对网络数据进行读写的情况与磁盘I/O类似。由于网络环境的不确定性,尤其是互联网上数据的读写,网络操作的速度可能比本地磁盘I/O更慢。因此,如不加特殊处理,也极可能成为系统瓶颈。 **c.CPU** 对计算机资源要求较高的应用,由于其长时间、不间断地大量占用CPU资源,那么对CPU的争夺将导致性能问题。如科学计算、3D渲染等对CPU需求旺盛的应用。 **d.异常** 对Java应用来说,异常的捕获和处理是非常耗费资源的,如果程序高频率地进行异常处理,则整理性能便会有明显下降。 **e.数据库** 大部分应用程序都离不开数据库,而海量数据的读写操作操作可能是相当费时的。而应用程序可能需要等待数据库操作完成或者返回请求的结果集,那么缓存的同步操作将成为系统瓶颈。 **f.锁竞争** 对高并发程序来说,如果存在激烈的锁竞争,无疑是对性能极大的打击。锁竞争将会明显增加线程上下文切换的开销。而且,这些开销都是与应用需求无关的系统开销,白白占用宝贵的CPU资源,去不带来任何好处。 **g.内存** 一般来说,只要应用程序设计合理,内存在读写速度上不太可能成为系统瓶颈。除非应用程序进行了高频率的内存交换和扫描,但这些情况比较少见。使内存制约系统性能瓶颈的情况是内存大小不足。与磁盘相比,内存的大小似乎小的可怜,这意味着应用软件只能尽可能将常用的核心数据读入内存,这在一定程序上降低了系统性能。 ### Amadahl定律 Amadahl定律是计算机科学中非常重要的定律,它定义了串行系统并行化后加速比的计算公式和理论上限。 加速比定义:加速比=优化前系统耗时/优化后系统耗时 加速比越高,表明优化效果越明显。 Amadahl定律给出了加速比与系统并行度和处理器数量的关系。 设加速比为Speedup,系统内必须串行化的程序比重为F,CPU数量为N,则有: Speedup<= 1/ (F+ (1-F) /N) 根据这个公式,如果CPU数量趋于无穷,那么加速比与系统的串行化成反比。如果系统中必须有50%的代码穿行执行,那么系统的最大加速比是2。 ### 性能调优的层次 #### **a.设计调优** 设计调优处于所有调优手段的上层,它往往需要在软件开发之前进行。 设计优化的一大显著特点是,它可以规避某一个组件的性能问题,而非改良该组件的实现, 如果说,代码优化和JVM优化是对系统微观层面上“量”的优化,那么设计优化就是对系统在宏观层面上“质”的优化。 一个良好的系统设计可以规避很多潜在的性能问题。因此,尽可能多花时间在系统设计上,是创建高性能程序的关键。 #### **b.代码调优** 代码调优涉及诸多编码技巧,需要开发人员熟悉相关语言的API,并在合适的场景中正确使用相关API或类库。同时,对算法、数据结构的灵活运用。 虽然代码优化是从微观上对性能进行调整,但是一个“好”的实现和一个“坏”的实现对系统的影响还是很大的。比如同样作为List的实现,LinkedList和ArrayList在随机访问上的性能却可以相差几个数量级。 作为微观层面的优化,却是对系统性能产生直接影响的优化方法。 #### **c.JVM调优** 作为Java软件的运行平台,JVM的各项参数将会直接影响Java程序的性能。比如JVM堆的大小、垃圾回收策略。 #### **d.数据库调优** 可以分为3个部分: 在应用层对SQL语句进行优化; 对数据库进行优化,比如建立索引; 对数据库软件进行优化。 #### **e.操作系统调优** Windows,Linux。 最大文件句柄数、虚拟内存大小等系统参数对系统性能有影响。 ### 基本调优策略和手段 #### 优化的一般步骤 明确目标,通过性能监控和统计工具,观察和确认当前系统是否已经达到相关目标,若已经达到,则没有必要再优化;否则,查找当前的系统瓶颈,通过定位相关代码查找代码性能问题,其次考虑JVM层、数据库层或者操作系统。 考虑修改原有设计或者升级硬件也是一种方式。 ####系统优化注意事项 性能优化可能对软件功能、正确性和可维护性造成负面影响。 比如改进算法使得代码出现了新的Bug,使得代码更难懂。
';

(七):性能监测工具JavaMelody

最后更新于:2022-04-01 07:00:00

# (七):性能监测工具JavaMelody ### 简介 JavaMelody 能够监测Java或Java EE应用程序服务器,并以图表的方式显示: Java内存和Java CPU使用情况,用户Session数量,JDBC连接数, 和http请求、sql请求、jsp页面与业务接口方法(EJB3、Spring、 Guice)的执行数量,平均执行时间,错误百分比等。 图表可以按天,周,月,年或自定义时间段查看。 ### 使用步骤 1.下载JavaMelody。 下载地址:[http://code.google.com/p/javamelody/downloads/list](http://code.google.com/p/javamelody/downloads/list) javamelody-1.16.0.jar,jrobin-1.5.9.1.jar (版本号可能会不同) 2.将Jar包拷贝到项目的WEB-INF的lib目录。 3.在web.xml下增加配置。 ~~~ <filter> <filter-name>monitoring</filter-name> <filter-class>net.bull.javamelody.MonitoringFilter</filter-class> </filter> <filter-mapping> <filter-name>monitoring</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>net.bull.javamelody.SessionListener</listener-class> </listener> ~~~ 4.重新启动项目,访问[http://localhost:8080/monitoring/](http://localhost:8080/monitoring/) 即可看到监控图表。 [![](image/569357debac20.gif)](http://fansunion.cn/wp-content/uploads/2013/11/Jamelody%E5%9B%BE%E8%A1%A8.gif) ### 安全问题 按照上述的配置,不需要登录就可以访问JavaMelody的页面。 实际项目中,常见需求是“登录用户,才可以查看”。 定制方法如下: web.xml的Filter配置 packageName.ProjectMonitoringFilter 自定义的Filter ~~~ public class ProjectMonitoringFilter extends net.bull.javamelody.MonitoringFilter{       @Override    public void doFilter(ServletRequest req, ServletResponse response,      FilterChain chain) throws IOException, ServletException {     HttpServletRequest request = (HttpServletRequest) req;     HttpSession session = request.getSession();     User user = (User) session.getAttribute(Constant.LOGIN_USER);     String uri = ((HttpServletRequest) request).getRequestURI();     if(user == null && uri.indexOf("/monitoring") != -1){      return;     }     super.doFilter(request, response, chain);    }       }   ~~~ ### 实践出真知 本来想自己总结的,发现别人总结的比我好,所以就借鉴了许多别人写的。 我觉得“借鉴”和“复制”别人的并不可耻,因为绝大部分技术都是别人开发的,我们只是用。 那些技术的优点、正确的使用方法,基本是固定的了,我们只有学习的份。 这类工具的使用方法,网上一大堆。 不过,自己总结下,加深下理解,今后也有自己的参考。 看自己总结的,更亲切,更可读,出错的可能性更小。
';

(六):设置MySQL的最大连接数(max_connections)

最后更新于:2022-04-01 06:59:58

# (六):设置MySQL的最大连接数(max_connections) 在上一篇文章中"[一个Web报表项目的性能分析和优化实践(二):MySQL数据库连接不够用(TooManyConnections)问题的一次分析和解决案例 ](http://blog.csdn.net/fansunion/article/details/13291301)"提到,项目中新增几个数据库后,数据库最大连接数达到了默认的最大值100。此时,如果再创建连接,就会报错(TooManyConnections)。 因此,需要手动设置MySQL的最大连接数(max_connections)。 就是这么一个简单的事,花了几个小时,查询很多资料,请教好友同事“飞鸟”,才搞定这个问题。 问题中存在问题,问题中存在陷阱,是这个问题的真实写照。 **1.设置MySQL的最大连接数。** 网友有如下经验,使用下面这个命令 ~~~ set GLOBAL max_connections=1500; ~~~ **让人迷惑的是,提示“查询OK,0行受到影响”** ~~~ mysql>  set GLOBAL max_connections=1500; Query OK, 0 rows affected (0.00 sec) ~~~ 我误认为没有设置成功,并且通过“show status”这个命令也没有找到“max_connections”这个参数和值。 **实际查询确认方法** ~~~ mysql> show variables like '%max_connections%'; +-----------------+-------+ | Variable_name   | Value | +-----------------+-------+ | max_connections | 1500  | +-----------------+-------+ 1 row in set (0.05 sec) ~~~ 通过这种方法,MySQL重启后,仍然有效。 **2.MySQL配置文件总是被忽略。** World-writable config file '/etc/my.cnf' is ignored To fix this problem, use the following command to change file’s permissions 要修复该问题,使用以下命令更改该文件的权限。 chmod 644 /etc/my.cnf [http://hi.baidu.com/perfect_song/item/32c25c0434dea110ebfe38bb](http://hi.baidu.com/perfect_song/item/32c25c0434dea110ebfe38bb) 权限过大是导致这个问题的原因。 **3.奇葩的MySQL配置文件。** Linux系统/etc/my.cnf有下面这项配置。 因为第一次打开这个文件的时候,就有这项配置,我就觉得这个配置是合理的。 不但如此,我们启动MySQL的方式是 "mysqld_safe &",所以我理解成下面这个配置是 专门为这个命令配置的。 ~~~ [mysqld_safe] log-error=/var/log/mysqld.log pid-file=/usr/local/mysql/mysqld.pid datadir=/usr/local/mysql/var socket=/usr/local/mysql/mysql.sock max_connections=1000 ~~~ 实际上,上面这个配置根本是不合理的,真够坑的。 **正确的配置** ~~~ [mysqld] datadir=/usr/local/mysql/var socket=/usr/local/mysql/mysql.sock pid-file=/usr/local/mysql/mysqld.pid log-error=/var/log/mysqld.log max_connections=1000 ~~~ **疑惑**1:如果这个地方配置了“max_connections”,同时设置了“set GLOBAL max_connections=1500”,哪个会起作用呢? **疑惑**2:今天在看MySQL5.1参考手册时,"一般情况,你不应编辑**mysqld_safe**脚本。相反,应使用命令行选项或my.cnf选项文件的[mysqld_safe]部分的选项来配置**mysqld_safe**。"貌似[mysqld_safe]是可以存在的。 **更多详细配置** 可以参考Windows下MySQL的配置参数, my.ini、 my-huge.ini、 my-large.ini。 **4.mysqld_safe正确的启动方式。** "mysqld_safe & ","&"的作用是让MySQL服务在后台跑。这样,咱们仍然可以正常使用当前bash终端。  以前书上有讲过,忘记了。  基础不牢,地动山摇! **5.无法使用localhost主机连接mysql。** mysql -uroot -p123456 无法登录,提示"Can't connect to local MySQL server through socket '/tmp/mysql.sock'" 而 mysql -h 192.168.1.1 -uroot -p123456 可以登录。 最终,我们发现mysql.sock是手动创建的,而Linux的socket文件是不能被编辑的。 解决方案:给个链接。 ln -s /var/lib/mysql/mysql.sock /tmp/mysql.sock 或者mysql的配置文件socket=/usr/local/mysql/mysql.sock,不使用/tmp/mysql.sock。 **小结** 在解决这个看似简单的问题过程中,遇到了更多的问题。 耐心真的非常重要! 此外,认真看书,打好基础,也是可以避免一些问题的。 幸好,有网友分享经验,有好友同事“飞鸟”相助,总算是圆满解决了。 自己也算是积累了不少经验。 **反思** “老鸟”与“菜鸟”的一个重要区别就是,“老鸟”有着丰富的实践经验,而“菜鸟”没有。 很多问题,不亲自遇到并解决一次,你根本就不知道这些莫名其妙的问题为什么会发生。 我不想一直作为“菜鸟”,希望早日成为“老鸟”,倘若是一只“年轻的老鸟”就更好了。 不但如此,还希望能够及时总结这些经验,供今日的和明日的菜鸟参考。 若干年之后,当回想起作为“菜鸟”的日子,亦能感到自豪。
';

(五):重构有助于性能优化么?

最后更新于:2022-04-01 06:59:55

# (五):重构有助于性能优化么? 项目从初次开发到现在,已经快3年了。期间,有N个工程师参与过。 需求方面:增加减少,反反复复,无数次;人力方面:增加减少,不稳定;时间方面:功能开发着急上线,Bug开发紧急修复。 因此,代码臃肿,问题颇多。 自从毕业加入到项目,深感代码质量问题对项目开发效率的影响,因此经常会对项目进行“重构”和“优化”。 主要经历了2次大的重构和无数次小的改进。 今天,特别整理下,希望今后可以做得更好。 **重构事项** **1.统一标准。**  包、类、方法、字段等命名按照Java标准规范那样,统一命名。 **2.重新组织代码。** a. 将项目代码,按照业务逻辑等原则,重新组织到不同的包中。 b. 按照一定的原则,调整类中的方法。 比如对于数据访问层Dao的方法,严格按照查询、增加、修改、删除这样的顺序组织。 而且,使用和修改频率高的代码,放在靠前的位置。 **3.增加必要的注释。** 对于那些业务比较复杂,容易混淆的代码,加上清晰简洁的注释。 **4.删除无用的代码。** 将废弃的功能代码,无用的注释,先用@Deprecated标记,过段时间,然后彻底删除。 **5.提高代码复用。** Dao层:编写一个实现了常用CRUD功能的BaseDao。 工具层:对常用的工具代码,进行整理,统一到工具库中。 **6.清理数据库。** a.将数据库中没有用到的表、字段全部删除。 b.优化表的数据类型。比如将longtext的字段,修改为varchar(20)。 c.规范化表和字段的命名。比如用户表用User,用户地址用userAddress表示。 同时,Java代码和实体配置文件,尽可能与数据库表相统一。 **参考建议** 更多重构和优化事项,不再赘述。 有兴趣的同学,可以看看《代码大全》、《重构》、《编写可读代码的艺术》这3本书。 **重构有助于性能优化么** 我只能说,有一定的帮助。 a.重复代码比较少,注释清晰,命名合理的代码,看起来“赏心悦目”。 本质上,不太可能提高系统性能,但是算得上是性能优化的准备工作。 项目重构后,开发更有效率,Bug更少,这样才可能有更多的时间去做性能优化。 b.去掉数据库表中无用的字段,这还是有点作用的。 至少,在SQL查询的时候,比如select *,会少查询一些字段。 **见仁见智** 重构是否有助于性能优化,是一个见仁见智的问题。 根据我有限的重构和优化经验,我只能说,“重构有助于优化性能,但作用有限”。 打个比喻,当你学画画,想要画一个苹果的时候,“把苹果洗干净”是否有助于你画出一个逼真的苹果呢? “把苹果洗干净”就是代码重构的过程。 “画出一个逼真的苹果”就是性能优化的过程。 你是怎么看待这个问题的呢?愿闻其详...
';

(四):MySQL建立索引,唯一索引和组合索引

最后更新于:2022-04-01 06:59:53

# (四):MySQL建立索引,唯一索引和组合索引 先大致介绍下项目的数据库信息。 数据库A:主要存放的通用的表,如User、Project、Report等。 数据库B、C、D:一个项目对应一个数据库,而且这几个项目的表是完全一样的。 **数据库表的特点** A中的表:数据量几乎都比较小,比如User表中用户数,顶多也就几百上千。 B中的表:X/Y/Z 3张表几乎是确定的,Data表 中的数据量比较大,几千万到上亿。 周期性的会加入一大批数据,比如,每月末增加几百万条数据。 即一般情况下,B中的表只有查询操作,而且特别是Data查询频繁且数据量很大。 **建立索引** 1.为所有的表建立了唯一索引,索引字段是主键id。 2.考虑到数据库A中表的数据量很小,暂时没有建立组合索引。 如有可能,对频繁查询的表和字段,后期尝试加入组合索引。 3.对Data表建立组合索引。 频繁查询的一条SQL语句 select  from Data where projectId=? and (inputVersion in (201)) and (sideId in (10001)) and (breakId in (?)) and (periodId in (?))  order by id desc; 建立组合索引的语句 **ALTER TABLE Data ADD INDEX data_query_index (projectId,inputVersion,sideId,breakId,periodId);** **建立索引之前,需要花费2.796秒。** **建立索引之后,只需要0.136秒。** 可以说是,大幅度提升了查询效率。 **索引的弊端** 随之而来的问题:如果已经建立了索引,那么批量增加数据的时候,会特别慢。 一种较快的方法是:批量插入数据之前,先删除索引,提高批量插入数据的效率。 然后,再重新建立索引,提高查询效率。 1000万条记录,重建5个字段的组合索引需要2到3秒。 重建索引的问题是,这个过程中,查询会比较慢。 应对之策:导入数据,重建索引 应该选择 晚上/凌晨等用户较少使用系统的时间段。 **一个建议**  用 explain sql;  可以分析sql语句的执行情况,进而对sql语句进行优化。 **天下武功,唯勤不破** 性能优化,以前只是看过一些书,没啥实践经验。 最近项目需要由我来进行优化,只好硬着头皮一点点去实践。 网上搜资料、请教同事、请教大牛。 周末再多看看书,认真复习和学习Linux、MySQL、Tomcat、Redis等一大堆,先侧重系统优化。
';

(三):提高Web应用服务器Tomcat的内存配置,并确认配置正确

最后更新于:2022-04-01 06:59:51

# (三) :提高Web应用服务器Tomcat的内存配置,并确认配置正确 **摘要** 上一篇,[一个Web报表项目的性能分析和优化实践(一):小试牛刀,统一显示SQL语句执行时间 ](http://blog.csdn.net/fansunion/article/details/13620783),讲述了项目优化的整体背景,重点讲述了统一显示了Web项目SQL语句的执行时间。 本篇,将重点介绍提高Web应用服务器Tomcat的内存配置,并确认配置正确的方法。 **背景** 这个Web报表项目,用的Linux系统,Web应用服务器使用的是Tomcat7.0。 根据已有程序开发的经验,我认为手动分配和提高Tomcat的内存,能够降低Web请求响应时间,提高系统的性能。 手动提高Tomcat内存前后的响应时间,没有记录和进行对比,因此“提高了系统的性能”现在只能算是一种“感觉”。 实际提升了多大幅度的性能,还需要有真实数据进行对比才能知道。 本文,不再探讨此问题,重点阐述如何配置Linux下的Tomcat的内存。 **1.配置方法** **Linux系统** 在/usr/local/tomcat/bin 目录下的catalina.sh添加: JAVA_OPTS='-Xms512m -Xmx1024m' 要加“m”说明是MB,否则就是KB了,在启动tomcat时会 报内存不足。 -Xms:初始值 -Xmx:最大值 -Xmn:最小值 注:Tomcat的路径可能有所不同。 **catalina.sh** ~~~ # -----------------------------------------------------------------------------(上面还有很多#开头的注释) # OS specific support.  $var _must_ be set to either true or false. **JAVA_OPTS= ‘-Xms512m -Xmx1024m’** cygwin=false darwin=false os400=false case "`uname`" in CYGWIN*) cygwin=true;; Darwin*) darwin=true;; OS400*) os400=true;; esac ~~~ **Windows系统** 在catalina.bat最前面加入 set JAVA_OPTS=-Xms128m -Xmx350m **经验教训** Linux下的配置方法和Windows下的是不一样的。 这类问题是很常见的,总结成文,而不是放在大脑里,很有必要。 **2.查看Tomcat的内存配置** 如果,我们只是根据自己的或者网友的方法,配置了参数,就认为“万事大吉”,是非常不科学的,是不负责任的表现。 Tomcat完整的压缩包中,webapps目录含有manager这个项目。 比如C:\apache-tomcat-7.0.22\webapps\manager 我们Linux系统的项目中,已经把这个模块删除了。 现在,重新拷贝这个模块到Tomcat的webapps目录。 **第一次访问的时候,没有权限。** 需要修改tomcat的conf目录下的tomcat-users.xml这个配置文件,增加以下配置 ~~~ <tomcat-users> <role rolename="manager-gui"/> <user username="username" password="123456" roles="manager-gui"/> </tomcat-uses> ~~~ 然后,使用配置的用户名和密码,访问 [http://ip:port/manager](http://ip:port/manager)。 点击Server Status 超链接,或者直接访问 [http://ip:port/manager/status](http://ip:port/manager/status)。 可以看到以下信息 [![](image/569355c2c0d8b.gif)](http://fansunion.cn/wp-content/uploads/2013/10/TomcatServerStatus.gif) 看到“Free memory: 268.18 MB Total memory: 970.18 MB Max memory: 1758.25 MB”才真正表明,我们的配置成功了。 **3.真相何在** 细心的读者可能会发现,“Free memory: 268.18 MB Total memory: 970.18 MB Max memory: 1758.25 MB”与我们配置的 ‘-Xms512m -Xmx1024m’不完全吻合。 从上面的参数中,我们只能知道Tomcat的内存确实增大了,但是增大的规则和我们预期的不完全一样。 Total memory和Max memory等具体表示啥含义,为什么会是这样,我现在还不是很清楚。 这个问题暂时不再深究,今后,再看Tomcat的书籍和文档,可能能够找到这个问题的答案。 知道的同学,可以留言告诉我下。 **4.回答一个热心网友的评论** **评论内容** 我没有仔细看你写的代码,但是看你的标题之后,我处理的流程会是: 在程序没有错的情况下, 一、看hql语句是否被滥用了,该用select col from table的写成select * from table了 二、看where子句的执行顺序,一般最后面的条件是过滤掉大部分数据的条件,以次类推。看看是否改用join的地方使用了select col from table where col in (select ...)的语句,检查视图的sql语句 三、给表的列适当建立索引 四、再高级一些的就是读写分离或者mysql集群了(我也木有搞过) --评论参见:[http://blog.csdn.net/fansunion/article/details/13620783](http://blog.csdn.net/fansunion/article/details/13620783) **我的回复** 1."该用select col from table的写成select * from table了" 查询了过多的字段,这种情况是存在的。 这个晚点优化。 2.看where子句的执行顺序。 咱们用的大多数 关联查询,很少用子查询。 这个晚点优化。 3.给表的列适当建立索引。 这个是本周的重点。 我们有一个数据库,全是数据和维度,有数据更新的时候,才会插入。 索引会比较有效。 4.读写分离或者mysql集群。 等正式上线,数据量大了之后,应该会弄。 我也不熟悉,需要边学边用,不会的就去问boss或者公司相关大牛。
';

(二):MySQL数据库连接不够用(TooManyConnections)问题的一次分析和解决案例

最后更新于:2022-04-01 06:59:49

# (二):MySQL数据库连接不够用(TooManyConnections)问题的一次分析和解决案例 最近,项目中遇到了数据库连接不够的问题。 **异常信息** com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException:  Data source rejected establishment of connection,  message from server: "Too many connections" 根据更详细的错误信息,我定位到报错的函数位置。 **关键函数** ~~~ /**   * 判断数据库是否存在   */   public boolean hasDatabaseByKey(String name) {    boolean containsKey = dataSourceMap.containsKey(name);    if (containsKey) {        try {         //根据数据库名称,把BoneCP数据源中的数据库参数取出来,用户名、密码、URL      Object obj = dataSourceMap.get(name);      com.jolbox.bonecp.BoneCPDataSource dataSource = (com.jolbox.bonecp.BoneCPDataSource) obj;         String password = dataSource.getPassword();      String username = dataSource.getUsername();      String url = dataSource.getJdbcUrl();      //建立新的数据库连接--这一行代码抛出“Too many connections”异常      Connection con = DriverManager.getConnection(url, username, password);      //关闭连接      if (con != null) {       con.close();      }      return true;        } catch (Exception e) {      LOG.error("Database name error:" + name);      e.printStackTrace();     }    }    return false;   }   ~~~   **是否正常关闭了数据库连接** 上述代码的主要功能是,根据前端传入的数据库名字,如“test”,检测该数据库的配置是否正确。 最初想到的,上面的数据库连接con可能没有正常关闭。 经过单步跟踪debug,和使用MySQL的 show processlist命令,发现所有的连接确实是正常关闭的。 因此,上述代码的功能是没有问题的。 **功能没有问题,但是上述代码还是存在其它问题的** a.数据库连接应该在finally里关闭。 b.这种检测方法,每次都需要打开一个连接,比较耗费时间。 一种好的方法是,在系统初始化的时候,检查所有的数据源配置是否正确,把结果 用Map保存起来,("test",true)表明test数据库配置正确; 或者每一次检查,先从缓存中取,如果存在直接使用,否则,打开连接,进行检查,然后保存结果到缓存中。 **问题根源** 项目中使用了多个数据库, 数据源公共的配置如下。 ~~~ <bean id="abstractDataSource" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close"> <property name="username" value="root" /> <property name="password" value="xxxx" /> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="maxConnectionsPerPartition" value="50"/> <property name="minConnectionsPerPartition" value="2"/> </bean> ~~~ **最近新增了10个数据源配置。** 也就是说,现在新增的数据库连接,最高可以达到50*(10+1)=550个了。(不是50*1=50个) 而数据库MySQL的默认max_connections是100。 因此,我们在访问Web项目,然后频繁切换项目的时候,数据库连接池中的数目,已经达到了100。 这个时候,我们再去手动创建数据库连接,就会失败。 **上文代码的更多配置信息** ~~~ @Resource   private Map dataSourceMap;   ~~~ ~~~ <!-- 配置多数据源映射关系 --> <bean id="dataSourceMap" class="java.util.HashMap"> <constructor-arg> <map key-type="java.lang.String"> <entry key="demo1"> <bean parent="abstractDataSource" > <property name="jdbcUrl" value="jdbc:mysql://ip:3306/demo1?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull" /> </bean> </entry> <entry key="demo2"> <bean parent="abstractDataSource" > <property name="jdbcUrl" value="jdbc:mysql://ip:3306/demo2?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull" /> </bean> </entry> </map> </constructor-arg> </bean> ~~~ **改进后的代码** ~~~ private static Map databaseStatusMap = new HashMap();     /**    * 判断数据库是否存在    */    public boolean hasDatabaseByKey(String name) {     //数据源中是否含有这个数据库名称     boolean containsKey = dataSourceMap.containsKey(name);     //不存在这个数据名字,直接false     if (!containsKey) {      return false;     }        //首先从缓存中拿,不为null,表明已经验证过了     Boolean status=databaseStatusMap.get(name);     if(status != null){      return status;     }          //缓存中不存在,第1次验证     boolean databaseConfigSucceed = false;     Connection con = null;     try {      Object obj = dataSourceMap.get(name);      com.jolbox.bonecp.BoneCPDataSource dataSource = (com.jolbox.bonecp.BoneCPDataSource) obj;         String password = dataSource.getPassword();      String username = dataSource.getUsername();      String url = dataSource.getJdbcUrl();         //验证数据库连接,把结果存到缓存中      con = DriverManager.getConnection(url, username, password);            //这个地方con不可能为null,要么是一个正常的连接,要么抛出异常      databaseConfigSucceed = true;      databaseStatusMap.put(name, true);      /*      if (con != null) {      databaseConfigSucceed = true;      databaseStatusMap.put(name, true);     }      else{      databaseConfigSucceed = false;      databaseStatusMap.put(name, false);     }*/          } catch (Exception e) {      //抛出异常,下次不会去重新检查,可能会存在bug,如果第1次检测的时候,网断了或者MySQL挂了或者数据库连接过多,并不能代表配置不正确      //小概率事件,暂时不考虑      LOG.error("Database name error:" + name);      databaseConfigSucceed=false;      databaseStatusMap.put(name, false);      e.printStackTrace();     } finally {      if (con != null) {       try {        //验证完数据库连接,需要手动关闭        con.close();       } catch (SQLException e) {        e.printStackTrace();       }      }     }     return databaseConfigSucceed;    }   ~~~ **小概率事件** 如果在执行 DriverManager.getConnection(url, username, password);获取数据库连接的时候,发生了异常。 如果恰好是第1次检测的时候,网断了或者MySQL挂了或者数据库连接过多,并不能代表配置不正确。 抛出异常,下次就不会去重新检查,可能会存在bug。 不过,系统第一次访问项目的时候,正好出故障的可能性很低。 可能存在的错误情况: 第一次成功,加入缓存为true,如果今后数据库连接打不开,应该提示“不可以打开”,也会提示“可以打开”。 第一次不成功,加入缓存为false,如果今后数据库能够打开,应该提示“可以打开”,也会提示“不可以打开”。 **结论** 1.在不使用缓存的情况下,每次都通过建立新连接的方式。    优点:可以准确的判断数据库配置是否正确,是否真正能够连接到数据库,代码的可读性和复杂度比较低。   缺点:性能比较差。 2.使用缓存。    优点:性能比较高。    缺点:在不正常情况下,准确性没有保障。代码的可读性和复杂度比较高。 **再次改进** 缓存,增加“时间限制”,过一段时间后,就失效。 **观点** 实现的功能越多,越准确,性能越好,代码可能会越来越复杂。 代码的正确性和性能有的时候是“互相排斥”的。 过多的追求完美,也会带来一些负担。 **原文参见**: [http://FansUnion.cn/articles/2961](http://fansunion.cn/articles/2961)
';

(一):小试牛刀,统一显示SQL语句执行时间

最后更新于:2022-04-01 06:59:46

# (一):小试牛刀,统一显示SQL语句执行时间 最近,在开发和优化一个报表型的Web项目,底层是Hibernate和MySQL。 当报表数据量大的时候,一个图表要花4秒以上的时间。 **以下是我的分析和体会。** **1.我首先需要知道哪些函数执行了多少时间,哪些sql花了多少时间。** a.最笨最简单的方法是,每一个函数的调用开始和结尾都保存开始时间startTime和结束时间endTime,  进行计算。 b.写一个“拦截器”,拦截每一个方法的执行,计算时间。 这个又太难了,没啥思路。 c.想到Hibernate查询,真正执行sql语句的方法是query.list()方法。 因此,只需要计算query.list()这个方法的执行,大概就能得出每个函数的执行时间了。 很巧的是,我写了一个功能强大的BaseDao,大部分查询最终归结到了一个方法。 ~~~ protected List executeQueryList(String hql, Map params,   Integer firstResult, Integer maxResults) {         Query query = createQuery(hql, params);         if (firstResult > 0) {             query.setFirstResult(firstResult);         }            if (maxResults > 0) {             query.setMaxResults(maxResults);         }         Date startTime = new Date();         List list = query.list();         String costTime = CostTimeUtils.getCostTime(startTime);         println(query, costTime);         return list;     }   ~~~ 这样,我只需要在一个地方,计算耗费的时间,就大致知道了每个方法和sql语句的实际执行时间了。 **2.看了下Hibernate打印出来的SQL语句和程序源码,发现执行的数据查询太多,平均每个0.02到0.05,少数需要0.34,0.35。**   一个0.02s,查询几十次到上百次,花费的时间就多了。 **3.少数查询要0.34,0.35, 应该是网络引起的??** 后来,经过自己的分析,与同事好友的交流,可能的原因是查询的字段太多导致的,Hibernate通过反射向Java实体对象填充值,也需要花费一些时间。 然后,我又想到,TReport这个表字段确实不少,更关键的是我们把一些图表的信息存放在这个表里的longblob类型的字段。 **4.字段的数据类型有影响么?** 有些小数据量的字段,用的是longtext。 逐个将其统一为varchar。 **5.统一Hibernate用法,方便查看HQL/SQL语句的执行时间。** 以前我对项目进行重构过,现在大部分的查询都要经过BaseDao中的一个查询方法, ~~~ Date startTime = new Date();    List list = query.list();    String costTime = CostTimeUtils.getCostTime(startTime);    println(query, costTime);   ~~~ 但是,还有很多查询没有经过这里,导致有的SQL执行时间,不方便查看。 **6.最初,想查看每个方法执行时间的时候,我首先想到的是找一个这样的工具。** 不过,最后还是没能找到。 JProfile之类的工具,还没怎么用过,抽空再学习和应用下。 **7.针对查询用到的SQL语句,建立合适的索引。** 这个还没有头绪,正在摸索中。 **8.查询方法执行效率不高?** ~~~ public Treport getReportById(Integer reportId) {           String hql = "from Treport where reportId = :reportId";           Treport report = super.executeQueryUnique(hql, "reportId", reportId);           return report;       }   ~~~ 有的地方,根据HQL语句查询没有必要。 Treport表的主键就是reportId,可以使用Hibernate的get(id)来查。 多写了代码,维护麻烦,效率还不高。 我的感觉是:自己构造HQL语句查询,没有Hibernate的get方法效率高,没有具体去测试。 **不足之处** 上文都是一些比较浅显的分析,更为详细的优化过程,最近几周正在实践之中。 后续几篇,将详细描述分析和优化过程。 **原文参见**:[http://FansUnion.cn/articles/2843](http://fansunion.cn/articles/2843)
';

前言

最后更新于:2022-04-01 06:59:44

> 原文出处:[性能分析和优化实践](http://blog.csdn.net/column/details/codeperformance.html) > 作者:[雷文](http://blog.csdn.net/FansUnion) **本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!** # 性能分析和优化实践 > 实际项目开发和代码研究学习过程中,总结的性能分析方法和优化实践经验。
';