事务并发
- 格式:doc
- 大小:39.50 KB
- 文档页数:4
在现代社会中,事务处理是一项非常重要的活动。
无论是个人生活中的日常琐事,还是企业、组织或政府的运营活动,都需要进行各种事务处理。
然而,在进行事务处理时,我们常常会遇到并发冲突的问题。
本文将探讨事务处理中的并发冲突以及冲突解决策略。
首先,我们需要了解并发冲突是什么。
简单来说,当多个任务同时操作一个共享资源时,就会引发并发冲突。
例如,在一个公司的财务系统中,多个员工同时处理相同的财务报表,可能会导致数据混乱或不一致的情况发生。
这就是并发冲突。
那么,怎样解决并发冲突呢?通常有以下几种策略可供选择。
首先,我们可以使用互斥锁来解决并发冲突。
互斥锁是一种机制,它可以确保同时只有一个任务可以访问共享资源。
当一个任务正在执行时,其他任务必须等待,直到该任务释放互斥锁。
通过互斥锁,我们可以有效地避免并发冲突的发生。
然而,互斥锁也带来了性能上的损耗,因为任务可能需要等待很长时间才能获得互斥锁。
除了互斥锁,还可以使用读写锁来解决并发冲突。
和互斥锁类似,读写锁也是一种控制资源访问的机制。
但是,与互斥锁不同的是,读写锁允许多个任务同时读取共享资源,但只允许一个任务进行写操作。
通过使用读写锁,我们可以在保证数据一致性的前提下,提高系统的并发性能。
此外,我们还可以使用事务来解决并发冲突。
事务是一组操作的逻辑单元,它们要么全部执行成功,要么全部回滚到原始状态。
通过使用事务,我们可以保证数据的一致性和完整性。
同时,事务还具有隔离性的特点,可以防止并发冲突的发生。
在事务中,我们可以使用锁机制或乐观并发控制等策略来解决并发冲突。
当然,以上只是解决并发冲突的一些常见策略,实际情况还会因具体的系统和应用而有所差异。
在进行事务处理时,我们需要结合实际需求和技术条件,选择合适的冲突解决策略。
总结一下,事务处理中的并发冲突是一个常见的问题。
为了解决并发冲突,我们可以采用互斥锁、读写锁等机制来控制资源访问;也可以使用事务来保证数据的一致性和完整性。
数据库事务的隔离级别与并发控制在数据库管理系统中,事务的隔离级别和并发控制是确保数据完整性和一致性的重要手段。
隔离级别定义了事务之间的可见性,而并发控制则管理并发执行事务的方式。
本文将详细介绍数据库事务的隔离级别和并发控制。
一、事务的隔离级别1. 未提交读(Read Uncommitted)未提交读是最低的隔离级别,事务对其他事务所做的修改可以立即可见。
这会导致脏读(Dirty Read)问题,即读取到了尚未提交的数据,容易造成数据不一致。
2. 提交读(Read Committed)提交读是较低的隔离级别,事务只能读取已经提交的数据。
这避免了脏读,但可能会导致不可重复读(Non-Repeatable Read)问题,即在同一个事务中,两次读取同一个数据的结果不一致。
3. 可重复读(Repeatable Read)可重复读是较高的隔离级别,事务在执行期间多次读取同一个数据得到的结果是一致的。
这避免了脏读和不可重复读,但可能会导致幻读(Phantom Read)问题,即在同一个事务中多次执行相同的查询,结果集却发生了变化。
4. 串行化(Serializable)串行化是最高的隔离级别,事务串行执行,保证了数据的完全一致性。
但这会导致并发性能降低,因为每次只有一个事务能够同时执行。
二、并发控制的方法1. 锁机制锁机制是最基本的并发控制方法之一,通过给数据或资源加锁来实现对并发访问的控制。
常见的锁类型有共享锁和排它锁,共享锁允许多个事务并发读取数据,而排它锁则只允许一个事务独占访问数据。
2. 并发控制算法并发控制算法包括多版本并发控制(MVCC)、时间戳排序和两段锁协议等。
这些算法通过在数据中维护版本信息、时间戳或锁状态来实现事务的并发控制。
不同的算法适用于不同的场景,具体的选择需要根据实际需求和性能考虑。
3. 乐观并发控制乐观并发控制是一种无锁的并发控制方法,通过版本号或时间戳等机制来检测并发冲突并解决。
并发事务引起的脏读、丢失修改、不可重复读、幻读等问题“读”是多个事务并发执⾏时,在读取数据⽅⾯可能碰到的状况。
先了解它们有助于理解各隔离级别的含义。
其中包括脏读、丢失修改、不可重复读和幻读。
脏读脏读⼜称⽆效数据的读出,是指在数据库访问中,事务T1将某⼀值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是⽆效的。
脏读就是指当⼀个事务正在访问数据,并且对数据进⾏了修改,⽽这种修改还没有提交(commit)到数据库中,这时,另外⼀个事务也访问这个数据,然后使⽤了这个数据。
因为这个数据是还没有提交的数据,那么另外⼀个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
丢失修改指在⼀个事务读取⼀个数据时,另外⼀个事务也访问了该数据,那么在第⼀个事务中修改了这个数据后,第⼆个事务也修改了这个数据。
这样第⼀个事务内的修改结果就被丢失,因此称为丢失修改。
例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失不可重复读不可重复读,是指在数据库访问中,⼀个事务范围内两个相同的查询却返回了不同数据。
这是由于查询时系统中其他事务修改的提交⽽引起的。
⽐如事务T1读取某⼀数据,事务T2读取并修改了该数据,T1为了对读取值进⾏检验⽽再次读取该数据,便得到了不同的结果。
⼀种更易理解的说法是:在⼀个事务内,多次读同⼀个数据。
在这个事务还没有结束时,另⼀个事务也访问该同⼀数据。
那么,在第⼀个事务的两次读数据之间。
由于第⼆个事务的修改,那么第⼀个事务读到的数据可能不⼀样,这样就发⽣了在⼀个事务内两次读到的数据是不⼀样的,因此称为不可重复读,即原始读取不可重复。
幻读幻读是指当事务不是独⽴执⾏时发⽣的⼀种现象,例如第⼀个事务对⼀个表中的数据进⾏了修改,⽐如这种修改涉及到表中的“全部数据⾏”。
同时,第⼆个事务也修改这个表中的数据,这种修改是向表中插⼊“⼀⾏新数据”。
并发事务可能导致的问题并发事务可能引起的事情:1.脏读:对于两个事务T1 和T2 , T1 读取了已经被T2 更新但还没有被提交的字段。
之后,若T2 进⾏回滚,T1读取的内容就是临时且⽆效的2.不可重复读:对于两个事务T1 和T2 , T1 读取了⼀个字段,然后T2 更新了该字段。
之后,T1再次读取同⼀个字段,值就不同了3.幻读:对于两个事务T1,T2,T1从表中都去了⼀个字段,然后T2在该表中插⼊了⼀些新的⾏,之后,如果T1再次读取同⼀个表,就会多出⼏⾏---------------------------------------------------------------------分割线-------------------------------------------------------------------------------------------------------------------------另外,在使⽤spring的注解式事务管理时,对事务的传播⾏为和隔离级别可能有点不知所措,下边就详细的介绍下以备⽅便查阅。
事物注解⽅式: @Transactional当标于类前时, 标⽰类中所有⽅法都进⾏事物处理 , 例⼦:@Transactional public class TestServiceBean implements TestService {}当类中某些⽅法不需要事物时:@Transactional public class TestServiceBean implements TestService { private TestDao dao; public void setDao(TestDao dao) { this.dao = dao; } @Transactional(propagation =Propagation.NOT_SUPPORTED) public ListgetAll() { return null; }事物传播⾏为介绍:@Transactional(propagation=Propagation.REQUIRED)如果有事务, 那么加⼊事务, 没有的话新建⼀个(默认情况下)@Transactional(propagation=Propagation.NOT_SUPPORTED)容器不为这个⽅法开启事务@Transactional(propagation=Propagation.REQUIRES_NEW)不管是否存在事务,都创建⼀个新的事务,原来的挂起,新的执⾏完毕,继续执⾏⽼的事务@Transactional(propagation=Propagation.MANDATORY)必须在⼀个已有的事务中执⾏,否则抛出异常@Transactional(propagation=Propagation.NEVER)必须在⼀个没有的事务中执⾏,否则抛出异常(与Propagation.MANDATORY相反)@Transactional(propagation=Propagation.SUPPORTS)如果其他bean调⽤这个⽅法,在其他bean中声明事务,那就⽤事务.如果其他bean没有声明事务,那就不⽤事务.事物超时设置:@Transactional(timeout=30) //默认是30秒事务隔离级别:@Transactional(isolation = Isolation.READ_UNCOMMITTED)读取未提交数据(会出现脏读, 不可重复读) 基本不使⽤@Transactional(isolation = Isolation.READ_COMMITTED)读取已提交数据(会出现不可重复读和幻读)@Transactional(isolation = Isolation.REPEATABLE_READ)可重复读(会出现幻读)@Transactional(isolation = Isolation.SERIALIZABLE)串⾏化MYSQL: 默认为REPEATABLE_READ级别SQLSERVER: 默认为READ_COMMITTED脏读 : ⼀个事务读取到另⼀事务未提交的更新数据不可重复读 : 在同⼀事务中, 多次读取同⼀数据返回的结果有所不同, 换句话说,后续读取可以读到另⼀事务已提交的更新数据. 相反, "可重复读"在同⼀事务中多次读取数据时, 能够保证所读数据⼀样, 也就是后续读取不能读到另⼀事务已提交的更新数据幻读 : ⼀个事务读到另⼀个事务已提交的insert数据@Transactional注解中常⽤参数说明参数名称功能描述readOnly该属性⽤于设置当前事务是否为只读事务,设置为true表⽰只读,false则表⽰可读写,默认值为false。
为什么需要并发事务处理?
1. 提高系统性能
并发事务处理能够让系统在同一时间处理多个事务,提高了系统资源的利用率和效率。
比如,在一个电子商务网站上,用户可以同时操作购物车、查看订单状态等,如果不使用并发事务处理,用户只能依次操作,大大降低了用户体验。
2. 保证数据一致性
在多用户同时访问系统的情况下,如果不采用并发事务处理,可能会出现数据不一致的情况。
比如,一个用户正在购买商品时,另一个用户同时在操作库存减少,如果不进行事务处理,可能会导致库存出现负数。
3. 避免数据丢失
并发事务处理可以防止数据丢失的情况发生。
在数据库系统中,如果多个事务同时进行写操作,并且不采用并发处理,可能会出现一个事务的修改被另一个事务覆盖,导致数据丢失的情况。
4. 提高系统的可靠性
通过并发事务处理,系统的操作更加稳定,不容易出现死锁、脏读等问题,保障了系统的可靠性。
这对于金融系统、医疗系统等对数据完整性要求较高的系统来说尤为重要。
5. 优化用户体验
并发事务处理可以让用户同时进行多个操作,不会出现等待时间过长的情况,提升了用户操作的效率和体验。
特别是在高并发情况下,保
证了系统的快速响应,为用户提供更好的服务。
综上所述,采用并发事务处理对于系统性能、数据一致性、数据安全、系统可靠性以及用户体验等方面都有重要意义,是现代信息系统中不
可或缺的重要技术之一。
只有通过并发事务处理,才能确保系统的稳
定运行和用户的良好体验。
事务处理中的并发冲突与冲突解决策略在现代信息时代,事务处理成为了重要的数据处理方式之一。
然而,随着数据量和处理需求的增加,事务处理中的并发冲突问题也愈发突出。
本文将重点探讨事务处理中的并发冲突及解决策略。
1. 并发冲突的原因并发冲突是指在多个事务同时进行的情况下,由于竞争资源或执行顺序的不同而导致的问题。
主要原因包括数据竞争、资源冲突和执行顺序冲突。
数据竞争是指多个事务对同一个数据进行读写操作,由于读写操作的执行顺序不同,导致最终结果出现差异。
资源冲突是指多个事务同时请求同一资源,例如数据库的表、文件等,造成资源争夺问题。
执行顺序冲突是指多个事务的执行顺序不同,导致最终结果与预期不符合。
2. 冲突解决策略为了解决事务处理中的并发冲突问题,需要采取一系列解决策略。
以下将介绍几种常见的冲突解决策略。
锁机制锁机制是最常见也是最基本的冲突解决策略之一。
它通过对资源进行加锁的方式来保证并发操作的一致性。
在并发读操作时,可以使用共享锁(读锁)来允许多个事务同时读取数据;而在并发写操作时,使用排他锁(写锁)来确保只有一个事务可以进行写操作。
通过合理的锁粒度和锁定策略,可以有效地避免并发冲突。
乐观并发控制乐观并发控制是一种基于版本控制的冲突解决策略。
它通过在每个数据项中添加版本号,并在更新操作时比较版本号来判断是否发生冲突。
当多个事务同时对同一数据进行读操作时,系统会记录下读操作时的版本号;而在事务提交时,系统会比较提交时的版本号与读操作时的版本号是否一致,如果不一致则说明发生了冲突,需要进行相应的处理。
时间戳排序时间戳排序是一种按照时间顺序调度事务执行的冲突解决策略。
每个事务在开始执行时会被分配一个唯一的时间戳,在执行时按照时间戳的顺序进行调度。
当多个事务请求同一资源时,系统会根据事务的时间戳来判断执行的先后顺序,从而避免冲突。
同时,时间戳还可以用于检测死锁和回滚操作,提高了事务处理的效率和可靠性。
3. 小结事务处理中的并发冲突是一个复杂而常见的问题。
事务处理中的并发冲突与冲突解决策略一、引言并发冲突是指在多个事务同时对相同的数据进行读写时可能发生的数据不一致的现象。
在计算机系统中,事务是指一组逻辑上有关联的操作,它们被当作一个整体来执行,要么全部成功完成,要么全部不影响数据库的一致性。
然而,并发处理会引发一些冲突,影响事务的正确执行。
本文将讨论事务处理中的并发冲突以及常见的冲突解决策略。
二、并发冲突的类型并发冲突主要分为读-写冲突、写-写冲突和写-读冲突。
读-写冲突指同时进行的事务中,一个事务读取了另一个事务正在修改的数据。
写-写冲突指同时进行的两个事务试图修改相同的数据,导致其中一个事务的修改被覆盖。
写-读冲突则是一个事务在修改数据时,另一个事务正在读取同一数据,可能读到既有修改又有未修改的数据。
三、冲突解决策略1. 串行化串行化是一种简单但效率较低的冲突解决策略,即对所有事务进行排序,依次执行,保证没有并发操作。
这样可以确保事务执行的正确性,但会大大降低系统的处理能力。
2. 锁机制锁机制是最常用的冲突解决策略之一,通过对数据的访问加锁,控制并发操作。
读锁和写锁是最常见的锁类型。
读锁可以允许多个事务同时读取数据,但不允许写操作。
而写锁则阻塞其他事务的读写操作,确保一次只有一个事务可以对数据进行操作。
锁机制可以有效解决并发冲突,但过度的加锁也可能导致性能下降和死锁等问题。
3. 时间戳时间戳是一种较为复杂但高效的冲突解决策略,通过为每个事务分配唯一的时间戳,并通过时间戳比较来判断读写操作的执行顺序。
时间戳策略可以避免死锁和饥饿等问题,提高并发性能。
4. 乐观并发控制乐观并发控制是一种基于冲突检测和冲突解决的策略,假设事务之间很少发生冲突,因此先进行操作,然后在提交时检查是否有冲突发生,如果发生冲突,则采取相应的解决方案。
乐观并发控制采用无锁的方式,减少了锁带来的开销,并能提供较好的并发性能。
四、实际应用场景并发冲突和冲突解决策略在各种实际应用场景中都有重要的作用。
并发事务的调度和控制方案随着信息系统的发展和应用,对于事务处理的需求越来越多。
并发事务是指在同一时间段内同时进行的多个事务操作。
然而,由于并发事务的特殊性,可能会引发各种问题,如数据不一致、死锁等。
因此,设计一个高效可靠的并发事务调度和控制方案显得尤为重要。
一、并发事务的调度事务调度是指确定事务之间执行顺序的过程。
合理的事务调度可以提高系统的并发能力和性能,同时保证数据的一致性。
1.1 短事务优先调度短事务优先调度是指优先执行执行时间短的事务。
这种调度方式适用于事务相对简单,执行时间短暂的情况,可以减少系统的响应时间和事务的等待时间。
1.2 长事务优先调度长事务优先调度是指优先执行执行时间长的事务。
这种调度方式适用于事务执行时间较长且执行频率较低的情况,可以避免长事务一直等待的情况,提高长事务的优先级。
1.3 先来先服务调度先来先服务调度是指按照事务到达的顺序进行调度,保证事务的公平性。
这种调度方式适用于对事务响应时间要求不高,系统并发能力要求较低的情况。
1.4 最短事务优先调度最短事务优先调度是指按照事务需要的资源和执行时间来确定调度顺序。
这种调度方式可以有效地利用系统资源,提高系统的并发性能。
二、并发事务的控制事务控制是指对并发事务进行同步和协调的过程。
恰当的事务控制可以避免冲突、死锁等并发问题的发生。
2.1 锁定机制锁定机制是指对资源进行加锁以实现事务的隔离和互斥。
常见的锁定方式包括共享锁和排他锁。
共享锁允许多个事务同时读取同一资源,而排他锁则只允许一个事务写入资源。
通过合理使用锁定机制,可以保证事务的一致性和数据的完整性。
2.2 时间戳机制时间戳机制是指为每个事务分配一个唯一的时间戳,并按照时间戳的顺序进行调度。
通过时间戳机制,可以避免死锁的发生,保证并发事务的顺序性和一致性。
2.3 两阶段提交协议两阶段提交协议是一种用于分布式事务的协议,包括准备阶段和提交阶段。
在准备阶段,所有参与者向协调者发送准备请求,并等待协调者的决策。
本文结合Hibernate以及JPA标准,对J2EE当前持久层设计所遇到的几个问题进行总结:事务并发访问控制策略当前J2EE项目中,面临的一个共同问题就是如果控制事务的并发访问,虽然有些持久层框架已经为我们做了很多工作,但是理解原理,对于我们开发来说还是很有用处的。
事务并发访问主要可以分为两类,分别是同一个系统事务和跨事务访问的并发访问控制,其中同一个系统事务可以采取乐观锁以及悲观锁策略,而跨多个系统事务时则需要乐观离线锁和悲观离线锁。
在讨论这四种并发访问控制策略之前,先需要明确一下数据库事务隔离级别的问题,ANSI标准规定了四个数据库事务隔离级别,它们分别是:读取未提交(Read Uncommitted)这是最低的事务隔离级别,读事务不会阻塞读事务和写事务,写事务也不会阻塞读事务,但是会阻塞写事务。
这样造成的一个结果就是当一个写事务没有提交的时候,读事务照样可以读取,那么造成了脏读的现象。
读取已提交(Read Committed)采用此种隔离界别的时候,写事务就会阻塞读事务和写事务,但是读事务不会阻塞读事务和写事务,这样因为写事务会阻塞读取事务,那么从而读取事务就不能读到脏数据,但是因为读事务不会阻塞其它的事务,这样还是会造成不可重复读的问题。
可重复读(Repeatable Read)采用此种隔离级别,读事务会阻塞写事务,但是读事务不会阻塞读事务,但是写事务会阻塞写事务和读事务。
因为读事务阻塞了写事务,这样以来就不会造成不可重复读的问题,但是这样还是不能避免幻影读问题。
序列化(serializable)此种隔离级别是最严格的隔离级别,如果设置成这个级别,那么就不会出现以上所有的问题(脏读,不可重复读,幻影读)。
但是这样以来会极大的影响到我们系统的性能,因此我们应该避免设置成为这种隔离级别,相反的,我们应该采用较低的隔离界别,然后再采用并发控制策略来进行事务的并发访问控制)。
其实我们也可以把事务隔离级别设置为serializable,这样就不需要采用并发控制策略了,数据库就会为我们做好一切并发控制,但是这样以来会严重影响我们系统的伸缩性和性能,所以在实践中,我们一般采用读取已提交或者更低的事务隔离级别,配合各种并发访问控制策略来达到并发事务控制的目的。
下面总结一下常用的控制策略:1 乐观锁乐观锁是在同一个数据库事务中我们常采取的策略,因为它能使得我们的系统保持高的性能的情况下,提高很好的并发访问控制。
乐观锁,顾名思义就是保持一种乐观的态度,我们认为系统中的事务并发更新不会很频繁,即使冲突了也没事,大不了重新再来一次。
它的基本思想就是每次提交一个事务更新时,我们想看看要修改的东西从上次读取以后有没有被其它事务修改过,如果修改过,那么更新就会失败,。
最后我们需要明确一个问题,因为乐观锁其实并不会锁定任何记录,所以如果我们数据库的事务隔离级别设置为读取已提交或者更低的隔离界别,那么是不能避免不可重复读问题的(因为此时读事务不会阻塞其它事务),所以采用乐观锁的时候,系统应该要容许不可重复读问题的出现。
了解了乐观锁的概念以后,那么当前我们系统中又是如何来使用这种策略的呢?一般可以采用以下三种方法:版本(Version)字段:在我们的实体中增加一个版本控制字段,每次事务更新后就将版本字段的值加1.时间戳(timestamps):采取这种策略后,当每次要提交更新的时候就会将系统当前时间和实体加载时的时间进行比较,如果不一致,那么就报告乐观锁失败,从而回滚事务或者重新尝试提交。
采用时间戳有一些不足,比如在集群环境下,每个节点的时间同步也许会成问题,并且如果并发事务间隔时间小于当前平台最小的时钟单位,那么就会发生覆盖前一个事务结果的问题。
因此一般采用版本字段比较好。
基于所有属性进行检测:采用这种策略的时候,需要比较每个字段在读取以后有没有被修改过,所以这种策略实现起来比较麻烦,要求对每个属性都进行比较,如果采用hiernate的话,因为Hibernate在一级缓存中可以进行脏检测,那么可以判断哪些字段被修改过,从而动态的生成sql语句进行更新。
下面再总结一下如何在JDBC和Hibernate中使用乐观锁: JDBC中使用乐观锁:如果我们采用JDBC来实现持久层的话,那么就可以采用以上将的三种支持乐观锁的策略,在实体中增加一个version字段或者一个Date字段,也可以采用基于所有属性的策略,下面就采用version字段来做一演示:假如系统中有一个Account的实体类,我们在Account中多加一个version字段,那么我们JDBC Sql语句将如下写:getVersion = Select a.version....from Account as a where (where condition..)Update Account set version = version+1.....(another field) where version = getVersion ...(another contidition)这样以来我们就可以通过更新结果的行数来进行判断,如果更新结果的行数为0,那么说明实体从加载以来已经被其它事务更改了,所以就抛出自定义的乐观锁定异常(或者也可以采用Spring封装的异常体系)。
具体实例如下:Java代码1........2.int rowsUpdated = statement.executeUpdate(sql);3.If(rowsUpdated= =0){4.throws new OptimisticLockingFailureException();5.}6.........在使用JDBC API的情况下,我们需要在每个update语句中,都要进行版本字段的更新以及判断,因此如果稍不小心就会出现版本字段没有更新的问题,相反当前的 ORM框架却为我们做好了一切,我们仅仅需要做的就是在每个实体中都增加version或者是Date字段。
Hibernate中使用乐观锁:如果我们采用Hibernate做为持久层的框架,那么实现乐观锁将变得非常容易,因为框架会帮我们生成相应的sql语句,不仅减少了开发人员的负担,而且不容易出错。
下面同样采用version字段的方式来总结一下:同样假如系统中有一个Account的实体类,我们在Account中多加一个version 字段,Java代码1.public class Account{2.Long id ;3........4.@Version//也可以采用XML文件进行配置 <class >5.Int version6........7.}这样以来每次我们提交事务时,hibernate内部会生成相应的SQL语句将版本字段加1,并且进行相应的版本检测,如果检测到并发乐观锁定异常,那么就抛出StaleObjectStateException.2 悲观锁所谓悲观锁,顾名思义就是采用一种悲观的态度来对待事务并发问题,我们认为系统中的并发更新会非常频繁,并且事务失败了以后重来的开销很大,这样以来,我们就需要采用真正意义上的锁来进行实现。
悲观锁的基本思想就是每次一个事务读取某一条记录后,就会把这条记录锁住,这样其它的事务要想更新,必须等以前的事务提交或者回滚解除锁。
最后我们还是需要明确一个问题,假如我们数据库事务的隔离级别设置为读取已提交或者更低,那么通过悲观锁,我们控制了不可重复读的问题,但是不能避免幻影读的问题(因为要想避免我们就需要设置数据库隔离级别为Serializable,而一般情况下我们都会采取读取已提交或者更低隔离级别,并配合乐观或者悲观锁来实现并发控制,所以幻影读问题是不能避免的,如果想避免幻影读问题,那么你只能依靠数据库的serializable隔离级别(幸运的是幻影读问题一般情况下不严重)。
下面就分别以JDBC和Hibernate来总结一下:JDBC中使用悲观锁:在JDBC中使用悲观锁,需要使用select for update 语句,假如我们系统中有一个Account的类,我们可以采用如下的方式来进行:Select * from Account where ...(where condition).. for update.当使用了for update语句后,每次在读取或者加载一条记录的时候,都会锁住被加载的记录,那么当其他事务如果要更新或者是加载此条记录就会因为不能获得锁而阻塞,这样就避免了不可重复读以及脏读的问题,但是其他事务还是可以插入和删除记录,这样也许同一个事务中的两次读取会得到不同的结果集,但是这不是悲观锁锁造成的问题,这是我们数据库隔离级别所造成的问题。
最后还需要注意的一点就是每个冲突的事务中(与该查询语句相关联的事务),我们必须使用select for update 语句来进行数据库的访问,如果一些事务没有使用select for update语句,那么就会很容易造成错误,这也是采用JDBC进行悲观控制的缺点。
Hibernate中使用悲观锁:相比于JDBC使用悲观锁来说,在Hibernate 中使用悲观锁将会容易很多,因为Hibernate有API让我们来调用,从而避免直接写SQL语句。
下面就Hibernate使用悲观锁做一总结:首先先要明确一下Hibernate中支持悲观锁的两种模式LockMode.UPGRADE以LockMode.UPGRADE_NO_WAIT.(PS:在JPA中,对应的锁模式是LockModeType.Read,这与Hibernate是不一样的呵呵)假如我们系统中有一个Account的类,那么具体的操作可以像这样:Java代码1........2.session.lock(account, LockMode.UPGRADE);3.......4.或者也可以采用如下方式来加载对象:5.session.get(Account.class,identity,LockMode.UPGRADE).这样以来当加载对象时,hibernate内部会生成相应的select for update语句来加载对象,从而锁定对应的记录,避免其它事务并发更新。