GORM で Lock

音楽の話しではなく、とある技術的な話で、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だと時々こういう事がある。

カテゴリー: 開発 パーマリンク