性能优化攻略
最后更新于: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)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# 性能分析和优化实践
> 实际项目开发和代码研究学习过程中,总结的性能分析方法和优化实践经验。