1. 비관적 잠금
1. 배타적 잠금은 데이터에 대해 트랜잭션이 실행될 때 해당 작업이 완료될 때까지 데이터의 이 부분을 잠긴 다음 그런 다음 잠금이 해제되면 다른 트랜잭션 작업만 데이터의 이 부분에서 작동할 수 있습니다. 이렇게 하면 다른 프로세스가 테이블의 데이터를 읽거나 수정하는 것을 방지할 수 있습니다.
2. 구현: 대부분의 경우 데이터베이스의 잠금 메커니즘에 의존합니다.
일반적으로 업데이트를 위해 select ...를 사용하면 선택한 데이터가 잠깁니다. 예를 들어 select * from account where name="Max" 업데이트의 경우 이 SQL 이 문은 검색 조건(name="Max")을 충족하는 계정 테이블의 모든 레코드를 잠급니다. 이 트랜잭션이 커밋되기 전에(트랜잭션이 커밋되면 트랜잭션 중 잠금이 해제됨) 외부 세계에서 이러한 레코드를 수정할 수 없습니다.
2. 낙관적 잠금
1. 누군가가 먼저 업데이트하면 사용자가 다시 업데이트를 거부할 수 있습니다. -작동하다.
2. 구현: 대부분의 구현은 데이터 버전(Version) 기록 메커니즘을 기반으로 합니다.
구체적인 내용 이는 버전 번호 또는 타임스탬프 필드를 테이블에 추가하면 가능합니다. 데이터를 읽을 때 버전 필드의 값은 데이터가 업데이트될 때마다 함께 읽혀집니다. 값이 1씩 증가합니다. 업데이트를 제출할 때 현재 버전 정보의 크기와 처음으로 가져온 버전 값을 결정합니다. 데이터베이스 테이블의 현재 버전 번호가 처음으로 가져온 버전 값과 같을 경우 그렇지 않으면 만료된 데이터로 간주되어 업데이트가 거부되고 사용자가 다시 작동할 수 있습니다.
3. ORM 프레임워크에서 비관적 잠금과 낙관적 잠금 적용
일반적으로 비관적 잠금과 낙관적 잠금은 SQL 문 설정, 데이터 설계 및 코드를 통해 구현되어야 합니다. 예를 들어 낙관적 잠금의 버전 번호 필드는 순전히 데이터베이스 작업을 위한 것이므로 다음을 수행해야 합니다. 간단히 말해서 잠금이란 버전 번호나 타임스탬프 필드가 프로그램 자체에 의해 유지되고 자체 증가, 크기 결정, 업데이트 여부가 모두 코드 판단을 통해 구현된다는 의미입니다. 데이터베이스는 동시성 제어에 대해 낙관적 및 비관적이라는 두 가지 아이디어를 제공합니다.
일반적인 Java 지속성 프레임워크의 경우 데이터베이스의 이 메커니즘에는 자체 구현이 있습니다. Hibernate를 예로 들어 ORM 프레임워크 애플리케이션을 요약합니다. 잠금 및 낙관적 잠금
1. Hibernate의 비관적 잠금:
데이터베이스 기반 잠금 메커니즘 구현. 다음 쿼리 문:
String hqlStr ="from TUser as user where user.name=Max"; Query query = session.createQuery(hqlStr); query.setLockMode("user",LockMode.UPGRADE); //加锁 List userList = query.list();//执行查询,获取数据
런타임 동안 Hibernate에 의해 생성된 SQL 문을 살펴보세요.
select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex from t_user tuser0_ where (tuser0_.name='Erica' ) for update
这里Hibernate通过使用数据库的for update子句实现了悲观锁机制。对返回的所有user记录进行加锁。
2、Hibernate的加锁模式有:
Ø LockMode.NONE : 无锁机制。
Ø LockMode.WRITE :Hibernate在写操作(Insert和Update)时会自动获取写锁。
Ø LockMode.READ : Hibernate在读取记录的时候会自动获取。
这三种锁机制一般由Hibernate内部使用,如Hibernate为了保证Update过程中对象不会被外界修改,会在save方法实现中自动为目标对象加上WRITE锁。
Ø LockMode.UPGRADE :利用数据库的for update子句加锁。
Ø LockMode. UPGRADE_NOWAIT :Oracle的特定实现,利用Oracle的for update nowait子句实现加锁。
注意,只有在查询开始之前(也就是Hiberate 生成SQL 之前)设定加锁,才会真正通过数据库的锁机制进行加锁处理,否则,数据已经通过不包含for update子句的Select
SQL加载进来,所谓数据库加锁也就无从谈起。
3、Hibernate的乐观锁
Hibernate 在其数据访问引擎中内置了乐观锁实现。如果不用考虑外部系统对数据库的更新操作,利用Hibernate提供的透明化乐观锁实现,将大大提升我们的生产力。Hibernate中可以通过class描述符的optimistic-lock属性结合version描述符指定。具体实现方式如下:
现在,我们为之前示例中的TUser加上乐观锁机制。
实现一、 配置optimistic-lock属性:
<hibernate-mapping> <class name="org.hibernate.sample.TUser" table="t_user" dynamic-update="true" dynamic-insert="true" optimistic-lock="version"> …… </class> </hibernate-mapping>
optimistic-lock属性有如下可选取值:
Ø none:无乐观锁
Ø version:通过版本机制实现乐观锁
Ø dirty:通过检查发生变动过的属性实现乐观锁
Ø all:通过检查所有属性实现乐观锁
通过version实现的乐观锁机制是Hibernate官方推荐的乐观锁实现,同时也是Hibernate中,目前唯一在数据对象脱离Session发生修改的情况下依然有效的锁机制。因此,一般情况下,我们都选择version方式作为Hibernate乐观锁实现机制。
实现二、添加一个Version属性描述符
<hibernate-mapping> <class name="org.hibernate.sample.TUser" table="t_user" dynamic-update="true" dynamic-insert="true" optimistic-lock="version"> <id name="id" column="id" type="java.lang.Integer"> <generator class="native"/> </id> <version column="version" name="version" type="java.lang.Integer"/> …… </class> </hibernate-mapping>
注意version 节点必须出现在ID 节点之后。这里声明了一个version属性,用于存放用户的版本信息,保存在TUser表的version字段中。
测试:
此时如果我们尝试编写一段代码,更新TUser表中记录数据,如:
Criteria criteria = session.createCriteria(TUser.class); criteria.add(Expression.eq("name","Max")); List userList = criteria.list(); TUser user =(TUser)userList.get(0); Transaction tx = session.beginTransaction(); user.setUserType(1); //更新UserType字段 tx.commit();
每次对TUser进行更新的时候,我们可以发现,数据库中的version都在递增。而如果我们尝试在tx.commit 之前,启动另外一个Session,对名为Max的用户进行操作,下面模拟并发更新时的情况:
Session session= getSession(); Criteria criteria = session.createCriteria(TUser.class); criteria.add(Expression.eq("name","Max")); Session session2 = getSession(); Criteria criteria2 = session2.createCriteria(TUser.class); criteria2.add(Expression.eq("name","Max")); List userList = criteria.list(); List userList2 = criteria2.list();TUser user =(TUser)userList.get(0); TUser user2 =(TUser)userList2.get(0); Transaction tx = session.beginTransaction(); Transaction tx2 = session2.beginTransaction(); user2.setUserType(99); tx2.commit(); user.setUserType(1); tx.commit();
执行并发更新的代码,在tx.commit()处抛出StaleObjectStateException异常,并指出版本检查失败,当前事务正在试图提交一个过期数据。通过捕捉这个异常,我们就可以在乐观锁校验失败时进行相应处理。
这就是hibernate实现悲观锁和乐观锁的主要方式。
四、总结
悲观锁相对比较谨慎,设想现实情况应该很容易就发生冲突,所以我还是独占数据资源吧。
乐观锁就想得开而且非常聪明,应该是不会有什么冲突的,我对表使用一个时间戳或者版本号,每次读、更新操作都对这个字段进行比对,如果在我之前已经有人对数据进行更新了,那就让它更新,大不了我再读一次或者再更新一次。
乐观锁的管理跟SVN管理代码版本的原理很像,如果在我提交代码之前用本地代码的版本号与服务器做对比,如果本地版本号小于服务器上最新版本号,则提交失败,产生冲突代码,让用户决定选择哪个版本继续使用。
在实际生产环境里边,如果并发量不大且不允许脏读,可以使用悲观锁;但如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,所以我们就要选择乐观锁定的方法 另外,Mysql在处理并发访问数据上,还有添加读锁(共享锁)、写锁(排它锁),控制锁粒度【表锁(table
lock)、行级锁(row lock)】等实现,有兴趣可以继续研究。
以上就是MySQL数据库优化(三)—MySQL悲观锁和乐观锁(并发控制)的内容,更多相关内容请关注PHP中文网(www.php.cn)!