MySQL データベースの最適化 (3) - MySQL の悲観的ロックと楽観的ロック (同時実行制御)

黄舟
リリース: 2017-03-30 15:34:54
オリジナル
6401 人が閲覧しました

1. 悲観的ロック
1. 排他的ロック。トランザクションがデータを操作する場合、データのこの部分は操作が完了するまでロックされ、その後ロックが解除されます。 。これにより、他のプロセスがテーブル内のデータを読み取ったり変更したりすることができなくなります。

2. 実装: ほとんどの場合、データベースのロックメカニズムに依存します

一般に、select ...for update は、select * など、選択されたデータをロックするために使用されます。 name="Max" のアカウントから 更新用、 この SQL このステートメントは、アカウント テーブル内の取得条件 (name="Max") を満たすすべてのレコードをロックします。このトランザクションがコミットされる前は (トランザクション中のロックはトランザクションがコミットされると解放されます)、外部はこれらのレコードを変更できません。

2. 楽観的ロック
1. 誰かがあなたより先に更新した場合、あなたの更新は拒否され、ユーザーは再操作できます。

2. 実装: ほとんどの実装は、データ バージョン (Version) 記録メカニズム

具体的には、これは、 テーブルにバージョン番号またはタイムスタンプ フィールド を追加することで実現できます データを読み込む際には、データが更新されるたびにバージョンフィールドの値も一緒に読み出されます。バージョン値は 1 ずつ増加します。更新を送信するとき、現在のバージョン情報のサイズと、データベース テーブルの現在のバージョン番号が初めて取得したバージョン値と等しい場合は、その値が決定されます。それ以外の場合は、期限切れのデータとみなされ、更新が拒否され、ユーザーは再操作できます。 3. ORM フレームワークでの悲観的ロックと楽観的ロックの適用 たとえば、楽観的ロックのバージョン番号フィールドは純粋にデータベース操作のためのものであり、楽観的ロックを自分で実装する必要があります。つまり、バージョン番号またはタイムスタンプ フィールドのメンテナンスは必要です。更新はプログラム自体によって維持され、自己インクリメントとサイズの決定により、コード判断によって更新が実装されるかどうかが決まります。データベースは、同時実行制御に関して楽観的と悲観的な 2 つのアイデアを提供します。

一般的な 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)!

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!