我们使用关系数据库技术的其中一个原因是现有的RDBMS实现提供了非常成熟、可扩展和健壮的并发控制。这不仅仅意味着简单的读写锁。例如,使用锁定的数据库被设计成在某个事务获得/多个/锁定时能够高效扩展 - 这被称为/锁定升级/。另一方面,一些数据库(例如,Oracle和PostgreSQL)根本不使用锁 - 相反,它们使用多版本并发模型。这种复杂的并发方法旨在实现比传统锁定模型更高的可扩展性。数据库甚至允许你指定所需的交易隔离级别,让你可以在隔离性和可扩展性之间进行权衡。
不幸的是,一些Java持久化框架(尤其是CMP引擎)假设他们可以通过在Java应用程序中实现自己的并发控制来改进这些关系型系统多年的研究和开发。通常,这会采用一种相对粗糙的锁定模型,锁被保留在Java中间层。这种做法有三个主要问题。首先,它破坏了底层数据库的并发模型。如果你在Oracle安装上投入了大量资金,似乎很疯狂,用(可扩展性较差的)锁定模型取代Oracle的复杂多版本并发模型。其次,共享相同数据库的其他(非Java?)应用程序不知道锁的存在。最后,中间层持有的锁不能自然地扩展到集群环境。需要某种类型的分布式锁。最好情况下,分布式锁定将使用一些高效的组通信库(如JGroups)来实现。最坏的情况下(例如,在OJB中),持久化框架将锁持久化到特殊数据库表中。显然,这两种解决方案都伴随着很高的性能成本。因此,Hibernate被设计为/不需要/任何中间层锁——甚至避免了线程同步。这也许是Hibernate最优秀且最不被理解的特性,也是Hibernate扩展性良好的关键。那么,为什么其他框架不直接让数据库处理并发呢?
在中间层持有锁的唯一合理理由是,我们可能正在使用中间层缓存。事实证明,确保数据库和缓存之间一致性的问题是极其困难的,而解决方案通常确实涉及中间层锁的使用。(顺便说一句,大多数使用缓存的程序甚至在没有集群的环境中也没有正确解决这个问题。)
因此,例如,当Hibernate与JBoss Cache集成时,缓存实现必须内部获取集群锁(再次使用JGroups)。在Hibernate中,我们认为这是缓存实现的质量服务问题,需要提供这种功能。我们可以这样做,因为Hibernate与其他许多持久化层不同,具有二级缓存架构。这种设计将事务范围内的/session缓存/(不需要中间层锁定并将并发问题委托给数据库)与进程或集群范围内的/二级缓存/(可能需要中间层锁定)分开。因此,当为特定类禁用二级缓存时,不需要中间层锁。因此,在这种情况下,Hibernate的可扩展性仅受底层数据库的可扩展性限制。我们的设计还允许我们考虑其他更复杂的确保二级缓存和数据库之间一致性的方法——这些方法不需要使用中间层锁定。我现在暂时保密;这是一个积极的调查领域!