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

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

新規登録して質問してみよう
ただいま回答率
85.50%
Java

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

MariaDB

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

MyBatis

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

Q&A

解決済

3回答

16274閲覧

悲観ロックの実装方法

flaumig

総合スコア67

Java

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

MariaDB

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

MyBatis

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

0グッド

1クリップ

投稿2018/05/10 13:22

前提

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")

実装

ロジック

Java

1 2private static final String WAITING = "1"; 3private static final String PROCESSING = "2"; 4 5// ~~省略~~ 6 7try (SqlSession session = sessionFactory.openSession()) { 8 HogeMapper hogeMapper = session.getMapper(HogeMapper.class); 9 // 処理するレコードを取得 10 HogeQueue record = hogeMapper.findByStatusForUpdate(WAITING); 11 12 // (特定の処理) 13 14 // ステータスの更新 15 rec.setStatus(PROCESSING);//←★ここにブレークポイントを置いて確認★ 16 int updateCount = hogeMapper.updateStatus(record); 17 session.commit(); 18}

HogeMapper.java

Java

1@Mapper 2public interface HogeMapper{ 3 4 HogeQueue findByStatusForUpdate(char status); 5 6 int updateStatus(HogeQueue queue); 7} 8

HogeMapper.xml

xml

1<select id="findByStatusForUpdate" resultType="HogeQueue"> 2 select 3 id 4 ,input_file_name 5 ,status 6 ,create_user_id 7 ,create_date 8 ,update_user_id 9 ,update_date 10 from hoge_queue 11 where status = #{status} 12 order by id 13 limit 0, 1 14 for update 15</select> 16<update id="updateStatus"> 17 update hoge_queue 18 set status = #{status} 19 ,update_user_id = 'batch' 20 ,update_date = now() 21 where id = #{id} 22</update>

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

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

検証環境: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/13 15:40

A-pZ

総合スコア12011

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

flaumig

2018/05/16 03:31

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

0

ベストアンサー

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

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

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

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

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

投稿2018/05/11 03:54

kochoru

総合スコア60

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

flaumig

2018/05/11 11:04

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

0

別途SQLクライアントを立ち上げそちらで"select * from hoge_queue;"を実行してみました。

予想ではロックされているため、selectが待ち状態になるはずでしたが、
実際はupdate文が実行される前のレコードが取得できてしまいました。

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

投稿2018/05/10 16:20

euledge

総合スコア2404

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

flaumig

2018/05/11 02:48

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

2018/05/13 21:33

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問