在产品上线时发现当用户数量大并发性能差,经常发现数据库死锁,经诊断,是由于设置了不正确的事务隔离,可以做如下优级化(这里我们使用JTA事务):
JTA
具有的
3
个接口:
UserTransaction
接口、
TransactionManager
接口和
Transaction
接口,这些接口共享公共的事务操作。
UserTransaction
能够执行事务划分和基本的事务操作,
TransactionManager
能够执行上下文管理。
在一个具有多个数据库的系统中,可能一个程序将会调用几个数据库中的数据,需要一种分布事务,或者准备用
JTA
来管理
Session
的长事务,那么就需要使用
JTATransaction
。
在
hibernate.cfg.xml
中配置
JTA
事务:
<session-factory>
<property name="hibernate.transaction.factory_class">
org.hibernate.transaction.JTATransactionFactory
</property>
org.hibernate.transaction.JTATransactionFactory
</property>
选择隔离级别:
数据库系统提供了四种事务隔离级别供用户选择。
Serializable(隔离级别值:8):串行化。隔离级别最高
Repeatable Read(4):可重复读。
Read Committed(2):读已提交数据。
Read Uncommitted(1):读未提交数据。隔离级别最差(不建议使用,除非只做查询)。
大多数应用程序,我们可以优先考虑Read Committed,它能够避免脏读,而且具有较好的并发性能。
在hibernate.cfg.xml中设置隔离级别:
<session-factory>
<property name="hibernate.connection.isolation">2</property>
</session-factory>
<property name="hibernate.connection.isolation">2</property>
</session-factory>
但是以上仅是针对一般应用而言,我们可以考虑如下情况:
当数据库系统采用Red Committed隔离级别时,会导致不可重复读和第二类丢失更新的并发问题,在可能出现这种问题的场合。可以
在应用程序中采用悲观锁或乐观锁来避免这类问题。
乐观锁假定当前事务操纵数据资源时,不会有其他事务同时访问该数据资源,因此不作数据库层次上的锁定。为了维护正确的数据,乐观锁使用应用程序上的版本控制(由程序逻辑来实现的)来避免可能出现的并发问题。唯一能够同时保持高并发和高可伸缩性的方法就是使用带版本化的乐观并发控制。版本检查使用版本号、 或者时间戳来检测更新冲突(并且防止更新丢失)。
Hibernate中通过版本号检查来实现后更新为主,这也是Hibernate推荐的方式。在数据库表中加入一个version(版本)字段,在读取数据时连同版本号一起读取,并在更新数据时比较版本号与数据库表中的版本号,如果等于数据库表中的版本号则予以更新,并递增版本号,如果小于数据库表中的版本号就抛出异常。
悲观锁假定当前事务操纵数据资源时,肯定还会有其他事务同时访问该数据资源,为了避免当前事务的操作受到干扰,先锁定资源。尽管悲观锁能够防止丢失更新和不可重复读这类并发问题,但是它影响并发性能,因此应该很谨慎地使用悲观锁。
另外,尽量在程序中尽量做小的事务,仅用于查找的方法,就只要用readonly
- <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
- <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>