mysql悲観ロックの総括と実践


最近、データベースの悲観的なロックと楽観的なロックを学び、自分の理解とネット上の参考資料に基づいて以下のようにまとめました.
 
悲観ロックの紹介(百科):
悲観的なロックは、その名のように、本システムの現在の他のトランザクション、および外部システムからのトランザクションを含む外部のデータの変更に保守的な態度を持っているため、データ処理中にデータがロックされた状態になることを意味します.悲観的なロックの実現は、データベースが提供するロックメカニズムに依存することが多い(データベース層が提供するロックメカニズムだけがデータアクセスの排他性を本当に保証することができる.そうしないと、本システムでロックメカニズムを実現しても、外部システムがデータを修正しないことは保証できない).
 
使用シーンの例:MySQL InnoDBを例に
商品goodsテーブルにはstatusというフィールドがあります.statusは1で商品が注文されていないことを表し、statusは2で商品が注文されたことを表しています.では、ある商品を注文するときは、その商品statusが1であることを確認しなければなりません.商品のidを1とする.
 
1ロックを使用しない場合、操作方法は次のとおりです.
//1.商品情報の検索
select status from t_goods where id=1;
//2.商品情報に基づいて注文を生成
insert into t_orders (id,goods_id) values (null,1);
//3.商品を修正する
update t_goods set status=2;
 
上記のシーンでは、高同時アクセスの場合に問題が発生する可能性があります.
前述したように、goods statusが1の場合にのみこの商品を注文することができ、上記の最初の操作で検索された商品statusは1です.しかし、第3のステップUpdate操作を実行すると、他の人が先に商品の注文にgoods statusを2に変更した可能性がありますが、データが変更されたことを知らないので、同じ商品が2回注文され、データが一致しない可能性があります.だからこの方法は安全ではありません.
 
2悲観的ロックを使用して実現:
上のシーンでは、商品情報がクエリーから修正に至るまで、注文を処理する過程があり、悲観的なロックを使用する原理は、goods情報をクエリーした後、現在のデータをロックし、修正が完了するまでロックを解除することです.では、この過程でgoodsがロックされているため、第三者が修正することはありません.
 
注意:悲観的なロックを使用するには、mysqlデータベースの自動コミットプロパティを閉じる必要があります.MySQLはautocommitモードをデフォルトで使用しているため、つまり、更新操作を実行すると、MySQLはすぐに結果をコミットします.
 
コマンドを使用してMySQLを非autocommitモードに設定できます.
set autocommit=0;
 
Autocommitの設定が完了すると、通常のビジネスを実行できます.具体的には以下の通りです.
//0.トランザクションの開始
begin;/begin work;/start transaction; (三者は一を選べばいい)
//1.商品情報の検索
select status from t_goods where id=1 for update;
//2.商品情報に基づいて注文を生成
insert into t_orders (id,goods_id) values (null,1);
//3.商品を修正する
update t_goods set status=2;
//4.トランザクションのコミット
commit;/commit work;
 
注意:上のbegin/commitはトランザクションの開始と終了です.前のステップでmysqlのautocommitを閉じたので、トランザクションのコミットを手動で制御する必要があります.ここでは表を細かくしません.
 
最初のステップでは、クエリー操作を行いました.select status from t_goods where id=1 for update;
通常のクエリーとは異なり、select...for updateを使用することで、データベースを介して悲観的なロックを実現します.ここでt_goodsテーブルでは、idが1のデータがロックされており、他のトランザクションは今回のトランザクションのコミットを待ってから実行する必要があります.これにより、現在のデータが他のトランザクションによって変更されないことを保証できます.
 
注:注意が必要なのは、事務ではSELECTしかありません...FOR UPDATEまたはLOCK IN SHARE MODEが同じデータの場合、他のトランザクションが終了してから実行されます.一般的にSELECT...この影響を受けません.上記の例ではselect status from t_を実行するとgoods where id=1 for update;後.別のトランザクションでselect status from t_を再実行するとgoods where id=1 for update;2番目のトランザクションは、1番目のトランザクションのコミットを待機します.2番目のクエリはブロックされていますが、2番目のトランザクションでselect status from t_goods where id=1;これにより、最初のトランザクションの影響を受けずにデータが正常にクエリーされます.
 
补充:MySQL select…for updateのRow LockとTable Lock
前述したように、select...for updateを使用するとデータがロックされますが、いくつかのロックのレベルに注意する必要があります.MySQL InnoDBのデフォルトRow-Livel Lockです.したがって、プライマリ・キーを「明確に」指定するだけで、MySQLはRow lock(選択されたデータのみをロック)を実行します.そうしないと、MySQLはTable Lock(データ・フォーム全体をロック)を実行します.
 
例:
データベーステーブルt_goodsはid、status、nameの3つのフィールドを含み、idはプライマリ・キーであり、データベースには以下のように記録されている.
mysql> select * from t_goods;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  1 |      1 |    |
|  2 |      1 |    |
+----+--------+------+
2 rows in set

