音楽の話しではなく、とある技術的な話で、GORM = Grails’Object Relational Mapping の テーブルロックに関するメモ。
Lock は、トランザクションの中で行うので、
Domain.withTransaction { status –>
def a = Domain.lock(id);
}
と書けば、 lock の位置で、ロックされる。つまり最初にここを実行したスレッドは通るが、そのスレッドのトランザクションが完了するまで、次に来たスレッドは待たされる。ロックするということは、たいてい、排他的な更新をしたいからなので、 a に変更を加えて保存(DB更新)することになる。となると、
Domain.withTransaction { status –>
def a = Domain.lock(id);
a.save(flush:true);
}
みたいな感じになる。
a を id で取得したい場合はこれで良いのだが、id以外のキーで取得したい場合は、次のようになる。
Domain.withTransaction { status –>
def a = Domain.findByKey(key, [lock : true]);
a.save(flush:true);
}
第二引数の、[lock : true] は、あまりドキュメントに出てこないのであるが、これで実際にロックされる。
ところが、実際にこれを頻繁に更新される状況で、非同期で実行すると、save のタイミングで、楽観的ロックエラーになる。
何故か? ロックされるのであるから、楽観的ロックエラーになるのはおかしいと思うのだが、実際に試してみると、findByKey() で取得した値は、ロック前の値なのだ。ロックはされるが、ロックが解除された後に取得した値は、ロックする前の値なので、前のスレッドが更新する前の値となるために、次に更新しようとすると、楽観的ロックエラーになる。
想像するに、以下のような処理になっているのではないかと思われる。
def a = Domain.findByKey(key);
a.lock();
ロックはされるが、取得される a はロック前の古い値で、待たされている間に変わってしまった新しいテーブル上の値ではない。
def a = Domain.findByKey(key);
a = Domain.get(a.id);
にしたらどうかと思ったが、どうやらキャッシュされているようで、駄目だった。
結局、次のコードで落ち着いた。
def a = Domain.findByKey(key, [lock : true]);
a.refresh();
ロックは、findByKey で行う。
で、自分の番が回ってきたら、refresh でDBから値を取り直す。
SQL直接だと、こんなに悩む必要は無いのだけれど、GORMだと時々こういう事がある。