## モジュール 6: 並行制御 - 悲観的モード vs 楽観的モード

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

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

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

+ 左側のVS Codeの**EXPLORER**セクションで、`DemoJdbcPessimisticLock.java`ファイルをクリックします。

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

  ```output
  ...
  for (int i = 0; i < 2; i++) {
      connections.add(DriverManager.getConnection(connection_string));
  }
  ...
  ```

+ メインメソッドでは、2つのスレッドが開始され、同じレコードを更新しようとします。スレッド1とスレッド2の両方で自動コミットが無効になっています。

  ```java
  new Thread(new DemoJdbcPessimisticLock.RowUpdater(0, id, 1, 6000)).start();
  new Thread(new DemoJdbcPessimisticLock.RowUpdater(1, id, 1000, 2000)).start();
  ```

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

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

  ```bash
  rm -f DemoJdbcTxPessimisticLock.class
  javac -cp mysql-connector-j-9.1.0.jar DemoJdbcPessimisticLock.java
  sleep 1
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcPessimisticLock
  ```

  ```output
  Connection established.
  Connection B session started
  Connection A session started
  Connection A session: BEGIN PESSIMISTIC
  Connection A session: UPDATE test_tx_pessimistic SET name = 'Connection A' WHERE id = 5188146770730811393
  Connection B session: BEGIN PESSIMISTIC
  Connection B session: UPDATE test_tx_pessimistic SET name = 'Connection B' WHERE id = 5188146770730811393
  Connection A session: Commit
  Connection A session: Checking result

  /* Executing query: select id, name from test_tx_pessimistic; */
          Row#, id, name
          1) 5188146770730811393, Connection A

  Connection B session: Commit
  Connection B session: Checking result

  /* Executing query: select id, name from test_tx_pessimistic; */
          Row#, id, name
          1) 5188146770730811393, Connection B
  ```

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

+ 左側のVS Codeの**EXPLORER**セクションで、`DemoJdbcOptimisticLock.java`ファイルをクリックします。

+ 悲観的モードのコードと同様に、2つのスレッドが開始され、同時に同じ行を更新しようとします。

  ```java
  new Thread(new DemoJdbcOptimisticLock.RowUpdater(0, id, retryCommit, 1, 6000, 1000)).start();
  new Thread(new DemoJdbcOptimisticLock.RowUpdater(1, id, retryCommit, 1000, 2000, 9000)).start();
  ```

+ 競合の場合、簡単な再試行が実装されています。

  ```java
  if (this.retryCommit) {
    try {
        Thread.sleep(this.waitBefore2ndCommit);
    } catch (InterruptedException ie) {
        ie.printStackTrace();
    }
    try {
        System.out.println(connectionTags[this.connectionNo] + " session: " + "Commit");
        Statement s = c.createStatement();
        s.executeUpdate("UPDATE test_tx_optimistic SET name = '" + connectionTags[this.connectionNo]
                + "' WHERE id = " + rowid);
        c.commit();
    } catch (SQLException e1) {
        e1.printStackTrace();
    }
  }
  ```

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

+ 以下のコマンドを使用して楽観的ロックのデモコードを実行できます。最初に`no-retry`でコードを実行し、次に`retry`で実行します。可能な出力を示します。

  ```bash
  rm -f DemoJdbcOptimisticLock.class
  javac -cp mysql-connector-j-9.1.0.jar DemoJdbcOptimisticLock.java
  sleep 1
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcOptimisticLock no-retry
  ```

  ```output
  Connection established.
  Connection A session started
  Connection B session started
  Connection A session: BEGIN OPTIMISTIC
  Connection A session: UPDATE test_tx_optimistic SET name = 'Connection A' WHERE id = 4899916394579099649
  Connection B session: BEGIN OPTIMISTIC
  Connection B session: UPDATE test_tx_optimistic SET name = 'Connection B' WHERE id = 4899916394579099649
  Connection B session: Commit
  Connection B session: Checking result

  /* Executing query: select id, name from test_tx_optimistic; */
          Row#, id, name
          1) 4899916394579099649, Connection B

  Connection A session: Commit
  Connection A ErrorCode: 9007
  Connection A SQLState: HY000
  Connection A Error: java.sql.SQLException: Write conflict, txnStartTS=458625318024118280, conflictStartTS=458625318286262286, conflictCommitTS=458625318889193486, key=????, reason=Optimistic [try again later]
  < Session in Connection A RAISED THE EXCEPTION !!! >
  Connection A session: Checking result

  /* Executing query: select id, name from test_tx_optimistic; */
          Row#, id, name
          1) 4899916394579099649, Connection B
  ```

  ```bash
  rm -f DemoJdbcOptimisticLock.class
  javac -cp mysql-connector-j-9.1.0.jar DemoJdbcOptimisticLock.java
  sleep 1
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcOptimisticLock retry
  ```

  ```output
  Connection established.
  Connection A session started
  Connection B session started
  Connection A session: BEGIN OPTIMISTIC
  Connection A session: UPDATE test_tx_optimistic SET name = 'Connection A' WHERE id = 2017612633061982209
  Connection B session: BEGIN OPTIMISTIC
  Connection B session: UPDATE test_tx_optimistic SET name = 'Connection B' WHERE id = 2017612633061982209
  Connection B session: Commit
  Connection B session: Checking result

  /* Executing query: select id, name from test_tx_optimistic; */
          Row#, id, name
          1) 2017612633061982209, Connection B

  Connection A session: Commit
  Connection A ErrorCode: 9007
  Connection A SQLState: HY000
  Connection A Error: java.sql.SQLException: Write conflict, txnStartTS=458625338366492677, conflictStartTS=458625338628898820, conflictCommitTS=458625339231567877, key=????, reason=Optimistic [try again later]
  < Session in Connection A RAISED THE EXCEPTION !!! >
  Connection A session: Commit
  Connection A session: Checking result

  /* Executing query: select id, name from test_tx_optimistic; */
          Row#, id, name
          1) 2017612633061982209, Connection A
  ```

### 手順 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 test;
    ```

    ![demo701_chat2query.png 85%](https://lab-static.pingcap.com/images/2026/3/2/cb2f9fbf97c188815f9c14a5cf329b71a966842b.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` をクリックします。

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

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

  ![demo701_chat2query_eg2.png 85%](https://lab-static.pingcap.com/images/2026/3/2/779fa0129a442787e40ef0e665ad0d6120d20984.png)

{% endif %}
