最近在维护系统的时候,发现有个页面加载速度明显比其他页面的加载速度慢一些,遂逐行进行跟踪排查,最后发现一段操作数据库的代码,其形式如下:
这里在循环中出现了频繁的数据库操作,共访问数据库ts.size()+1
次。虽然,现在各种ORM框架都提供了对数据池的支持与较好的管理,省去了很多的打开连接关闭连接的操作,但是访问数据库的IO开销依然不得不纳入思考的范围。
为了验证频繁的数据库操作所带来的IO开销是否会对系统的性能造成一定的影响,下面通过一个例子进行测试比较:
首先,假设有一个学校,学校有多个班级,每个班级都有一定数量的学生。现在学校领导想要知道某个年级某几个班级今年的学生的数量有多少。
接下来,我们将通过编码来实现该功能。
开发环境 | 开发技术 |
---|---|
IDEA,MySQL | hibernate,junit |
##搭建项目的框架
首先,添加Hibernate配置文件hibernate.cfg.xml
定义实体类:ClassRoom
和 Student
|
|
|
|
编写Hibernate工具类HibernateUtil
##编写测试类
为了测试我们的想法,首先需要先向数据库中添加数据。
编写测试类ClassRoomAndStudentGenerator
,在数据库中随机添加一定量的数据,由于在实体类中没有定义ClassRoom和Student的关系映射,所以在设置某个学生的班级id的时候,要保证不要设置一个不存在的班级id。
首先我们编写本文一开始提到的loop
方法,这种方法的想法就是:先查询出这些班级的ids,然后遍历这些id,查询班级id为特定值的学生个数。思路很简单,代码也很好编写,但是需要访问n+1
次数据库。
我在方法的前后统计其执行的时间,来与不同的实现方式进行对比。
第二种思路:全连接。从两个表中选择班级id=学生班级id
然后group by 班级id
,最后count(*)
返回结果。只需要访问一次数据库。
第三种思路:利用in
进行查询。首先,查询出指定的班级ids,利用in
字句进行查询,返回结果。访问数据库两次。
当改变
private static final int CLASS_NUM = 10; //100,1000
private static final int STUDENT_NUM = 100; //1000,10000
的值就会有不同的结果出现。
当CLASS_NUM=10,STUDENT_NUM=100
时,
loopCount : 1352 ms
innberJoinCount : 1345 ms
batchCount : 1368 ms
当CLASS_NUM=100,STUDENT_NUM=10000
时,
loopCount : 1780ms
innberJoinCount : 1346ms
batchCount : 1434ms
当CLASS_NUM=1000,STUDENT_NUM=100000
时,
loopCount : 25615 ms
innberJoinCount : 1472 ms
batchCount : 1605 ms
当数据量增大的时候,可以明显的看到,每多一次数据库访问,就会额外的多带来一定的开销。如果一次查询就能完成的工作不建议分为多次查询。