# 楽観的なトランザクションロック

### 概要
+ この演習では、楽観的なトランザクション ロックの使い方を学びます。
+ 最初にテーブルを作成し、2つの接続を確立します。どちらの接続も、テーブル内のまったく同じレコードを更新しようとします。悲観的なトランザクションロックとは異なり、楽観的なトランザクションロックは、トランザクションが最後にコミットできるかどうかをチェックします。競合が発生した場合、アプリケーションコードによって再試行するかエラーを発生させるかが決まります。

### 注意
+ いくつかのステップでは、2つの異なるターミナルウィンドウでコマンドを入力する必要があります。2つのターミナルを起動し、演習の間ずっと開いておくと便利でしょう。
+ この演習で利用する、`IP`、`port`、`username`、`password`、`directory`などは、あなたの環境と異なる場合があります。
+ ターミナルウィンドウの"`$`"とはOSプロンプトです。
+ ターミナルウィンドウ内の "`tidb>`" または "`mysql>`" は、SQL コマンドプロンプトです。
+ これらの演習は、ノートパソコンなどの単一のマシン上で実行することができます。

### 前提条件
+ macOSまたはLinuxを使用していること。
+ [lab repository](https://github.com/pingcap/tidb-course-201-lab.git)をクローン済みであること。
+ 演習「テスト用にTiDBクラスタを作成する」を完了していること。
+ [JDK 8 以降](https://docs.oracle.com/javase/8/docs/technotes/guides/install/install_overview.html)がインストールされていること。

### 所要時間
+ この演習は約15分程度かかります。

### タスク
1. デモプログラムのJavaソースコードを確認します。
2. デモスクリプトを実行して、2つのトランザクションによるコンフリクトを作成し、スクリプトを実行して結果を観察します。

### 手順
1. `tidb-course-201-lab/scripts`フォルダの下のJavaソースコード`DemoJdbcTxOptimisticLock.java`では、次の行に注意する必要があります（すべてのソースコードを確認する必要はありません）。

      `main()`メソッドの次のコードは2つの接続を確立します。
      ```java
      for (int i = 0; i < 2; i++) {
          connections.add(DriverManager.getConnection(
                  "jdbc:mysql://" + tidbHost
                          + ":" + port
                          + "/test?"
                          + securityOption,
                  dbUsername, dbPassword));
      }
      ```

      最初の接続では、`id`と`name`という2つのカラムを持つ`test_tx_optimistic`というテーブルが作成されます。`id`カラムはプライマリキーで、`AUTO_RANDOM`という属性があります。その後、キーが返されるので、後で両方の接続でどのレコードを更新すればよいかがわかります。

      ```java
      ...
      Statement s = c.createStatement();
      ...
      s.executeUpdate("CREATE TABLE test_tx_optimistic (id BIGINT PRIMARY KEY AUTO_RANDOM, name CHAR(20))");
      s.executeUpdate("INSERT INTO test_tx_optimistic (name) VALUES ('INIT') ", Statement.RETURN_GENERATED_KEYS);
      ```

      `ATUOCOMMIT`は無効にされています。
      ```java
      for (Connection c : connections) {
          c.setAutoCommit(false);
      }
      ```

      次のセクションでは、2つの接続で`RowUpdater`クラスを利用して楽観的なトランザクションを開始し、同じ行の`name`カラムを更新するという主な目的を示しています。
      ```java
      s.executeUpdate("BEGIN OPTIMISTIC");
      System.out.println(connectionTags[this.connectionNo] + " session: "
              + "UPDATE test_tx_optimistic SET name = '" + connectionTags[this.connectionNo]
              + "' WHERE id = " + rowid);
      s.executeUpdate("UPDATE test_tx_optimistic SET name = '" + connectionTags[this.connectionNo]
              + "' WHERE id = " + rowid);
      try {
          Thread.sleep(this.waitBefore1stCommit);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      System.out.println(connectionTags[this.connectionNo] + " session: " + "Commit");
      c.commit();
      ```

      コンフリクトが発生した場合、次のブロックは、アプリケーション側のコードがどのようにトランザクションを再試行できるかを示しています。
      ```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();
          }
      }
      ```

2. オプション `local no-retry` を指定してスクリプト `09-demo-jdbc-tx-optimistic-01-show.sh` を実行し、結果を確認します。コード`9007` エラーが予想され、トランザクションの再試行は行われません。トランザクションを正常に完了できる接続は1つだけです。
   ```
   ./09-demo-jdbc-tx-optimistic-01-show.sh local no-retry
   ```
   ```
   $ ./09-demo-jdbc-tx-optimistic-01-show.sh local no-retry
   TiDB endpoint: 127.0.0.1
   TiDB username: root
   Default TiDB server port: 4000
   Security options: 
   Connection established.
   Connection B session started
   Connection A session started
   Connection A session: BEGIN OPTIMISTIC
   Connection A session: UPDATE test_tx_optimistic SET name = 'Connection A' WHERE id = 3170534137668829185
   Connection B session: BEGIN OPTIMISTIC
   Connection B session: UPDATE test_tx_optimistic SET name = 'Connection B' WHERE id = 3170534137668829185
   Connection B session: Commit
   Connection B session: Checking result

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

   Connection A session: Commit
   Connection A ErrorCode: 9007
   Connection A SQLState: HY000
   Connection A Error: java.sql.SQLException: Write conflict, txnStartTS=442128584348794883, conflictStartTS=442128584610938881, conflictCommitTS=442128585135226882, key={tableID=92, tableName=test.test_tx_optimistic, handle=3170534137668829185}, originalKey=74800000000000005c5f72ac00000000000001, primary={tableID=92, tableName=test.test_tx_optimistic, handle=3170534137668829185}, originalPrimaryKey=74800000000000005c5f72ac00000000000001, 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
     2) 3170534137668829185, Connection B
   ```

3. オプション `local retry` を指定してスクリプト `09-demo-jdbc-tx-optimistic-01-show.sh` を実行し、結果を確認します。今回は再試行が行われるため、どちらのトランザクションも成功するはずです。
   ```
   ./09-demo-jdbc-tx-optimistic-01-show.sh local retry
   ```
   ```
   $ ./09-demo-jdbc-tx-optimistic-01-show.sh local retry
   TiDB endpoint: 127.0.0.1
   TiDB username: root
   Default TiDB server port: 4000
   Security options: 
   Connection established.
   Connection B session started
   Connection A session started
   Connection A session: BEGIN OPTIMISTIC
   Connection A session: UPDATE test_tx_optimistic SET name = 'Connection A' WHERE id = 1441151880758558721
   Connection B session: BEGIN OPTIMISTIC
   Connection B session: UPDATE test_tx_optimistic SET name = 'Connection B' WHERE id = 1441151880758558721
   Connection B session: Commit
   Connection B session: Checking result

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

   Connection A session: Commit
   Connection A ErrorCode: 9007
   Connection A SQLState: HY000
   Connection A Error: java.sql.SQLException: Write conflict, txnStartTS=442128746578182147, conflictStartTS=442128746840326145, conflictCommitTS=442128747364876290, key={tableID=95, tableName=test.test_tx_optimistic, handle=1441151880758558721}, originalKey=74800000000000005f5f729400000000000001, primary={tableID=95, tableName=test.test_tx_optimistic, handle=1441151880758558721}, originalPrimaryKey=74800000000000005f5f729400000000000001, 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
     2) 1441151880758558721, Connection A
   ```

## 演習終わり