## モジュール 6: 同時実行制御 - 悲観的モード vs. 楽観的モード

同時アクセスをサポートするデータベースでは、複数のトランザクションが同時に同じデータを更新しようとする場合に競合のリスクがあります。適切に設計されたデータモデルはこのような競合の可能性を最小限に抑えることができますが、それでも競合は発生する可能性があり、効果的に管理する必要があります。データベースは同時データアクセスを処理するメカニズムを提供しており、主な2つのアプローチは悲観的同時実行制御と楽観的同時実行制御です。TiDBは両方のアプローチをサポートしています。

このモジュールでは、TiDBにおける悲観的ロックと楽観的ロックの両方の動作を示します。

### 手順 1. 悲観的モードのデモコードを読む

+ VS Codeの左側の**エクスプローラー**で、`mysql_connector_python_demo`フォルダの下にある`lock_pessimistic.py`ファイルをクリックします。内容を確認しましょう。

+ 以下のコードでは、2つのスレッドが開始されます。各スレッドはTiDBへの接続を作成します。

  ```output
  ...
  t1 = threading.Thread(target=thread1_task)
  t2 = threading.Thread(target=thread2_task)
  t1.start()
  t2.start()
  ...
  ```

+ 以下のコードでは、`threading.Event`を使用してスレッド1とスレッド2のアクションを調整し、確実に競合が発生するようにしています。スレッド1と2の両方で自動コミットは無効になっています。

  ```output
  def thread1_task():
    conn1 = get_connection(autocommit=False)
    cur1 = conn1.cursor(dictionary=True)
    cur1.execute("BEGIN PESSIMISTIC;")
    print("[Thread1] BEGIN PESSIMISTIC", flush=True)

    # notify thread2 to begin transaction
    thread1_begin.set()

    # update and hold lock
    cur1.execute("UPDATE users SET name = 'conn1' WHERE id = 1;")
    print("[Thread1] UPDATED row → name='conn1'", flush=True)

    # notify thread2 to begin transaction
    thread1_updated.set()

    # wait thread2 to attempt update (blocked)
    thread2_ready.wait()

    cur1.execute("COMMIT;")
    print("[Thread1] COMMITTED", flush=True)
    ...
  ```

  ```output
  def thread2_task():
    conn2 = get_connection(autocommit=False)
    cur2 = conn2.cursor(dictionary=True)

    # wait thread1 to complete BEGIN
    thread1_begin.wait()

    cur2.execute("BEGIN PESSIMISTIC;")
    print("[Thread2] BEGIN PESSIMISTIC", flush=True)

    # wait thread1 to update and hold lock
    thread1_updated.wait()

    # notify thread1 that I will attempt UPDATE
    thread2_ready.set()
    print("[Thread2] attempting UPDATE (will block)...", flush=True)

    # this line will be blocked until thread1 COMMIT release row lock
    cur2.execute("UPDATE users SET name = 'conn2' WHERE id = 1;")
    print("[Thread2] UPDATED row → name='conn2'", flush=True)

    cur2.execute("COMMIT;")
    print("[Thread2] COMMITTED", flush=True)
    ...
  ```

### 手順 2. 悲観的モードでデモを実行する

+ VS Code の左側の **EXPLORER** で、`mysql_connector_python_demo` フォルダの下にある `lock_pessimistic.py` ファイルをクリックしてください。内容を確認しましょう。

+ 悲観的モードでデモコードを実行し、出力を観察してください。スレッド1がコミットするまで、スレッド2がコミットできないことに注目してください。

  ```bash
  poetry run python mysql_connector_python_demo/lock_pessimistic.py
  ```

  ```output
  Create database lab_demo_405_3450001
  [Thread1] BEGIN PESSIMISTIC
  [Thread2] BEGIN PESSIMISTIC
  [Thread1] UPDATED row → name='conn1'
  [Thread2] attempting UPDATE (will block)...
  [Thread1] COMMITTED
  [Thread2] UPDATED row → name='conn2'
  [Thread2] COMMITTED
  Update Result: name is changed to conn2
  ```

### 手順 3. 楽観的モードのデモコードを読む

+ VS Code の左側の **EXPLORER** で、`mysql_connector_python_demo` フォルダの下にある `lock_optimistic.py` ファイルをクリックしてください。内容を確認しましょう。

+ 楽観的ロックのデモコードは悲観的ロックのものと似ていますが、楽観的ロックでは両方のスレッドに `try` と `except` のロジックが実装されている点が異なります。また、楽観的ロックのデモコードでは `threading.Event` は使用されていません。

+ スレッド1を例として見てみましょう。

  ```output
  def thread1_task():
    ...
    cur1.execute("BEGIN OPTIMISTIC;")
    print("[Thread1] BEGIN OPTIMISTIC", flush=True)

    # update
    cur1.execute("UPDATE users SET name = 'conn1' WHERE id = 1;")
    print("[Thread1] UPDATED row → name='conn1'", flush=True)
    ...

    # thread1 try to commit.
    # error(s) will be raised if there is a conflict
    try:
        cur1.execute("UPDATE users SET name = 'conn1' WHERE id = 1;")
        print("[Thread1] UPDATED row → name='conn1'", flush=True)
        print("[Thread1] Try to commit", flush=True)

        cur1.execute("COMMIT;")
        print("[Thread1] Commit Successful", flush=True)

    except mysql.connector.Error as err:
        print("[Thread1] Conflict occurs: {}".format(err), flush=True)
        print("[Thread1] Retry", flush=True)

        try:
            cur1.execute("BEGIN OPTIMISTIC;")
            print("[Thread1 retry] BEGIN OPTIMISTIC", flush=True)

            cur1.execute("UPDATE users SET name = 'conn1' WHERE id = 1;")
            print("[Thread1 retry] UPDATED row → name='conn1'", flush=True)

            cur1.execute("COMMIT;")
            print("[Thread1 retry] Commit successful", flush=True)

        except mysql.connector.Error as err:
            print("[Thread1 retry] Conflict occurs: {}".format(err), flush=True)
  ```

