数据访问层的性能优化

本文只是自己项目的经验总结。

目前自己独立做的一些项目都是采用传统的MVC模式,采用Spring MVC+Hibernate+Mysql 开发,Request的调用层次是Controller 调用Service 调用DAO。项目的成品部署在自己的阿里云上面,配置是单核+2G内存。因为服务器的配置较低,所以就对程序的性能也做了一些优化,算是有一些思考。

做过几个项目之后发现,首先数据库结构的设计对性能影响很大,比如表之间的关联很多的话就会造成一个功能需要访问多张表。

首先谈谈我所供职的公司的一个系统,有个Hibernate的domain Object定义用到了很多many-to-one的关系。实体是一个用户,然后用户有他的address(一张表),phonenumber(一张表),electricaddress(一张表)等等,总共有七八张表。每次查询出一个用户信息,这些附带信息都会一并查询,而且这里lazy=false。这个查询在压力较大的情况下马上就出问题了,虽然性能测试的环境有十几个server一起并发。最终相关的负责人员只是把这个查询改成了native query--这已经有很大的性能提升了。我本人觉得还需要更大的改动:

1. 对于用户这个entity的定义不该关联那么多张表,这个entity的定义应该是个简单的单表对象,大部分的使用场景中它不需要查询关联表。尽量避免one-to-many, many-to-one.使用了这些关联关系之后,需要设置cascade,如果设置为all或者save-update之类,在更新,新增方面的性能也比较差强人意。我们公司有一个对实时性要求比较高的系统就因为是使用了cascade=save-update造成腰保存一个对象的时候触发update其他相关的表,多个SQL语句对性能影响很大。

2. 对于需要查询那一堆数据的情况下,我们可以单独处理,用native query或者存储过程等手段。

3. 尽量避免使用lazy=false,Hibernate多表关联的情况下,lazy=false会触发多条查询语句,严重影响性能。你真的需要查询多张表的话也应该尽量考虑自己写SQL,性能要好的多。这也许是Mybatis对Hibernate的优势之一。


我自己的系统因为运行在低配的server上所以更加需要优化,一开始设计表结构的时候比较复杂,又是用的Hibernate,使用了很多关联,造成查询时间较长。

首先考虑了使用缓存,启用Hibernate的缓存-二级缓存和查询缓存。这是有一定的效果的,但是每次数据发生变化缓存失效以后,查询还是很费时间。所以优化查询才是最重要的,对于查询的优化我想到三种方式:

1. 使用native query

2. 使用view

3.使用存储过程

测试了三种方式之后发现,如果仅仅是一个简单的查询,三种方式效果差不多。当然都比直接用Hibernate的对象映射快很多。如果查询比较复杂而且需要带输入参数的话,我觉得可能存储过程还是最好的选择,毕竟是预先编译的。而且使用存储过程对于数据库多表更新,更新,新增,删除操作在同一个方法里面的这种场景非常有用。使用java直接编码会很慢。

但是使用这三种方式之后都会有一个共同的问题,就是Hibernate的查询缓存不能再用了,因为这个时候Hibernate就不知道什么时候该让缓存失效了,这就是优化SQL本身带来的问题。取舍的问题,是采用native的方式,还是Hibernate能够控制的方式很重要,具体要看对性能的需求以及系统的复杂度了。

虽然这个时候已经快很多了,但是不能使用Hibernate的缓存还是让我不是很满意,所以我决定自己加一层缓存。我首先想到的是加到Service层上,给Service加个代理,查询的时候先从Cache拿,拿不到再去DB拿,在数据没有发生改变的情况下这种访问肯定是很快的,在本地机器上查DB是300 到400 ms,而查缓存只有3ms,在server上查DB要200多ms,查缓存是0ms。这是空间换时间的好处。而这个时间在没有优化SQL之前在本机上是7000多ms。如果在这个方法上加上@Transactional的声明,即便是从缓存读取也需要200多ms,即使加上readOnly=true,时间也只是是十几ms。说明@Transactional只应该加在真正要做数据访问的地方。Cache的代理也应该最好是加在DAO层,service层也可以加,具体看业务了。使用代理模式还说比较灵活的,基本符合开闭原则。

由此而引出了@Transactional对于性能的影响,懒人的做法是在所有方法上都加上@Transactional,这样可以保证事务的一致性,原子性。但是假如一个方法里面除了数据库访问之外还包括其他操作,尤其是一些消耗时间的操作,比如文件读写,邮件发送等,那么将会让整个事务的时间变长。事务本身只服务于数据库访问,Spring的事务管理器本质上也是在管理数据库事务,比如Spring的Hibernate Transaction Manager,它管理了一个Hibernate的SessionFactory,在一个被@Transactional声明的方法中Spring管理了Hibernate的Session。所以我认为@Transactional使用的最佳实践应该是只加在DAO层,当然如果需要加在Service层也要尽量把一个大事务划分成几个小事务,把数据库不相关的操作从事务的声明中移除。比如上面说到的缓存代理,在访问缓存的时候根本不需要开启事务,这样节省了很多时间,只要真正要访问数据库的时候才开启事务。

需要注意的是@Transactional只有加在被注入的类的public方法上才起作用。虽然它被加在private方法上不会报错但是其实是完全没有作用的。假如你在一个类的某个方法上加了@Transactional注解,但是这个类被其他类使用的时候不是被注入的,那也是没用的。其次,如果@Transactional被加在父类的方法上,但是使用的时候注入的是它的子类,那这个事务声明也是没用的。换句话说,它只有被直接注入才有用。这个错误不容易被发现,因为编译器不会报错给你,运行时也不会报错。如果没有事务相关的错误你永远不会知道。

总结一下,总共讨论了一下几个方面对性能的影响:

1. 数据库表的结构的设计。

2. Hibernate的表关联的使用。

3. 优化SQL,使用存储过程,view等对性能的影响。

4. 使用Cache

5. @Transactional的性能的影响。

另外在读写操作同时都很频繁的情况下,读写分离也是一个提高性能的很好的方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值