SELECT ... FOR UPDATE
はロックを待機する必要があり、その間に別のスレッドが結果を変更してコミットすると、最初のクエリはold結果を返します。つまり、before変更の前。
これは予想される動作ですか、それともバグですか?それは確かに役に立たないようです。
簡単に説明できます。次の表を見てみましょう。
CREATE TABLE `orders`(
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`description` VARCHAR(50),
PRIMARY KEY (`id`)
) ENGINE=INNODB;
そして、この一連のステートメント:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SELECT MAX(id)
FROM orders
FOR UPDATE;
# PAUSE HERE
INSERT INTO orders (description)
VALUES ('bla');
COMMIT;
今、私たちは次のことを行います:
SELECT
を実行します。 Now更新された値を返します。したがって、賢明で望ましいと思われる動作を実現するには、SELECT
を2回実行する必要があるようです。
これは予想される動作ですか、それともバグですか?
(MySQL 5.7.25でテスト済み)
私はいくつかの実験を行うことによって、その振る舞いの説明を理解しました。新しい行を挿入するのではなく、接続1を使用して他のいくつかの変更を実行しました。結合された結果は、プロセスがどのように機能するかを明らかにします。
description
のid
を更新すると、接続2は更新されたdescription
を参照します。 (索引スキャンは、ロックされた行の前に停止しました。)id
をsmaller値に更新する場合、たとえば、 10から9まで、接続2は新しい値9を返します(ロックされた行の前にインデックススキャンが停止し、行が消えることを確認し、降順で続行して、9に到達します)。id
をgreater値に更新した場合、たとえば、 10から11まで、接続2はその値をスキップし代わりに、元の値よりも小さい最大のid
を返します。この例では、存在する場合はid
9を返します。 (インデックススキャンは、ロックされた行の前に停止し、行が消えることを確認し、降順で続行するため、11を通過することはなく、9に到達します。)id
をより大きい値に更新した場合。 10から11に変更し、古い行の代わりに新しい行を挿入します。 id
10の場合、接続2は新しく挿入された行を選択します。 (インデックススキャンはロックされた行の前で停止しており、その場所に挿入されたものが表示されます。)id
をより大きい値に更新した場合。 10から20に変更してから、古い行の代わりに新しい行を挿入しますが、元の行よりも大きな値になります。 11、接続2はそれを見ますか?おそらくそうではありませんが、これはテストする必要があります。MAX(id)
を10にロックし、接続2がハードコーディングされたid
を9に選択した場合、ロックは重複せず、両方が独立して処理できます。MAX(id)
は逆スキャンを使用して決定され、ロックは実際の(インデックス)行にあると結論付けることができます。意味あり。分離レベルREAD COMMITTED
はギャップロックを取得しません。 REPEATABLE READ
可能性がありますが、接続2のトランザクションが完全に失敗するため、そのシナリオをこれ以上詳しく調査することはありません。
正確な仕組みの結論
接続2は、特定の行がロックされるまで逆スキャンを開始します。
それはそのロックが解放されるのを待ち、それからスキャンを続行しますそれから(そしてそれを含めて)
id
が中断したもの。何でもないrowではなく、何でもid
。
id
に行がなくなった場合、スキャンはその行を超えて、より小さなid
sまで続行されます。これは予想される動作です。選択クエリを開始した場合、それらの結果をそのクエリ(またはトランザクション)の開始と一致させる必要があります。 FOR UPDATEを指定しているため、トランザクションの開始を示しています。コミットするとトランザクションが終了します。そのため、手順6で更新された値が返されます。
別のセッションが選択に関係する行を更新してコミットすると、セッションはロールバックデータを使用して、トランザクションの開始時のデータを再構築します。
単純に行うのに複雑さは必要ありません
_INSERT ...;
SELECT LAST_INSERT_ID();
_
LAST_INSERT_ID()
の値は接続で保持されるため、他のアクションが実行されても影響を受けません。
なぜMAX(id)
が必要なのですか?
ケース1:他のインサートで使用します。その後、トランザクションは完全に正常ですthis方法:
_BEGIN;
INSERT ...;
SELECT @id := LAST_INSERT_ID(); -- and put into local variable or @variable
INSERT something else ... VALUES (..., @id, ...);
COMMIT;
_
ケース2:さて、あなたのケースを説明してください。