OceanBaseデータベースはマルチバージョン同時実行制御(MVCC)をサポートしています。デフォルトでは、読み取りトランザクションは書き込みトランザクションの実行をブロックしませんが、SELECT ... FOR UPDATE を使用して読み取り対象にロックをかけることで、書き込みトランザクションをブロックすることができます。
本記事では、具体的な例を通じて SELECT ... FOR UPDATE を使用してクエリ結果をロックする方法を説明します。
ロック動作
ある行のデータが他のトランザクションによってロックされている場合、そのロックが解除されるのを待つことなく処理を続行するために、SELECT ... FOR UPDATE のようなロック読み取り文と共に NOWAIT および SKIP LOCKED オプションを使用できます。詳細は以下の通りです:
SELECT ... FOR UPDATE句を使用した場合の動作:- ロック待機:特定の行が他のトランザクションによってロックされている場合、現在のトランザクションはロックが解除されるか、待機がタイムアウトするまで待機します。必要なロックを取得すると、トランザクションは処理を続行します。
- 他のトランザクションのブロック:現在のトランザクションが特定の行のロックを保持しており、別のトランザクションが同じ行をロックしようとすると、そのトランザクションは現在のトランザクションが行ロックを解除するまでブロックされます。
SELECT ... FOR UPDATE NOWAIT句を使用した場合の動作:トランザクションが行をロックしようとした際、その行が別のトランザクションによってロックされている場合、ロックが解除されるのを待つことなく、直ちにエラーが返されます。
SELECT ... FOR UPDATE SKIP LOCKED句を使用した場合の動作:トランザクションが行をロックしようとした際、その行が別のトランザクションによってロックされている場合、その行をスキップして次の行の処理を続けます。
例
サンプルテーブルを作成し、テストデータを挿入します。
テーブル
fruit_orderを作成します。CREATE TABLE fruit_order( order_id INT NOT NULL AUTO_INCREMENT COMMENT '注文ID', user_id BIGINT NOT NULL COMMENT '顧客ID', user_name VARCHAR(16) NOT NULL DEFAULT '' COMMENT '顧客名', fruit_price DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '注文金額', order_year SMALLINT NOT NULL COMMENT '注文年', PRIMARY KEY (order_id) ) COMMENT '注文表';テーブル
fruit_orderにテストデータを挿入します。INSERT INTO fruit_order(user_id, user_name, fruit_price, order_year) VALUES (1011,'張三',13.11,'2019'), (1011,'張三',22.21,'2020'), (1011,'張三',58.83,'2020'), (1022,'李四',23.34,'2019'), (1022,'李四',12.22,'2019'), (1022,'李四',14.66,'2021'), (1022,'李四',34.44,'2021'), (1033,'王五',51.55,'2020'), (1033,'王五',63.66,'2021');
FOR UPDATEを使用したクエリ結果のロック
以下のステートメントを実行して、自動コミット機能を無効にします。
SET GLOBAL autocommit = 0;自動コミット機能の詳細については、autocommitを参照してください。
注意
グローバルレベルの変数の設定は、現在のセッションには適用されません。有効にするには、再ログインして新しいセッションを確立する必要があります。
セッション1で以下のステートメントを実行し、注文IDが7のクエリ結果をロックします。
obclient [test]> SELECT * FROM fruit_order WHERE order_id = 7 FOR UPDATE;実行結果は次のとおりです:
+----------+---------+-----------+-------------+------------+ | order_id | user_id | user_name | fruit_price | order_year | +----------+---------+-----------+-------------+------------+ | 7 | 1022 | 李四 | 34.44 | 2021 | +----------+---------+-----------+-------------+------------+ 1 row in setセッション2で以下のステートメントを実行し、注文IDが7の行の
fruit_priceデータを16.15に変更します。このSQL文は、上記のトランザクションがロールバックされるか、COMMITが実行されるまで待機します。それ以外の場合は、タイムアウトエラーが発生するまで待機します。obclient [test]> UPDATE fruit_order SET fruit_price = 16.15 WHERE order_id = 7;実行結果は次のとおりです:
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transactionセッション1で以下のステートメントを実行し、トランザクションをコミットします。
COMMIT;セッション2で再度以下のステートメントを実行し、注文IDが7の行の
fruit_priceデータを16.15に変更します。obclient [test]> UPDATE fruit_order SET fruit_price = 16.15 WHERE order_id = 7;実行結果は次のとおりです:
Query OK, 1 row affected Rows matched: 1 Changed: 1 Warnings: 0セッション2で以下のステートメントを実行し、トランザクションをコミットします。
COMMIT;セッション1で以下のステートメントを実行し、更新後のデータをクエリします。
obclient [test]> SELECT * FROM fruit_order WHERE order_id = 7;実行結果は次のとおりです:
+----------+---------+-----------+-------------+------------+ | order_id | user_id | user_name | fruit_price | order_year | +----------+---------+-----------+-------------+------------+ | 7 | 1022 | 李四 | 16.15 | 2021 | +----------+---------+-----------+-------------+------------+ 1 row in set
NOWAITまたはSKIP LOCKEDオプションを使用してクエリ結果をロックする
セッション1で以下のステートメントを実行し、
FOR UPDATEを使用して注文IDが7のクエリ結果をロックします。obclient [test]> SELECT * FROM fruit_order WHERE order_id = 7 FOR UPDATE;実行結果は次のとおりです:
+----------+---------+-----------+-------------+------------+ | order_id | user_id | user_name | fruit_price | order_year | +----------+---------+-----------+-------------+------------+ | 7 | 1022 | 李四 | 16.15 | 2021 | +----------+---------+-----------+-------------+------------+ 1 row in setセッション2で以下のステートメントを実行し、
FOR UPDATE NOWAITを使用して注文IDが7のクエリ結果をロックします。obclient [test]> SELECT * FROM fruit_order WHERE order_id = 7 FOR UPDATE NOWAIT;実行結果は次のとおりです:
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transactionセッション3で以下のステートメントを実行し、
FOR UPDATE SKIP LOCKEDを使用して注文IDが7以上のクエリ結果をロックします。obclient [test]> SELECT * FROM fruit_order WHERE order_id >= 7 FOR UPDATE SKIP LOCKED;実行結果は次のとおりです:
+----------+---------+-----------+-------------+------------+ | order_id | user_id | user_name | fruit_price | order_year | +----------+---------+-----------+-------------+------------+ | 8 | 1033 | 王五 | 51.55 | 2020 | | 9 | 1033 | 王五 | 63.66 | 2021 | +----------+---------+-----------+-------------+------------+ 2 rows in set