mysql>

注意:データベース・ロックをテストするには、2つのconsoleを使用して異なるトランザクション・オペレーションをシミュレートし、それぞれconsole 1、console 2で表します. 
 
例1:(プライマリ・キーを明確に指定し、このデータがある、row lock)
console 1:結果がクエリーされますが、データがロックされています.
mysql> select * from t_goods where id=1 for update;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  1 |      1 |    |
+----+--------+------+
1 row in set

mysql>

console 2:クエリーがブロックされています
mysql> select * from t_goods where id=1 for update;

console 2:console 1が長い間コミットされていない場合は、エラーが発生します.
mysql> select * from t_goods where id=1 for update;
ERROR 1205 : Lock wait timeout exceeded; try restarting transaction

 
例2:(プライマリ・キーを明確に指定し、このデータがない場合はlockなし)
console 1:クエリー結果が空です
mysql> select * from t_goods where id=3 for update;
Empty set

console 2:クエリーの結果が空で、クエリーにブロックがなく、console 1がデータにロックを実行していないことを示します.
mysql> select * from t_goods where id=3 for update;
Empty set

 
例3:(プライマリ・キーなし、table lock)
console 1:name=アイテムのデータを照会し、正常に照会する
mysql> select * from t_goods where name='  ' for update;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  1 |      1 |    |
+----+--------+------+
1 row in set

mysql>

console 2:name=装備のデータをクエリーし、クエリーがブロックされ、console 1がテーブルをロックしたことを説明します.
mysql> select * from t_goods where name='  ' for update;

console 2:console 1が長時間コミットされていない場合、クエリは空に戻ります.
mysql> select * from t_goods where name='  ' for update;
Query OK, -1 rows affected

 
例4:(主キー不明、table lock)
console 1:クエリーは正常です
mysql> begin;
Query OK, 0 rows affected

mysql> select * from t_goods where id>0 for update;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  1 |      1 |    |
|  2 |      1 |    |
+----+--------+------+
2 rows in set

mysql>

console 2:クエリーがブロックされ、console 1がテーブルをロックしたことを示します.
mysql> select * from t_goods where id>1 for update;

 
例5:(主キー不明、table lock)
console1:
mysql> begin;
Query OK, 0 rows affected

mysql> select * from t_goods where id<>1 for update;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  2 |      1 |    |
+----+--------+------+
1 row in set

mysql>

console 2:クエリーがブロックされ、console 1がテーブルをロックしたことを示します.
mysql> select * from t_goods where id<>2 for update;

console 1:トランザクションのコミット
mysql> commit;
Query OK, 0 rows affected

console 2:console 1トランザクションがコミットされると、console 2クエリの結果は正常です.
mysql> select * from t_goods where id<>2 for update;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  1 |      1 |    |
+----+--------+------+
1 row in set

mysql>

 
以上、データベースのプライマリ・キーがMySQLロック・レベルに与える影響の例について説明したが、プライマリ・キーのほかにインデックスを使用してもデータベースのロック・レベルに影響を与えることに注意してください.
 
例:
t_を修正しますgoodsテーブル、statusフィールドにインデックスを作成
idが2のデータを変更するstatusは2で、この時表のデータは:
mysql> select * from t_goods;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  1 |      1 |    |
|  2 |      2 |    |
+----+--------+------+
2 rows in set

mysql>

 
例6:(インデックスを明確に指定し、このデータがある、row lock)
console1:
mysql> select * from t_goods where status=1 for update;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  1 |      1 |    |
+----+--------+------+
1 row in set

mysql>

console 2:status=1のデータをクエリーするとブロックされ、タイムアウト後に空に戻り、console 1によってデータがロックされたことを示します
mysql> select * from t_goods where status=1 for update;
Query OK, -1 rows affected

console 2:status=2のデータをクエリーして、正常にクエリーすることができて、console 1が行だけをロックして、表をロックしていないことを説明します
mysql> select * from t_goods where status=2 for update;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  2 |      2 |    |
+----+--------+------+
1 row in set

mysql>

 
例7:(インデックスを明確に指定し、このデータがない場合はlockなし)
console 1:status=3のデータを問合せ、空のデータを返す
mysql> select * from t_goods where status=3 for update;
Empty set

console 2:status=3のデータをクエリーし、空のデータを返します.
mysql> select * from t_goods where status=3 for update;
Empty set

 
 
以上、私のデータベースの悲観的なロックに対する理解と総括について、悪いところがあればレンガを撮ることを歓迎して、次はデータベースの楽観的なロックの総括と実践を持ってきます
 
参考資料:
MySQLトランザクションとロックコマンド:http://www.docin.com/p-16805970.html
悲観ロック:http://www.cnblogs.com/chenwenbiao/archive/2012/06/06/2537508.html