質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.84%

  • Java

    12845questions

    Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

  • MariaDB

    271questions

    MariaDBは、MySQL派生のオープンソースなリレーショナルデータベースシステムです。 また、MySQLとほぼ同じデータベースエンジンに対応しています。

  • MyBatis

    61questions

    MyBatisはJavaや.NET Frameworkでなどで使用できる、SQL文や、ストアドプロシージャをオブジェクトと紐付けるO/Rマッピングフレームワークです。

悲観ロックの実装方法

解決済

回答 3

投稿

  • 評価
  • クリップ 1
  • VIEW 212

flaumig

score 39

 前提

cronで定期的に実行するバッチプログラムを作成しています。

Hogeテーブルに処理のキューを登録しておき、
バッチが起動するとHogeテーブルからステータス”待ち状態”のレコードを1件取得して特定の処理を実行します。
その処理の完了後、Hogeテーブルのステータスを”実行中”に更新します。

このバッチは多重起動されるものなので、ステータスを”待ち状態”から”実行中”に更新する間、
対象のレコードを他のプロセスから参照(select)させたくありません。
そのため、select ~ for updateを利用してロックをかけようと考え下記の実装で動作を試してみました。

 問題

Eclipseのデバッグモードで起動し、ブレークポイント(下記参照)で止めた状態で
別途SQLクライアントを立ち上げそちらで"select * from hoge_queue;"を実行してみました。

予想ではロックされているため、selectが待ち状態になるはずでしたが、
実際はupdate文が実行される前のレコードが取得できてしまいました。
(プログラムをステップ実行し、commit後に再度selectすると、update後のレコードが正しく取得できました)

下記の実装はどこが間違っているのでしょうか?
解決方法をご存知の方がいらっしゃいましたら、ご指摘、アドバイスを頂きたいです。
どうぞよろしくお願い致します。

 開発環境

Java 1.8.0_172
MyBatis 3.4.6
MariaDB 10.2.14(トランザクション分離レベルはデフォルトの"REPEATABLE-READ")

 実装

 ロジック

private static final String WAITING = "1";
private static final String PROCESSING = "2";

// ~~省略~~

try (SqlSession session = sessionFactory.openSession()) {
    HogeMapper hogeMapper = session.getMapper(HogeMapper.class);
    // 処理するレコードを取得
    HogeQueue record = hogeMapper.findByStatusForUpdate(WAITING);

    // (特定の処理)

    // ステータスの更新
    rec.setStatus(PROCESSING);//←★ここにブレークポイントを置いて確認★
    int updateCount = hogeMapper.updateStatus(record);
    session.commit();
}

 HogeMapper.java

@Mapper
public interface HogeMapper{

    HogeQueue findByStatusForUpdate(char status); 

    int updateStatus(HogeQueue queue);
}

 HogeMapper.xml

<select id="findByStatusForUpdate" resultType="HogeQueue">
    select
      id
     ,input_file_name
     ,status
     ,create_user_id
     ,create_date
     ,update_user_id
     ,update_date
    from hoge_queue
    where status = #{status}
    order by id
    limit 0, 1
    for update
</select>
<update id="updateStatus">
    update hoge_queue
    set status = #{status}
       ,update_user_id = 'batch'
       ,update_date = now()
    where id = #{id}
</update>
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+2

これ「ホントにロックかからないのだろうか?」と思い、すでに解決済みになっているようですが、検証してみました。

検証環境:Windows10+Eclipse4.7.2+SpringBoot+Mybatis+MariaDB

検証対象:質問と同様の単一テーブルに対し、Spring+Mybatisの組み合わせて、MybatisからSELECT (複数カラム指定) FOR UPDATE を実施するSQLを1つ実行したところでブレークポイント設置

  1. ブレーク中は別スレッド(ブラウザから別タブで同URLを実行)を起動し、同じSELECTを投げてロックされるかを確認
  2. ブレーク中にHeidiSQLから同一のロック対象のテーブルに対してSELECTを実行。このとき、SELECTには以下の2種類を検証。
  • SELECT * FROM ~~~ FOR UPDATE
  • SELECT (いくつかのカラム指定) FOR UPDATE

検証結果:1を実行した結果、先行したスレッドが完了するまで後発のスレッドが待機していることから、MybatisからSELECT ~ FOR UPDATEには行ロックがかかっていることは明らかだった。

検証結果:2を実行した結果、HeidiSQLからSELECT * FROM は無条件で取得されたが、HeidiSQLからSELECT (カラム複数指定) FOR UPDATE はロック待機待ち状態になった。

つまり、MariaDBでは、SELECT * は行ロックを突き抜けて検索される。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/16 12:31

    わざわざお時間を割いて検証して頂きまして、本当にありがとうございます。
    “select * 〜”だけがロックをすり抜けるという挙動をするのですね。
    こちらの確認不足でした。

    キャンセル

checkベストアンサー

+1

