トランザクションとロック

Menu Menu

データベースへのアクセスの一つのまとまりをトランザクションという。トランザクションの個々の動作に他のトランザクションの動作が混ざると、トランザクションが期待する動作にならないことがある。


トランザクション

個々の動作は、SQLでは、SQL文ということになる。複数のSQL文の集まりがトランザクションを構成する。

    start transaction;

で始めて、

    commit;

または、

    rollback;

で終わる。rollback はトランザクションをなかったことにする。

mysql は、 default では、autocommit mode になっていて、一つの SQL 文の終了後、自動的に commit する。

start transaction から commit までは、一つのトランザクションとして扱われる。


仕様と、その記述

トランザクションが守らなければならない仕様とは何か?

(1) update account set login_count = login_count + 1; で、これをn回繰り返したら、login_count は n 増加する。

(2) 銀行のアカウントの残金は、入金-出金

これらは、プログラミング言語で記述することができる。C や Java ならば assert で記述する。複数のトランザクションにまたがる記述を、そのまま記述することはできない。

そこで pre condition と post condition を使う。

P1 pre condition トランザクションを実行する前に満たしていること
T2 トランザクション
P2 post condition トランザクションを実行後に満たしていること

P2 は T1 を使って P1 から計算できる。逆に、P1 を P2 から計算することもできる。

   P1 T2 P2 T3 P3 .... TN PN

のようにトランザクションをつなげて、PN が仕様を満たしていれば良い。これがすべてのトランザクションの組み合わせに対して成立する必要がある。


整列可能性

トランザクションは一般的には一つ一つ実行されるわけではない。いくつかは平行に実行される。並行実行された複数のトランザクションが、一つ一つ順次実行した場合と同じ実行になる時に、トランザクションは整列可能だという。

整列可能なトランザクションは仕様を満たしているかどうかを調べることができる。そして、その仕様を満たすようにトランザクションをプログラムすることができる。

整列可能でないと、pre condition / post condition のような方法では仕様を保証することはできない。


整列可能でないトランザクションの例

read / write の個々の順序がトランザクションの順序を決める。前の図で、 User A のトランザクションTA と User B のトランザクションTBは、個々の動作 a1,a2,b1,b2 で順序付けられている。

    s1,s2があるので、b1 → a1 したがって、TB → TA
    s2,s3があるので、a1 → b1 したがって、TA → TB

TB → TA, TA → TB があるので、TA, TB には順序が付けられない。したがって整列可能ではない。

整列可能かどうかは、順序付けられた動作が循環しているかどうかを調べれば良い。これは有向グラフの輪を探す問題になる。(そのようなアルゴリズムを実装せよ)

相互に順序付けられてないトランザクションは勝手な順序を決めて良い。それは動作に影響しない。例えば、お互いに関係ないレコードにアクセスしている場合がそれに相当する。レコード間は関数従属性で繋がっていることがあるので、それを考慮する必要がある。


整列可能性の実現

このような順序のループを防いでやれば、トランザクションは整列可能になり仕様を満たすようにすることができる。仕様を満たすことを整合性という。

一番簡単に整合性を保証するには、トランザクションを受付順に並べて一つ一つ実行すれば良い。Unix の select/accept は、複数の stream からの入力を一つ一つ並べて処理することを可能にする。

この方法ではお互いに関係ないトランザクションも逐次的に実行されてしまうので性能が落ちる場合がある。そこで関係するデータに着目して、そこへのアクセスを専有する機構を考える。これは lock と呼ばれる。

ある資源を lock を取得すると、それより後に、その資源を lock するトランザクションは、最初のlock が解放するまで待たされる。これにより実行順序を保証することができる。


Perl を使ったロック

Perl には flock がある。

flock test を使って、飛行機とホテルを同時にとるトランザクションを考える。片方だけとれても無意味である。


デッドロック

前の Perl script の飛行機とホテルの順序を入れ替えたものを複数同時に走らせて、デッドロックさせてみる。

デッドロックとはお互いに資源を取り合って、複数のトランザクションどうしが待ち合って動作が止まってしまうことである。

デッドロックはトランザクションの待ち合わせの順序が輪を作ることで起きる。

したがって複数の資源を lock する時には順序を前もって決めてやるとデッドロックは起きない。(起きないことを証明せよ)

デッドロックの解消にタイムアウトを用いることもできる。タイムアウトしたトランザクションは roll back してもう一度実行する。この繰り返しは永遠に続く可能性がある。これをライブロックという。


問題0

dead lock の問題

ロックの粒度

ロックの対象となる資源は複数まとめても良い。

  レコード(タプル)単位のロック  テーブル単位のロック  動作単位のロック

など、さまざまなロックがある。テーブルは非常に大量のレコードを持っているのでロックの粒度が大きいという。

粒度が大きいほど影響されるトランザクションの数は多くなると予想される。


MySQLのロックとトランザクション

MySQL は順序付きのテーブルロックである。

    start transaction
    commit 
    roll back

start transaction で始めて、roll back で、insert したレコードが変わらないことを確認せよ。


問題1

MySQLのトランザクション

問題2

MySQLのロックとトランザクションの関係を調べる。

MySQLのトランザクションの関係


select for update

lock table は、あまり薦められてなくて、

   select .... for update;

のを使うのが一般的。こうすると、write lock がかかる。

     start transacation

   select .... for update;
     update ...
     commit

transaction の構文は残念ながら標準化されてなくて、DB毎によって違う。

* SQLite3 start transaction で、select/update で自動的に read/write lock がかかる* MYSQL select for update で write lock、select lock for share で read lock。* PosgreSQL select for update で write lock、select for share で read lock。

MYSQL では lock table は「それまでの lcok を解除してから、lock を取得」で、unlock tables は勝手にcommitするので以後 roll back 不可。したがって、事実上、lock table は使えない。なので、select for update を使う必要がある。


Shinji KONO / Tue Jan 24 23:46:44 2012