事务是数据库并发控制不可分割的基本逻辑单位,可以用于确保数据库能够被正确修改,避免数据至修改了一部分而导致的数据不完整,或修改时收到用户干扰。
事务具有原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)、和持久性(Durability)。
多个事务同时使用相同数据时可能发生的问题:
1、第一类丢失更新:当多个事务同时操作同一个数据,撤销其中一个事务时,把其他事务已提交的更新数据覆盖,对其他事务来说造成数据丢失。
2、第二类丢失更新:多个事务同时操作同一个数据,事务A将修改结果成功提交后,对事务B已提交的修改结果进行了覆盖,对B来说造成数据丢失。
3、脏读:多个事务同事操作同一数据,事务A读到事务B未提交的更新数据,且对数据进行操作,如果B撤销更新后,事务A所操作的数据变成了脏数据。
4、不可重复读:多个事务操作同一数据,事务A对同一行数据重复读两次,每次读取的结果不同。有可能第二次读取数据时原数据被事务B更改,并成功提交。
5、幻想读:多个事务操作同一数据,事务A执行两次查询,第二次查询结果比第一次查询多出一行,这是因为两次查询之间事务B插入了新数据造成的。
为避免以上并发问题,提出4个事务隔离级别:
1、序列化(8级)
提供最严格的事务隔离,该隔离不允许事务并行执行,只允许一个接一个执行,可有效防止脏读、不可重复度和幻想读。
2、可重复读取(4级)
事务执行过程中可以访问其他事务成功提交的新插入的数据,不能访问成功修改的数据。
3、读已提交数据(2级)
一个事务在执行过程中既可以访问其他事务成功提交的新插入数据,又可以访问成功修改的数据。但未提交的写事务会禁止其他事务访问数据,可防止脏读。
4、读未提交的数据(1级)
事务执行过程中既可以访问其他事务未提交的新插入数据,又可以访问未提交的修改的数据。如果一个事务已经开始写数据,则不允许另外一个事务同时进行写操作,但允许其他事务进行读操作。此隔离可以防止第一类丢失更新。
通常数据库隔离级别设置为2,即读已提交数据。进而导致的可能不可重复读、幻读和第二类丢失更新可以通过悲观锁或乐观锁来加以控制。
Hibernate配置文件hibernate.cfg.xml中通过hibernate.connection.isolation属性设置隔离级别:<property name="hibernate.connection.isolation">2</property>
Hibernate的悲观锁和乐观锁
1、悲观锁:每次操作数据时,总是悲观的认为会有其他事务也会来操作同一数据,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁由数据库来实现,在锁定的时间其他事务不能对数据进行存取,这样很有可能造成长时间等待。Hibernate中,用户可以显示的设定要锁定的表或字段及锁模式。
锁模式:
(1)LockMode.NONE
如果缓存中存在对象,直接返回该对象的引用,否则通过select语句到数据库中加载该对象,这是锁模式的默认值。
(2)LockMode.READ
不管缓存中是否存在对象,总是通过select语句到数据库中加载该对象,如果映射文件中设置了版本元素,就执行版本检查,比较缓存中对象是否与数据库中的对象版本一致。
(3)LockMode.UPGRADE
不管缓存中是否存在对象,总是通过select语句到数据库中加载该对象,如果映射文件中设置了版本元素,就执行版本检查,比较缓存中对象是否与数据库中对象版本一致,如果数据库系统支持悲观锁(如Oracle/MySQL),就执行select```from update语句,如果不支持(如Sybase),就执行普通select语句。
(4)LockMode.UPGRADE
与LockMode.UPGRADE具有同样功能,此外,对于Oracle等支持update nowait的数据库,执行select```for update nowait语句,nowait表明如果执行该select语句的事务不能立即获得悲观锁,那么不会等待其他事务释放锁,而是立刻抛出锁定异常。
(5)LockMode.WRITE
保存对象时会自动使用这种锁定模式,仅供Hibernate内部使用,应用程序中不应该使用它。
(6)LockMode.FORCE
强制更新数据库中对象的版本属性,从而表明当前事务已经更新了这个对象。