select for updateは他のトランザクションから更新や削除をさせないためのロックなので、
他のトランザクションからselectできるのはRepeatabl Read以下の分離レベルにおいては普通かと思います。
正確には、Repeatable Readなのでコミット"前"の状態が見えるという状況は仕様通りの動きです。

やりたいことを実現されたいのであれば、
分離レベルをSeriarizableに上げることも選択肢ですが、並行処理の意味が無くなりパフォーマンスが悪くなりますし、
ましてやバッチ処理でその分離レベルは少なくとも私は見たことがありません。
(Seriarizableでも許容時間内に完了するのであれば、問題は無いかと思います)

並行で、パフォーマンスもある程度保ちたいのであれば

  • 親プロセスで一旦処理対象のレコードをすべて読み取り、レコード分の子プロセスを起動して一つずつ処理を移譲する
  • もしくはcronによるバッチプロセス起動は1回のみで、Javaの中で親スレッドのようなもので処理対象のレコードをすべて読み取り、子スレッドを起動して一つずつ処理を移譲するようなマルチスレッドプログラミングを実装する

といった選択肢しか思いつきませんでした。
(やりたいことの理解が間違っていたらすみません)
後者はやったことがありますが、並行処理を自前で作りこむのは複雑でバグを生みやすいので、
昨今ではこういった仕組み的なところはSpring Batch等のフレームワークに任せたほうが良いかもしれません。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/11 20:04

    ご回答ありがとうございます。
    select出来るのは仕様通りなのですね。私が挙動を勘違いしておりました。
    仰る通り、処理そのものを一旦見直そうと思います。

    キャンセル

0

別途SQLクライアントを立ち上げそちらで"select * from hoge_queue;"を実行してみました。
予想ではロックされているため、selectが待ち状態になるはずでしたが、
実際はupdate文が実行される前のレコードが取得できてしまいました。

トランザクション内で行わないとロックされませんよ。
MySQLでのselect for updateを実施したがロックされない
また、別のセッションでSELECT しているほう(SQLクライアント側)でも FOR UPDATEしないと駄目じゃなかったかな?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/11 11:48

    すみません。間違って確認前にベストアンサーを押してしまいました…。
    まだご覧になっていたら再度、ご教示いただきたいのですがMyBatisを利用していますのでopenSession()を呼んだ時点でトランザクションが開始されている認識です。
    そうではなく明示的にトランザクションを開始する、というメソッドや実装方法があるのでしたら、そちらを教えて頂きたいのですが可能でしょうか?
    自分で探してみましたがMyBatisで”begin”を実行する方法が見つけられませんでした。

    キャンセル

  • 2018/05/14 06:33

    トランザクションが無いとという記述はSQLクライアント側での手動実行の時にという意図です。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.84%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

  • 解決済

    processing 描画について

    processingでこれを描こうと思い size(600,600); background(0); strokeWeight(10); int p ; int a 

  • 受付中

    pascalプログラミングについての質問です。

    手も足も出ない状況です。 よろしくお願いいたします。

  • 解決済

    processing 泡の落下の再現をしたいです。

    課題 今、processing3の3Dマッピングを使って、水(?)の落下を再現したいと考えております。 まず2dでellipseを描写したところうまくいきました。以下コードです。

  • 受付中

    MacでProcessingから他のスケッチを起動する方法について

    前提・実現したいこと MacOSでProcessingから他のスケッチを起動させるプログラムを作成したいです. Windowsでは実現することができたのですがMac環境では動きませ

  • 解決済

    円柱の曲面に文字や図形を表示したい。

    いつもお世話になっています。  前提・実現したいこと 表題通りです。 円柱の曲面に文字や図形を表示したいです。 テクスチャマッピングなるもので要件を満足する方法があるのです

  • 解決済

    Processing 運動のアニメーションについての質問

     前提・実現したいこと ここに質問の内容を詳しく書いてください。 processingを用いてpackmanを左から右に口をパクパクさせながら動かすプログラムを作成しているのですが

  • 解決済

    弾幕ゲームの作成に関して

     processingでのゲーム作成 初めて投稿します。 最近processingを勉強し始め、ゲームを作るという課題が出て、私は弾幕シューティングゲームを作ろうと思い、作成してい

  • 解決済

    processngでのゲーム作りに関して

    ゲーム画面の切り替えに関して processingで弾幕ゲームを作成しています。 質問① 弾と自機、レーザーとボスの当たり判定に関して、当たったと識別する条件は書けたのですが、

同じタグがついた質問を見る

  • Java

    12845questions

    Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

  • MariaDB

    271questions

    MariaDBは、MySQL派生のオープンソースなリレーショナルデータベースシステムです。 また、MySQLとほぼ同じデータベースエンジンに対応しています。

  • MyBatis

    61questions

    MyBatisはJavaや.NET Frameworkでなどで使用できる、SQL文や、ストアドプロシージャをオブジェクトと紐付けるO/Rマッピングフレームワークです。