### 手順 4. 楽観的モードでデモを実行する

+ 以下のコマンドを使用して楽観的ロックのデモコードを実行できます。`threading.Event`を使用して実行順序を制御していないため、多くの可能な出力があります。ここでは3つの典型的な出力を紹介します。最初のサンプル出力では、`Thread 2`が競合を起こし、その後再試行しています。2番目のサンプル出力では、`Thread 1`が競合を起こしています。3番目のサンプル出力では、競合は発生していません。

+ ただし、このシナリオではトランザクションロックの待機は発生しません。競合が発生した場合、その解決はあなたの裁量に委ねられます。このデモでは、単純な再試行が実行されます。

  ```bash
  poetry run python mysql_connector_python_demo/lock_optimistic.py
  ```

  ```output
  [Thread1] BEGIN OPTIMISTIC
  [Thread1] UPDATED row → name='conn1'
  [Thread1] UPDATED row → name='conn1'
  [Thread1] Try to commit
  [Thread2] BEGIN OPTIMISTIC
  [Thread1] Commit Successful
  [Thread2] UPDATED row → name='conn2'
  [Thread2] Try to commit
  [Thread2] Conflict occurs: 9007 (HY000): Write conflict, txnStartTS=458156067333603352, conflictStartTS=458156067333603338, conflictCommitTS=458156067333603359, key=????, reason=Optimistic [try again later]
  [Thread2] Retry
  [Thread2 retry] BEGIN OPTIMISTIC
  [Thread2 retry] UPDATED row → name='conn2'
  [Thread2 retry] Commit successful
  Update Result: name is changed to conn2
  ```

  ```output
  [Thread2] BEGIN OPTIMISTIC
  [Thread2] UPDATED row → name='conn2'
  [Thread2] Try to commit
  [Thread1] BEGIN OPTIMISTIC
  [Thread2] Commit Successful
  [Thread1] UPDATED row → name='conn1'
  [Thread1] UPDATED row → name='conn1'
  [Thread1] Try to commit
  [Thread1] Conflict occurs: 9007 (HY000): Write conflict, txnStartTS=458156066350563402, conflictStartTS=458156066350563397, conflictCommitTS=458156066350563406, key=????, reason=Optimistic [try again later]
  [Thread1] Retry
  [Thread1 retry] BEGIN OPTIMISTIC
  [Thread1 retry] UPDATED row → name='conn1'
  [Thread1 retry] Commit successful
  Update Result: name is changed to conn1
  ```

  ```output
  [Thread2] BEGIN OPTIMISTIC
  [Thread2] UPDATED row → name='conn2'
  [Thread2] Try to commit
  [Thread2] Commit Successful
  [Thread1] BEGIN OPTIMISTIC
  [Thread1] UPDATED row → name='conn1'
  [Thread1] UPDATED row → name='conn1'
  [Thread1] Try to commit
  [Thread1] Commit Successful
  Update Result: name is changed to conn1
  ```

### ステップ 5. TiDB Cloud コンソールにアクセスして、自分自身の旅を始めましょう

+ ハンズオンラボはここで終了です。[こちら](https://tidbcloud.com/clusters)から TiDB Cloud の旅を始めましょう。このラボで自分の無料クラスターを使用していた場合、同じページにクラスターが表示されます。クラスター名は ${LAB:SERVERLESS_CLUSTER_NAME} です。

+ TiDB Cloud は Web ベースの SQL エディターを提供しており、TiDB Cloud を探索するために使用できます。ノートパソコンでの追加セットアップは一切不要です。

![sql_editor.png 85%](https://lab-static.pingcap.com/images/2026/3/1/da0e9e005e2d10932b0c62b497d3c3282fc58567.png)

+ SQL クエリを自分で書きたくない場合も問題ありません。`Chat2Query` は自然言語でクエリを記述できるツールです。
  + まず、以下のコマンドを SQL エディターにコピーして、使用したいデータベースを選択し、右上の `Run` をクリックしてください。

    ```sql
    USE ${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}
    ```

    ![chat2query.png 85%](https://lab-static.pingcap.com/images/2026/3/2/92fb25fbfdbbf592a6e6b2abd0b095584a06d477.png)

  + 次に、Windows の場合は `ctrl` + `i`、Mac の場合は `command` + `i` を押して、TiDB の `Chat2Query` を試してみましょう。自然言語でクエリを記述できます。AI がクエリを自動生成してくれます。ぜひお楽しみください。

{% if SERVERLESS_CLUSTER_ENVIRONMENT == 'user_hosted' %}

+ 以下に 2 つの例を示します：

  + 例 1 では、`Chat2Query` に `show me the number of records in the table online_ddl_test` と入力しています。クエリが生成されたら内容を確認し、`Accept` をクリックします。最後に、右上の `Run` をクリックします。

  ![chat2query_eg1.png 85%](https://lab-static.pingcap.com/images/2026/3/2/0e3e5d953884b1f8afc24cf4ed4e4d8d91f4f7af.png)

  + 例 2 では、`Chat2Query` に `add a column 'c' for me in table t1` と入力しています。クエリが生成されたら内容を確認し、`Accept` をクリックします。最後に、右上の `Run` をクリックします。

  ![chat2query_eg2.png 85%](https://lab-static.pingcap.com/images/2026/3/2/242d79b8cdb188df3520185b27483bcdbe1eef6d.png)

{% endif %}
