# TiDB 8.1 および 8.5 の新機能

## 概要

この演習では、TiDB 8.x に導入された主要ないくつかの新機能を取り扱います。ここでは、TiProxy、外部キー、パーティションテーブルのグローバルユニークインデックス、インデックス使用統計のモニタリング、ベクトル検索、および TiKV MVCC インメモリエンジンの6つの重要なモジュールに注目します。TiDB v8 に導入された新機能の完全なリストについては、[TiDB リリースノート](https://docs.pingcap.com/tidb/stable/release-notes)をご覧ください。

### 所要時間

+ この演習は約 90 分程度かかります。

### 前提条件

+ SQL とデータベース管理の基本的な理解。
+ MySQL または同様のリレーショナルデータベース、および何らかの分散型 NoSQL データベースの経験があることが望ましい。
+ TiDB の[コアコンポーネント](https://docs.pingcap.com/tidb/stable/tidb-architecture/)および[MVCC](https://docs.pingcap.com/tidb/stable/tidb-storage/#mvcc)に精通していること。
+ [TiUP](https://docs.pingcap.com/tidb/stable/tiup-documentation-guide/)ユーティリティに精通していること。

> [!TIP]
> 演習環境を起動するには、**開始**をクリックしてください。
> 環境の準備が整ったら、下部のツールバーで **次のモジュール** を選択して **モジュール 1** に進みます。
> そして、ステップバイステップの演習ガイドに従って、ハンズオン環境に接続し、タスクを開始してください。

## モジュール 1: ロードバランサーとセッションマネージャー - TiProxy

TiProxyはTiDBの公式なプロキシコンポーネントであり、ロードバランシング、接続の持続性、およびサービスディスカバリを提供することでデータベースの機能を強化するように設計されています。クライアントとTiDBサーバーの間の仲介役として機能し、スケーリング、ローリングアップグレード、および再起動などのさまざまなメンテナンス操作中にクライアント接続が安定し、中断されないようにします。これは、計画的なサーバーダウンタイム中や複数のTiDBサーバーインスタンス間でワークロードをバランスさせる必要があるシナリオで特に役立ちます。

このモジュールでは、新しいTiDBクラスタとTiProxyコンポーネントをデプロイし、そのロードバランシングとセッション移行機能を検証します。

### 手順 1. TiUP コントロールマシンに接続する

+ ここをクリック - [ターミナル 1](ssh://monitor1-ssh#1) で、ハンズオン演習用に割り当てられた仮想マシンに接続された新しいターミナルを開きます。

+ ログイン後に表示されるバナー：

    ```output
     ,     #_
    ~\_  ####_        Amazon Linux 2
    ~~  \_#####\
    ~~     \###|      
    ~~       \#/ ___
    ~~       V~' '->
    ~~~         /    A newer version of Amazon Linux is available!
     ~~._.   _/
        _/ _/       Amazon Linux 2023, GA and supported until 2028-03-15.
      _/m/'           https://aws.amazon.com/linux/amazon-linux-2023/
    ```

+ このハンズオン演習で割り当てられたすべてのサーバーインスタンスのIPアドレスの概要を取得します。環境変数のプレフィックスは、それぞれのTiDBクラスタ内の役割を表します。`sample_output`に表示されるIPアドレスは、実際のものとは異なることに注意してください。

  + `HOST_MONITOR`はPrometheusとGrafanaをホストします。
  + `HOST_CM`はTiUPコントロールマシンをホストします。
  + `HOST_PD`はPlacement Driverサーバー用です。
  + `HOST_DB`はTiDBサーバー用です。
  + `HOST_KV`はTiKVサーバー用です。
  + `HOST_TIFALSH`はTiFlashサーバー用です。
  + `HOST_TIPROXY`はTiProxyサーバー用です。

  ```bash
  cat hosts-env.sh
  ```
  ```output
  export HOST_MONITOR1_PRIVATE_IP=10.90.4.40
  export HOST_MONITOR1_PUBLIC_IP=35.166.47.245
  export HOST_CM_PRIVATE_IP=10.90.4.40
  export HOST_CM_PUBLIC_IP=35.166.47.245
  export HOST_PD1_PRIVATE_IP=10.90.2.51
  export HOST_PD1_PUBLIC_IP=52.27.45.10
  export HOST_DB1_PRIVATE_IP=10.90.2.14
  export HOST_DB1_PUBLIC_IP=44.244.238.105
  export HOST_DB2_PRIVATE_IP=10.90.2.43
  export HOST_DB2_PUBLIC_IP=34.219.136.55
  export HOST_KV1_PRIVATE_IP=10.90.1.93
  export HOST_KV1_PUBLIC_IP=52.12.133.66
  export HOST_KV2_PRIVATE_IP=10.90.1.30
  export HOST_KV2_PUBLIC_IP=18.236.119.16
  export HOST_KV3_PRIVATE_IP=10.90.1.208
  export HOST_KV3_PUBLIC_IP=35.90.161.51
  export HOST_TIFLASH1_PRIVATE_IP=10.90.4.243
  export HOST_TIFLASH1_PUBLIC_IP=34.210.114.26
  export HOST_TIPROXY1_PRIVATE_IP=10.90.4.154
  ```



### 手順 2. TiProxy を備えた新しい TiDB クラスタを作成する

+ TiDBクラスタをデプロイするために使用される提供されたトポロジファイル`topology-with-tiproxy.yaml`を確認します。トポロジファイル内のTiProxy設定（`security.auto-tls: true`、TiDBサーバー構成の下の`graceful-wait-before-shutdown: 10`、および`tiproxy_servers`）に注意してください。`sample_output`に表示されるIPアドレスは、実際のものとは異なることに注意してください。

  ```bash
  cat topology-with-tiproxy.yaml
  ```
  ```output
  ...
  server_configs:
  ...
    tidb:
      security.auto-tls: true
      graceful-wait-before-shutdown: 10
  ...
  tiproxy_servers:
    - host: 10.90.4.113
  ```

+ 次のコードブロックのコマンドは、一連の操作を順番に実行します。これらは、1. [TiUPミラー](https://docs.pingcap.com/tidb/stable/production-deployment-using-tiup)設定の変更、2. システム設定の確認、3. 失敗したチェックの修正、4. TiDBクラスタのデプロイ、最後に5. TiDBクラスタの起動です。完了するまでに約3分かかります。
   
  ```bash
  tiup mirror set tidb-community-server-tidb_version-linux-amd64
  tiup cluster check ./topology-with-tiproxy.yaml \
   --user ec2-user \
   -i /home/ec2-user/.ssh/pe-class-key-oregon.pem \
   --apply
  tiup cluster deploy tidb-demo tidb_version ./topology-with-tiproxy.yaml \
   --user ec2-user \
   -i /home/ec2-user/.ssh/pe-class-key-oregon.pem \
   --yes
  tiup cluster start tidb-demo
  sleep 5;
  tiup cluster display tidb-demo
  ```
  ```output
  Successfully set mirror to /home/ec2-user/tidb-community-server-tidb_version-linux-amd64
  /home/ec2-user/tidb-community-server-tidb_version-linux-amd64
  ...
  Cluster type:       tidb
  Cluster name:       tidb-demo
  Cluster version:    v8.5.1
  Deploy user:        ec2-user
  SSH type:           builtin
  Dashboard URL:      http://52.27.45.10:2379/dashboard
  Grafana URL:        http://10.90.4.40:3000
  ID                 Role          Host         Ports                            OS/Arch       Status  Data Dir                      Deploy Dir
  --                 ----          ----         -----                            -------       ------  --------                      ----------
  10.90.4.40:9093    alertmanager  10.90.4.40   9093/9094                        linux/x86_64  Up      /tidb-data/alertmanager-9093  /tidb-deploy/alertmanager-9093
  10.90.4.40:3000    grafana       10.90.4.40   3000                             linux/x86_64  Up      -                             /tidb-deploy/grafana-3000
  10.90.2.51:2379    pd            10.90.2.51   2379/2380                        linux/x86_64  Up|L    /tidb-data/pd-2379            /tidb-deploy/pd-2379
  10.90.4.40:9090    prometheus    10.90.4.40   9090/12020                       linux/x86_64  Up      /tidb-data/prometheus-9090    /tidb-deploy/prometheus-9090
  10.90.2.14:4000    tidb          10.90.2.14   4000/10080                       linux/x86_64  Up      -                             /tidb-deploy/tidb-4000
  10.90.2.43:4000    tidb          10.90.2.43   4000/10080                       linux/x86_64  Up      -                             /tidb-deploy/tidb-4000
  10.90.4.243:9000   tiflash       10.90.4.243  9000/3930/20170/20292/8234/8123  linux/x86_64  Down    /tidb-data/tiflash-9000       /tidb-deploy/tiflash-9000
  10.90.1.208:20160  tikv          10.90.1.208  20160/20180                      linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
  10.90.1.30:20160   tikv          10.90.1.30   20160/20180                      linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
  10.90.1.93:20160   tikv          10.90.1.93   20160/20180                      linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
  10.90.4.154:6000   tiproxy       10.90.4.154  6000/3080                        linux/x86_64  Up      -                             /tidb-deploy/tiproxy-6000
  Total nodes: 11
  ```

+ `tiproxy`コンポーネントもクラスタ表示出力にリストされていることに注意してください。このSelf-Managedクラスタは、1つのTiProxyインスタンスと2つのTiDBサーバーインスタンスで構成されています。さらに、通常は少なくとも3つのPDサーバーを使用しますが、このハンズオン演習ではPD関連のトピックはカバーしていないため、ここでは1つのPDサーバーのみを使用しています。

### 手順 3. ロードバランシング機能を検証する

+ デフォルトでは、TiProxyインスタンスはポート6000でリッスンします。TiProxy経由でTiDBサーバーインスタンスに接続します。

  ```bash
  mysql -h ${HOST_TIPROXY1_PRIVATE_IP} --port 6000 -u root
  ```
  ```output
  mysql> 
  ```

+ `test.tiproxy_test`という名前のテーブルを作成し、接続を終了します。これを使用して、TiProxyの接続ロードバランシング機能を確認します。

  ```sql
  DROP TABLE IF EXISTS test.tiproxy_test; 
  CREATE TABLE test.tiproxy_test (id INT AUTO_INCREMENT PRIMARY KEY, tidb_server_instance VARCHAR(30));
  EXIT
  ```
  ```output
  Query OK, 0 rows affected, 1 warning (0.00 sec)
  Query OK, 0 rows affected (0.06 sec)
  Bye
  ```

+ TiProxy経由で50のデータベース接続を生成し、各接続で新しい行をインサートし、接続を終了します。`test.tiproxy_test`テーブルの`tidb_server_instance`列は、接続を受け入れてインサートDMLを処理したTiDBサーバーインスタンスのID（**TIDB_SERVER_IP_ADDRESS:10080**形式）を示します。

  ```bash
  cat > insert_one_row.sql <<EOF
  INSERT INTO test.tiproxy_test (tidb_server_instance)
  VALUES (
      (SELECT instance FROM information_schema.cluster_processlist
       WHERE host=(SELECT host FROM information_schema.processlist WHERE id=CONNECTION_ID())
      )
  );
  EOF
  cat > insert_one_row.sh <<EOF
  mysql -h ${HOST_TIPROXY1_PRIVATE_IP} --port 6000 -u root < insert_one_row.sql
  EOF
  chmod +x insert_one_row.sh
  for c in {1..50}; do
  ./insert_one_row.sh &
  done;
  sleep 1;
  jobs
  ```
  ```output
  ...
  [43]   Done                    ./insert_one_row.sh
  [44]   Done                    ./insert_one_row.sh
  [45]   Done                    ./insert_one_row.sh
  [46]   Done                    ./insert_one_row.sh
  [47]   Done                    ./insert_one_row.sh
  [48]   Done                    ./insert_one_row.sh
  [49]-  Done                    ./insert_one_row.sh
  [50]+  Done                    ./insert_one_row.sh
  ```

+ TiDBサーバーインスタンスに接続します。

  ```bash
  mysql -h ${HOST_TIPROXY1_PRIVATE_IP} --port 6000 -u root
  ```

+ `test.tiproxy_test`テーブルを確認します。接続とインサート操作が2つのTiDBサーバーインスタンスによって均等に処理されたことがわかります。接続数にわずかな差異（例：28対22）がある場合、これは正常で予想されることです。

  ```sql
  SELECT tidb_server_instance, COUNT(1)
  FROM test.tiproxy_test
  GROUP BY tidb_server_instance;
  EXIT
  ```
  ```output
  +----------------------+----------+
  | tidb_server_instance | count(1) |
  +----------------------+----------+
  | 10.90.1.14:10080     |       25 |
  | 10.90.1.43:10080     |       25 |
  +----------------------+----------+
  2 rows in set (0.00 sec)
  Bye
  ```

### 手順 4. セッション移行機能を検証する

+ 計画的なメンテナンス操作中（例：TiDBのスケールアウト、スケールイン、またはクラスタのアップグレード）、TiProxyは既存のセッションをアクティブなTiDBサーバーインスタンス間でシームレスに移行することで、クライアントアプリケーションの中断のない接続を保証します。TiProxyの主な目標は、アプリケーションのダウンタイムをゼロにしながら、TiDBサーバー間でバランスの取れた負荷を維持することです。

+ 提供されたミニJavaプログラムのコードを確認します。これは、`test.tiproxy_test`テーブルに対して挿入ワークロードを実行するために4つのスレッドを初期化します。

  ```bash
  cat InsertJobNoExceptionHandling.java | grep -A1 -B1 'new Thread'
  ```
  ```output
          for (int i = 0; i < 4; i++) {
              new Thread(new Worker(tidbHost, port, dbUsername, dbPassword, (i + 1))).start();
          }
  ```

+ スレッドが例外に遭遇した場合、エラースタックを出力して終了します。

  ```bash
  cat InsertJobNoExceptionHandling.java | grep -A3 catch
  ```
  ```output
          } catch (Exception e) {
              e.printStackTrace();
              System.exit(1);
          }
  ```

+ ポート6000でTiProxyに対してワークロードを開始します。4つのスレッドを使用してTiDBクラスタに接続し、4つのデータベース接続が確立されます。

  ```bash
  rm -f InsertJobNoExceptionHandling.class
  javac -cp .:mysql-connector-java-5.1.36-bin.jar InsertJobNoExceptionHandling.java
  sleep 1
  java -cp .:mysql-connector-java-5.1.36-bin.jar InsertJobNoExceptionHandling ${HOST_TIPROXY1_PRIVATE_IP} 6000 root
  ```
  ```output
  Inserting 0 by worker 3
  Inserting 0 by worker 1
  Inserting 0 by worker 4
  Inserting 0 by worker 2
  Inserting 1 by worker 1
  Inserting 1 by worker 2
  Inserting 1 by worker 3
  Inserting 1 by worker 4
  Inserting 2 by worker 1
  Inserting 2 by worker 3
  Inserting 2 by worker 2
  Inserting 2 by worker 4
  ...
  ```

+ [Grafana Prometheus Dashboard](http://${LAB:HOST_MONITOR1_PUBLIC_IP}:3000) をクリックし、ユーザー名とパスワード（両方とも「admin」）を使用してログインします。`Home` > `tidb-demo` > `tidb-demo-TiProxy-Summary` > `Balance`に移動します。`Backend Connections`パネルを確認し、各TiDBサーバーに2つの接続があることを確認します。

    ![90%](https://lab-static.pingcap.com/quick-demo/TiProxyBalance_2x2.png)

+ `Refresh time`を`10s`に変更します。 `Balance`パネルをブラウザで開いたままにし、Javaプログラムを実行し続けます。

+ ここをクリック - [ターミナル 2](ssh://monitor1-ssh#2) で、ハンズオン演習用に割り当てられた仮想マシンに接続された新しいターミナルを開きます。

+ ログイン後に表示されるバナー：

    ```output
     ,     #_
    ~\_  ####_        Amazon Linux 2
    ~~  \_#####\
    ~~     \###|      
    ~~       \#/ ___
    ~~       V~' '->
    ~~~         /    A newer version of Amazon Linux is available!
     ~~._.   _/
        _/ _/       Amazon Linux 2023, GA and supported until 2028-03-15.
      _/m/'           https://aws.amazon.com/linux/amazon-linux-2023/
    ```

+ SQLレイヤーをスケールインし、IPアドレス${HOST_DB2_PRIVATE_IP}のTiDBサーバーをクラスタから削除します。完了すると、クラスタには1つのTiDBサーバーのみが残ります。

  ```bash
  tiup cluster scale-in tidb-demo --node ${HOST_DB2_PRIVATE_IP}:4000 -y
  sleep 2
  tiup cluster display tidb-demo
  ```
  ```output
  ...
  Stopping component tidb
  ...
  Scaled cluster `tidb-demo` in successfully
  ...
  Cluster type:       tidb
  Cluster name:       tidb-demo
  Cluster version:    v8.5.1
  Deploy user:        ec2-user
  SSH type:           builtin
  Dashboard URL:      http://13.115.186.238:2379/dashboard
  Grafana URL:        http://10.90.4.53:3000
  ID                 Role          Host         Ports                            OS/Arch       Status  Data Dir                      Deploy Dir
  --                 ----          ----         -----                            -------       ------  --------                      ----------
  10.90.4.53:9093    alertmanager  10.90.4.53   9093/9094                        linux/x86_64  Up      /tidb-data/alertmanager-9093  /tidb-deploy/alertmanager-9093
  10.90.4.53:3000    grafana       10.90.4.53   3000                             linux/x86_64  Down    -                             /tidb-deploy/grafana-3000
  10.90.3.19:2379    pd            10.90.3.19   2379/2380                        linux/x86_64  Up|L    /tidb-data/pd-2379            /tidb-deploy/pd-2379
  10.90.4.53:9090    prometheus    10.90.4.53   9090/12020                       linux/x86_64  Up      /tidb-data/prometheus-9090    /tidb-deploy/prometheus-9090
  10.90.1.202:4000   tidb          10.90.1.202  4000/10080                       linux/x86_64  Up      -                             /tidb-deploy/tidb-4000
  10.90.4.75:9000    tiflash       10.90.4.75   9000/3930/20170/20292/8234/8123  linux/x86_64  Up      /tidb-data/tiflash-9000       /tidb-deploy/tiflash-9000
  10.90.3.10:20160   tikv          10.90.3.10   20160/20180                      linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
  10.90.3.207:20160  tikv          10.90.3.207  20160/20180                      linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
  10.90.3.208:20160  tikv          10.90.3.208  20160/20180                      linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
  10.90.4.56:6000    tiproxy       10.90.4.56   6000/3080                        linux/x86_64  Up      -                             /tidb-deploy/tiproxy-6000
  Total nodes: 10
  ```

+ [ターミナル 1](ssh://monitor1-ssh#1) で、ミニJavaプログラムは中断なく実行を続けます。

  ```output
  ...
  Inserting 1295 by worker 4
  Inserting 1295 by worker 3
  Inserting 1295 by worker 1
  Inserting 1295 by worker 2
  Inserting 1296 by worker 4
  Inserting 1296 by worker 3
  ...
  ```

+ ブラウザから Grafana TiProxy `Balance`パネルを確認します。4つの接続が単一のTiDBサーバーにあります。2つの接続が移行されました。

![90%](https://lab-static.pingcap.com/quick-demo/TiProxyBalance_4x0.png)

+ [ターミナル 2](ssh://monitor1-ssh#2) に切り替え、SQLレイヤーをスケールアウトしてTiDBサーバーをクラスタに追加します。完了すると、クラスタには2つのTiDBサーバーが存在します。

  ```bash
  tiup cluster scale-out tidb-demo scale-out-tidb.yaml \
   --user ec2-user \
   -i ~/.ssh/pe-class-key-oregon.pem \
   -y
  tiup cluster display tidb-demo
  ```
  ```output
  ...
  Deploy TiDB instance
  ...
  Scaled cluster `tidb-demo` out successfully
  ...
  Cluster type:       tidb
  Cluster name:       tidb-demo
  Cluster version:    v8.5.1
  Deploy user:        ec2-user
  SSH type:           builtin
  Dashboard URL:      http://13.115.186.238:2379/dashboard
  Grafana URL:        http://10.90.4.53:3000
  ID                 Role          Host         Ports                            OS/Arch       Status  Data Dir                      Deploy Dir
  --                 ----          ----         -----                            -------       ------  --------                      ----------
  10.90.4.53:9093    alertmanager  10.90.4.53   9093/9094                        linux/x86_64  Up      /tidb-data/alertmanager-9093  /tidb-deploy/alertmanager-9093
  10.90.4.53:3000    grafana       10.90.4.53   3000                             linux/x86_64  Up      -                             /tidb-deploy/grafana-3000
  10.90.3.19:2379    pd            10.90.3.19   2379/2380                        linux/x86_64  Up|L    /tidb-data/pd-2379            /tidb-deploy/pd-2379
  10.90.4.53:9090    prometheus    10.90.4.53   9090/12020                       linux/x86_64  Up      /tidb-data/prometheus-9090    /tidb-deploy/prometheus-9090
  10.90.1.202:4000   tidb          10.90.1.202  4000/10080                       linux/x86_64  Up      -                             /tidb-deploy/tidb-4000
  10.90.1.233:4000   tidb          10.90.1.233  4000/10080                       linux/x86_64  Up      -                             /tidb-deploy/tidb-4000
  10.90.4.75:9000    tiflash       10.90.4.75   9000/3930/20170/20292/8234/8123  linux/x86_64  Up      /tidb-data/tiflash-9000       /tidb-deploy/tiflash-9000
  10.90.3.10:20160   tikv          10.90.3.10   20160/20180                      linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
  10.90.3.207:20160  tikv          10.90.3.207  20160/20180                      linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
  10.90.3.208:20160  tikv          10.90.3.208  20160/20180                      linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
  10.90.4.56:6000    tiproxy       10.90.4.56   6000/3080                        linux/x86_64  Up      -                             /tidb-deploy/tiproxy-6000
  Total nodes: 11
  ```

+ 再び[ターミナル 1](ssh://monitor1-ssh#1) に戻ると、ミニJavaプログラムが中断なく実行を続けていることがわかります。

  ```output
  ...
  Inserting 1707 by worker 1
  Inserting 1707 by worker 3
  Inserting 1706 by worker 4
  Inserting 1708 by worker 2
  Inserting 1708 by worker 1
  Inserting 1708 by worker 3
  Inserting 1707 by worker 4
  ...
  ```

+ 再度、ブラウザからGrafana TiProxy `Balance`パネルを確認します。各TiDBサーバーに2つの接続があります。2つの接続が移行されました。

![90%](https://lab-static.pingcap.com/quick-demo/TiProxyBalance_2x2_2.png)

+ これで、TiProxyのセッション/接続移行機能が完了しました。TiProxyを使用した計画的なデータベースオンラインローリングアップグレードをサポートするために、これを実証するための別のスタンドアロンのハンズオン演習があります。詳細については、[Database Zero Downtime Online Upgrade With TiProxy](https://labs.tidb.io/labs/demo_005)をご覧ください。

+ [ターミナル 1](ssh://monitor1-ssh#1) に移動し、**CTRL-C**を押してJavaワークロードを停止します。その後、次のモジュールに進みます。

## モジュール 2: 外部キー (v8.5.0)

外部キーは従来のRDBMSでは一般的ですが、分散データベースではほとんど見られません。多くの分散データベースは外部キーを実装しておらず、データベース内のデータ整合性を維持することが課題となっています。しかし、分散データベースとして、TiDBは外部キーをサポートしています。

TiDBの外部キー制約を使用すると、テーブル間の関係を定義し、これらの関係を利用してデータの整合性を強制できます。基本的なレベルでは、外部キー制約は、子テーブルの列セットが親テーブルの列セットに対応し、整合性を壊す可能性のある操作を禁止することを保証します。注文テーブルと顧客テーブルの例を考えてみましょう。注文テーブルの外部キー制約は、すべての注文が既存の顧客に対応することを保証し、データの正確性と整合性を維持します。

### 手順 1. bookshop スキーマを作成し、TiDB SQL レイヤーを介してデータをロードする

+ [ターミナル 1](ssh://monitor1-ssh#1) で、TiDBサーバーインスタンスに接続します。

  ```bash
  mysql -h ${HOST_TIPROXY1_PRIVATE_IP} --port 6000 -u root
  ```
  ```output
  mysql> 
  ```

+ `mysql>`プロンプトで次のコマンドを実行して、データベースオブジェクトを作成し、ブックショップスキーマのデータをロードします。このスクリプトは`bookshop`というスキーマを作成します。

  ```sql
  SOURCE bookshop_demo.sql
  SHOW TABLES;
  ```
  ```output
  Query OK, 0 rows affected (0.08 sec)
  ...
  +--------------------+
  | Tables_in_bookshop |
  +--------------------+
  | authors            |
  | book_authors       |
  | books              |
  | orders             |
  | ratings            |
  | users              |
  +--------------------+
  6 rows in set (0.00 sec)
  ```

+ 前のステップで実行された`bookshop_demo.sql`スクリプトの内容の抜粋を以下に示します。これは、ブックショップアプリケーションのテーブルとインデックスの作成を示しています。これらのステートメントはすでに実行されているため、今は実行する必要はありませんが、アプリケーションのデータをどのようにモデル化したかを確認できます。`AUTO_RANDOM`を除いて、すべての宣言がMySQLで使用されるものと同じであることに注意してください。

  ```output
  CREATE TABLE `authors` (
   `id` bigint(20) AUTO_RANDOM,
   `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
   `gender` tinyint(1) DEFAULT NULL,
   `birth_year` smallint(6) DEFAULT NULL,
   `death_year` smallint(6) DEFAULT NULL,
   PRIMARY KEY (`id`)
  );

  CREATE TABLE `book_authors` (
   `book_id` bigint(20) NOT NULL,
   `author_id` bigint(20) NOT NULL,
   PRIMARY KEY (`book_id`, `author_id`)
  );

  CREATE TABLE `books` (
  `id` bigint AUTO_RANDOM,
  `title` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `type` enum(
      'Magazine',
      'Novel',
      'Life',
      'Arts',
      'Comics',
      'Education & Reference',
      'Humanities & Social Sciences',
      'Science & Technology',
      'Kids',
      'Sports'
  ) COLLATE utf8mb4_unicode_ci NOT NULL,
  `published_at` datetime NOT NULL,
  `stock` int DEFAULT '0',
  `price` decimal(15, 2) DEFAULT '0.0',
  PRIMARY KEY (`id`)
  ) PARTITION BY HASH (`id`) PARTITIONS 8;

  CREATE TABLE `orders` (
   `id` bigint(20) NOT NULL,
   `book_id` bigint(20) NOT NULL,
   `user_id` bigint(20) NOT NULL,
   `quality` tinyint(4) NOT NULL,
   `ordered_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
   PRIMARY KEY (`id`),
   KEY `orders_book_id_idx` (`book_id`)
  );

  CREATE TABLE `ratings` (
   `book_id` bigint(20) NOT NULL,
   `user_id` bigint(20) NOT NULL,
   `score` tinyint(4) NOT NULL,
   `rated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
   UNIQUE KEY `uniq_book_user_idx` (`book_id`, `user_id`),
   PRIMARY KEY (`book_id`, `user_id`)
  );
  
  CREATE TABLE `users` (
   `id` bigint(20) AUTO_RANDOM,
   `balance` decimal(15, 2) DEFAULT '0.0',
   `nickname` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `nickname` (`nickname`)
  );
  ```

+ MySQLコマンドを使用して、スキーマを探索し、データベース内のオブジェクトを説明できます。これは、任意のMySQLデータベースと同様です。

  ```sql
  DESC bookshop.books;
  ```
  ```output
  +--------------+-----------------------------------------------------------------------------------------------------------------------------------------------+------+------+---------+-------+
  | Field        | Type                                                                                                                                          | Null | Key  | Default | Extra |
  +--------------+-----------------------------------------------------------------------------------------------------------------------------------------------+------+------+---------+-------+
  | id           | bigint(20)                                                                                                                                    | NO   | PRI  | NULL    |       |
  | title        | varchar(100)                                                                                                                                  | NO   |      | NULL    |       |
  | type         | enum('Magazine','Novel','Life','Arts','Comics','Education & Reference','Humanities & Social Sciences','Science & Technology','Kids','Sports') | NO   |      | NULL    |       |
  | published_at | datetime                                                                                                                                      | NO   |      | NULL    |       |
  | stock        | int(11)                                                                                                                                       | YES  |      | 0       |       |
  | price        | decimal(15,2)                                                                                                                                 | YES  |      | 0.0     |       |
  +--------------+-----------------------------------------------------------------------------------------------------------------------------------------------+------+------+---------+-------+
  6 rows in set (0.00 sec)
  ```

+ `mysql> `プロンプトで、次のクエリを実行して、いくつかの本のレコードを表示します。
   
  ```sql
  SELECT * FROM bookshop.books ORDER BY title LIMIT 3;
  ```
  ```output
  +----+-------------------------------+------------------------------+---------------------+--------+-------+
  | id | title                         | type                         | published_at        | stock  | price |
  +----+-------------------------------+------------------------------+---------------------+--------+-------+
  | 43 | (I've Got a Gal In) Kalamazoo | Sports                       | 2006-05-22 05:19:49 | 231321 | 85.72 |
  | 88 | (Let Me Be Your) Teddy Bear   | Sports                       | 2003-04-08 08:37:52 |    140 | 22.14 |
  | 40 | (You Keep Me) Hangin' On      | Humanities & Social Sciences | 2005-02-12 05:29:04 |    163 | 32.72 |
  +----+-------------------------------+------------------------------+---------------------+--------+-------+
  3 rows in set (0.05 sec)
  ```

### 手順 2. 外部キーで参照整合性を強制する

+ 例のブックショップスキーマでは、モジュール1でスキーマ作成中に外部キーを追加しませんでした。次に、以下の図に示すエンティティ関係を強制する必要があります。

  ```mermaid
  ---
  title: Book Authors Example
  ---
  erDiagram
    AUTHORS ||--|{ BOOK_AUTHORS : references
        AUTHORS {
            BIGINT id PK
            VARCHAR name
            TINYINT gender
            SMALLINT birth_year
            SMALLINT death_year
        }
    BOOK_AUTHORS {
        BIGINT book_id   PK 
        BIGINT author_id PK,FK
    }
  ```

+ `book_authors.auth_id`と`authors.id`に対して`book_authors`と`authors`の間の参照制約を強制するために、次のコマンドを実行します。`mysql> `プロンプトでTerminal 1で実行します。

  ```sql
  ALTER TABLE bookshop.book_authors
      ADD CONSTRAINT book_author_fk 
      FOREIGN KEY (author_id) REFERENCES bookshop.authors (id); 
  ```
  ```output
  Query OK, 0 rows affected (0.10 sec)
  ```

+ 宣言したばかりの外部キー制約に違反するレコードを`book_authors`に追加しようとします。`auth_id`が99999999の著者が存在しないため、**エラー**が予想されます。

  ```sql
  INSERT INTO bookshop.book_authors (book_id, author_id) VALUES (100, 99999999);
  ```
  ```output
  ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`bookshop`.`book_authors`, CONSTRAINT `book_author_fk` FOREIGN KEY (`author_id`) REFERENCES `authors` (`id`))
  ```

+ 宣言したばかりの外部キー制約に一致するレコードを`book_authors`に追加しようとします。インサートの成功が予想されます。たとえば、Dr. Lindsey Welch II（著者IDが1の既存の著者）が「Till The End of Time」（書籍IDが100の既存の本）の著者でもあると宣言します。

  ```sql
  INSERT INTO bookshop.book_authors (book_id, author_id) VALUES (100, 1);
  ```
  ```output
  Query OK, 1 row affected (0.00 sec)
  ```

+ この時点で、分散データベース - TiDBの外部キー制約機能を体験しました。mysqlクライアント接続を開いたままにして、次のモジュールに進みます。

## モジュール 3: パーティションテーブルのグローバル一意インデックス (v8.5.0)

インデックスと同様に、データベースのテーブルパーティションはデータ検索操作を高速化するためのデータ構造です。クエリが実行されると、テーブルパーティションはクエリ条件を満たすデータを迅速に見つけることができ、テーブル全体を順次スキャンする（`TableFullScan`）のではなく、クエリ効率を向上させます。

グローバルインデックスの導入前は、TiDBは各パーティションのローカルインデックスをサポートしており、主キーと一意キーはデータの一意性を確保するためにパーティションキーを含める必要がありました。さらに、複数のパーティションにまたがるデータをクエリする場合、TiDBは各パーティションのデータをスキャンして結果を返す必要がありました。

これらの問題に対処するために、TiDBはグローバルインデックス機能を導入しました。グローバルインデックスは単一のインデックスでテーブル全体のデータをカバーし、主キーと一意キーがすべてのパーティションキーを含めることなくグローバルな一意性を維持できるようにします。さらに、グローバルインデックスは複数のパーティションにまたがるインデックスデータに単一の操作でアクセスでき、非パーティションキーのクエリパフォーマンスを大幅に向上させます。

### 手順 1. パーティションテーブルの特性を確認する

+ モジュール1で、すでにパーティションテーブル - `books`を作成しました。これは8つのハッシュパーティションで`id`列にパーティションされています。

+ 次のSQLコマンドを実行してテーブルパーティションを確認します。出力の`PARTITION BY`句に注目してください。

  ```sql
  SHOW CREATE TABLE books\G
  ```
  ```output
  *************************** 1. row ***************************
         Table: books
  Create Table: CREATE TABLE `books` (
    `id` bigint NOT NULL /*T![auto_rand] AUTO_RANDOM(5) */,
    `title` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
    `type` enum('Magazine','Novel','Life','Arts','Comics','Education & Reference','Humanities & Social Sciences','Science & Technology','Kids','Sports') COLLATE utf8mb4_unicode_ci NOT NULL,
    `published_at` datetime NOT NULL,
    `stock` int DEFAULT '0',
    `price` decimal(15,2) DEFAULT '0.0',
    PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![auto_rand_base] AUTO_RANDOM_BASE=30002 */
  PARTITION BY HASH (`id`) PARTITIONS 8
  1 row in set (0.00 sec)
  ```
  ```sql
  SELECT table_name, partition_name, partition_method
  FROM information_schema.partitions
  WHERE table_name='books';
  ```
  ```output
  +------------+----------------+------------------+
  | table_name | partition_name | partition_method |
  +------------+----------------+------------------+
  | books      | p0             | HASH             |
  | books      | p1             | HASH             |
  | books      | p2             | HASH             |
  | books      | p3             | HASH             |
  | books      | p4             | HASH             |
  | books      | p5             | HASH             |
  | books      | p6             | HASH             |
  | books      | p7             | HASH             |
  +------------+----------------+------------------+
  8 rows in set (0.01 sec)
  ```

+ `books`テーブルは8つのパーティションでパーティション化されているため、行を格納するために少なくとも8つのTiKVリージョンがあります。

  ```sql
  SHOW TABLE books REGIONS;
  ```
  ```output
  +-----------+-----------+---------+-----------+-----------------+---------------+------------+---------------+------------+----------------------+------------------+------------------------+------------------+
  | REGION_ID | START_KEY | END_KEY | LEADER_ID | LEADER_STORE_ID | PEERS         | SCATTERING | WRITTEN_BYTES | READ_BYTES | APPROXIMATE_SIZE(MB) | APPROXIMATE_KEYS | SCHEDULING_CONSTRAINTS | SCHEDULING_STATE |
  +-----------+-----------+---------+-----------+-----------------+---------------+------------+---------------+------------+----------------------+------------------+------------------------+------------------+
  |       286 | t_119_    | t_120_  |       287 |               1 | 287, 288, 289 |          0 |             0 |       1301 |                    1 |                8 |                        |                  |
  |       290 | t_120_    | t_121_  |       291 |               1 | 291, 292, 293 |          0 |            39 |       1321 |                    1 |                0 |                        |                  |
  |       294 | t_121_    | t_122_  |       295 |               1 | 295, 296, 297 |          0 |             0 |       1329 |                    1 |               44 |                        |                  |
  |       298 | t_122_    | t_123_  |       299 |               1 | 299, 300, 301 |          0 |            39 |       1332 |                    1 |                8 |                        |                  |
  |       302 | t_123_    | t_124_  |       305 |               2 | 303, 304, 305 |          0 |            39 |       1374 |                    1 |              125 |                        |                  |
  |       306 | t_124_    | t_125_  |       307 |               1 | 307, 308, 309 |          0 |             0 |       1278 |                    1 |                2 |                        |                  |
  |       310 | t_125_    | t_126_  |       313 |               2 | 311, 312, 313 |          0 |             0 |       1231 |                    1 |               14 |                        |                  |
  |       314 | t_126_    | t_128_  |       315 |               1 | 315, 316, 317 |          0 |            39 |       1248 |                    1 |                9 |                        |                  |
  +-----------+-----------+---------+-----------+-----------------+---------------+------------+---------------+------------+----------------------+------------------+------------------------+------------------+
  8 rows in set (0.02 sec)
  ```

+ `books`テーブルは主キー - `id`でハッシュパーティション化されているため、行は8つのパーティションに均等に分散されるはずです。次のクエリを実行して確認します。

  ```sql
  SELECT 'P0_COUNT', COUNT(1) FROM books PARTITION (p0)
  UNION ALL
  SELECT 'P1_COUNT', COUNT(1) FROM books PARTITION (p1)
  UNION ALL
  SELECT 'P2_COUNT', COUNT(1) FROM books PARTITION (p2)
  UNION ALL
  SELECT 'P3_COUNT', COUNT(1) FROM books PARTITION (p3)
  UNION ALL
  SELECT 'P4_COUNT', COUNT(1) FROM books PARTITION (p4)
  UNION ALL
  SELECT 'P5_COUNT', COUNT(1) FROM books PARTITION (p5)
  UNION ALL
  SELECT 'P6_COUNT', COUNT(1) FROM books PARTITION (p6)
  UNION ALL
  SELECT 'P7_COUNT', COUNT(1) FROM books PARTITION (p7)
  ORDER BY 1;
  ```
  ```output
  +----------+----------+
  | P0_COUNT | COUNT(1) |
  +----------+----------+
  | P0_COUNT |       12 |
  | P1_COUNT |       13 |
  | P2_COUNT |       13 |
  | P3_COUNT |       13 |
  | P4_COUNT |       13 |
  | P5_COUNT |       12 |
  | P6_COUNT |       12 |
  | P7_COUNT |       12 |
  +----------+----------+
  8 rows in set (0.00 sec)
  ```

### 手順 2. パーティションテーブルにグローバルセカンダリインデックスを追加する

+ 次のコマンドを使用して、`books`テーブルにグローバル一意インデックスを作成しようとします。たとえば、本のタイトルとタイプの組み合わせが一意であることを確認したいとします。**エラー**が予想されます。

  ```sql
  CREATE UNIQUE INDEX book_title_uk ON bookshop.books (title, type);
  ```
  ```output
  ERROR 8264 (HY000): Global Index is needed for index 'book_title_uk', since the unique index is not including all partitioning columns, and GLOBAL is not given as IndexOption
  ```

+ エラーメッセージは、ローカル一意インデックス（`GLOBAL`キーワードが指定されていない）を作成しようとしており、パーティションキー（ローカル一意インデックスはパーティションキーを含める必要があります。私たちの場合は`id`）を含めていないため、TiDBはそのSQL実行を実行できないことを示しています。この問題を解決するには、次のいずれかを行うことができます：

    > `title`と`type`に**ローカル一意インデックス**を作成します。ローカルインデックスであるため、実際にはインデックスに`id`列が必要ないにもかかわらず、追加で`id`列を含める必要があります。
    
    または
    
    > `title`と`type`に**グローバル一意インデックス**を作成します。`id`を含める必要はありません。これは単一列のインデックスです。**これは、このモジュールで議論している新機能です。**

+ `GLOBAL`キーワードを追加して再度グローバル一意インデックスを作成してみましょう。今回はエラーは予想されません。

  ```sql
  CREATE UNIQUE INDEX book_title_uk ON bookshop.books (title, type) GLOBAL;
  ```
  ```output
  Query OK, 0 rows affected (3.06 sec)
  ```

+ インデックスが`GLOBAL`であることを確認するために、`information_schema.tidb_indexes`テーブルをクエリし、出力の`is_global`列を確認します。

  ```sql
  SELECT key_name, seq_in_index, column_name, clustered, is_global
  FROM information_schema.tidb_indexes
  WHERE table_name='books';
  ```
  ```output
  +---------------+--------------+-------------+-----------+-----------+
  | key_name      | seq_in_index | column_name | clustered | is_global |
  +---------------+--------------+-------------+-----------+-----------+
  | PRIMARY       |            1 | id          | YES       |         0 |
  | book_title_uk |            1 | title       | NO        |         1 |
  | book_title_uk |            2 | type        | NO        |         1 |
  +---------------+--------------+-------------+-----------+-----------+
  3 rows in set (0.00 sec)
  ```

### 手順 3. グローバル一意インデックスの典型的なシナリオ

+ `books`テーブルの`title`と`type`の組み合わせが一意であることを覚えておいてください。

+ グローバル一意インデックス`book_title_uk`を削除し、クエリのEXPLAINを実行します。すべてのパーティションが結果のためにスキャンされることに注意してください。説明出力の`access object`列の`partition:all`に注意してください。データセットが巨大な場合、この操作はIOコストが高くなります。

  ```sql
  DROP INDEX book_title_uk ON bookshop.books;
  EXPLAIN SELECT stock, price FROM bookshop.books
  WHERE type = 'Science & Technology' AND title = 'Play That Funky Music';
  ```
  ```output
  Query OK, 0 rows affected (0.14 sec)

  +---------------------------+---------+-----------+---------------+----------------------------------------------------------------------------------------------------+
  | id                        | estRows | task      | access object | operator info                                                                                      |
  +---------------------------+---------+-----------+---------------+----------------------------------------------------------------------------------------------------+
  | TableReader_11            | 0.00    | root      | partition:all | data:Projection_5                                                                                  |
  | └─Projection_5            | 0.00    | cop[tikv] |               | bookshop.books.stock, bookshop.books.price                                                         |
  |   └─Selection_10          | 0.00    | cop[tikv] |               | eq(bookshop.books.title, "Play That Funky Music"), eq(bookshop.books.type, "Science & Technology") |
  |     └─TableFullScan_9     | 100.00  | cop[tikv] | table:books   | keep order:false, stats:pseudo                                                                     |
  +---------------------------+---------+-----------+---------------+----------------------------------------------------------------------------------------------------+
  4 rows in set (0.00 sec)
  ```

+ `(type, title)`に**ローカル**一意インデックスを追加し、クエリのEXPLAINを再度実行します。今回は、すべてのパーティションで`IndexRangeScan`が使用されていることがわかります。良いニュースは、インデックスがクエリを高速化するために活用されていることです。悪いニュースは、結果のために8つのローカルインデックスが8つのパーティションで範囲スキャンされていることです。`type`と`title`の一意の組み合わせにとって、これは必要ありません。説明出力の`access object`列の`IndexLookUp`行の`partition:all`に注意してください。以前よりは良いですが、私たちのケースにとっては最良の解決策ではありません。

  ```sql
  CREATE UNIQUE INDEX book_title_idx ON bookshop.books (type, title, id);
  EXPLAIN SELECT stock, price FROM bookshop.books
  WHERE type = 'Science & Technology' AND title = 'Play That Funky Music';
  ```
  ```output
  Query OK, 0 rows affected (2.98 sec)

  +---------------------------------+---------+-----------+----------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  | id                              | estRows | task      | access object                                      | operator info                                                                                                                                                                                                                                                                                                                                                             |
  +---------------------------------+---------+-----------+----------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  | Projection_4                    | 0.00    | root      |                                                    | bookshop.books.stock, bookshop.books.price                                                                                                                                                                                                                                                                                                                                |
  | └─IndexLookUp_8                 | 0.00    | root      | partition:all                                      |                                                                                                                                                                                                                                                                                                                                                                           |
  |   ├─IndexRangeScan_6(Build)     | 0.00    | cop[tikv] | table:books, index:book_title_idx(type, title, id) | range:["Science & Technology" "\x0f\xa7\x0f.\x0e3\x10^\x02\t\x10\x02\x0e\xe1\x0e3\x10\x02\x02\t\x0e\xb9\x10\x1f\x0fd\x0f!\x10^\x02\t\x0f[\x10\x1f\x0f\xea\x0e\xfb\x0e`","Science & Technology" "\x0f\xa7\x0f.\x0e3\x10^\x02\t\x10\x02\x0e\xe1\x0e3\x10\x02\x02\t\x0e\xb9\x10\x1f\x0fd\x0f!\x10^\x02\t\x0f[\x10\x1f\x0f\xea\x0e\xfb\x0e`"], keep order:false, stats:pseudo |
  |   └─TableRowIDScan_7(Probe)     | 0.00    | cop[tikv] | table:books                                        | keep order:false, stats:pseudo                                                                                                                                                                                                                                                                                                                                            |
  +---------------------------------+---------+-----------+----------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  4 rows in set (0.00 sec)
  ```

+ **ローカル**インデックスを**グローバル**一意インデックスに変更しましょう。

  ```sql
  DROP INDEX book_title_idx ON bookshop.books;
  CREATE UNIQUE INDEX book_title_uk ON bookshop.books (title, type) GLOBAL;
  ```
  ```output
  Query OK, 0 rows affected (0.17 sec)
  Query OK, 0 rows affected (2.38 sec)
  ```

+ グローバルインデックスが機能していることを確認するために次のクエリを実行します。説明出力の`access object`列の`index:book_title_uk`オブジェクトと`operator info`列の`Point_Get`操作に注意してください。すべてのパーティションで`IndexLookUp`スキャンも`TableFullScan`もなく、単一の`Point_Get`のみで、特にデータセットが巨大な場合、このモジュールで最も効率的なクエリです。

  ```sql
  EXPLAIN SELECT stock, price FROM bookshop.books
  WHERE type = 'Science & Technology' AND title = 'Play That Funky Music';
  ```
  ```output
  +-------------+---------+------+-----------------------------------------------+---------------+
  | id          | estRows | task | access object                                 | operator info |
  +-------------+---------+------+-----------------------------------------------+---------------+
  | Point_Get_1 | 1.00    | root | table:books, index:book_title_uk(title, type) |               |
  +-------------+---------+------+-----------------------------------------------+---------------+
  1 row in set (0.00 sec)
  ```

+ この時点で、グローバル一意インデックスを使用する典型的なシナリオを学びました。mysqlクライアント接続を開いたままにして、次のモジュールに進みます。

## モジュール 4: インデックス使用統計の監視

適切なインデックス設計は、データベースパフォーマンスを維持するための重要な前提条件です。TiDB v8.0.0は、`information_schema.tidb_index_usage`テーブルと`sys.schema_unused_indexes`ビューを導入し、インデックスの使用統計を提供します。この機能は、データベース内のインデックスの効率を評価し、インデックス設計を最適化するのに役立ちます。

このモジュールで作業するための前提知識として、[TiDB SQL Tuning](https://www.pingcap.com/education/course-tidb-sql-tuning/)トレーニングからの以下の補足情報を示します。便利のためにここに表示します。

  ![70%](https://lab-static.pingcap.com/quick-demo/SelCarCost.png)

### 手順 1. tidb_index_usage をクエリしてインデックスの効率を確認する

+ `tidb_index_usage`テーブルをクエリして`book_title_uk`インデックスの使用ベースラインを取得します。前のモジュールでの`EXPLAIN`コマンドは実際にクエリを実行しなかったため、統計はすべてゼロです。
  
  ```sql
  SELECT table_schema, table_name, index_name, query_total, rows_access_total, last_access_time,
  percentage_access_0, percentage_access_0_1, percentage_access_1_10, percentage_access_10_20,
  percentage_access_20_50, percentage_access_50_100, percentage_access_100
  FROM information_schema.tidb_index_usage
  WHERE index_name='book_title_uk'\G
  ```
  ```output
  *************************** 1. row ***************************
              table_schema: bookshop
                table_name: books
                index_name: book_title_uk
               query_total: 0
         rows_access_total: 0
          last_access_time: NULL
       percentage_access_0: 0
     percentage_access_0_1: 0
    percentage_access_1_10: 0
   percentage_access_10_20: 0
   percentage_access_20_50: 0
  percentage_access_50_100: 0
     percentage_access_100: 0
  1 row in set (0.01 sec)
  ```

+ CBO統計を収集します。

  ```sql
  ANALYZE TABLE bookshop.books;
  ```
  ```output
  Query OK, 0 rows affected, 8 warnings (0.69 sec)
  ```

  + `book_title_uk`インデックスを使用する可能性のあるクエリを実行します。

  ```sql
  SELECT stock, price FROM bookshop.books
  WHERE type = 'Science & Technology' AND title = 'Play That Funky Music';
  ```
  ```output
  +-------+--------+
  | stock | price  |
  +-------+--------+
  |   147 | 185.76 |
  +-------+--------+
  1 row in set (0.00 sec)
  ```

+ 使用統計を再度確認します。`query_total`、`rows_access_total`、および`percentage_access_1_10`が増加することに注意してください。

  ```sql
  SELECT table_schema, table_name, index_name, query_total, rows_access_total, last_access_time,
  percentage_access_0, percentage_access_0_1, percentage_access_1_10, percentage_access_10_20,
  percentage_access_20_50, percentage_access_50_100, percentage_access_100
  FROM information_schema.tidb_index_usage
  WHERE index_name='book_title_uk'\G
  ```
  ```output
  *************************** 1. row ***************************
              table_schema: bookshop
                table_name: books
                index_name: book_title_uk
               query_total: 1
         rows_access_total: 1
          last_access_time: 2025-03-09 05:33:47
       percentage_access_0: 0
     percentage_access_0_1: 0
    percentage_access_1_10: 1
   percentage_access_10_20: 0
   percentage_access_20_50: 0
  percentage_access_50_100: 0
     percentage_access_100: 0
  1 row in set (0.01 sec)
  ```

+ `book_title_uk`インデックスを使用しない可能性のある別のクエリを実行します。この場合、`type`が述語であり、`title`は省略されています。`book_title_uk`インデックスの先頭列は`title`であるため、CBOは`book_title_uk`インデックスアクセスパスを選ばないでしょう。

  ```sql
  SELECT stock, price FROM bookshop.books
  WHERE type = 'Science & Technology';
  ```
  ```output
  +------------+--------+
  | stock      | price  |
  +------------+--------+
  | 2147483647 | 101.02 |
  |         30 | 195.32 |
  |        141 |  13.20 |
  |        138 |  94.34 |
  |         38 | 138.97 |
  |         66 |  13.00 |
  |        147 | 185.76 |
  |          6 | 109.95 |
  |         20 |  75.24 |
  |        990 | 197.15 |
  |       4981 | 133.59 |
  |    9888998 | 106.51 |
  |        288 | 197.48 |
  |      30000 |  56.56 |
  |    5555555 | 104.12 |
  |          6 | 187.77 |
  +------------+--------+
  16 rows in set (0.02 sec)
  ```

+ ただし、前のクエリの結果セットに基づいて、クエリがインデックスアクセスパスを介して実行されたのか、フルテーブルスキャンアクセスパスを介して実行されたのかはわかりません。`tidb_index_usage`テーブルから答えを得ましょう。カウンタが増加していないことがわかります。これは、前のクエリでインデックスが使用されなかったことを意味します。

  ```sql
  SELECT table_schema, table_name, index_name, query_total, rows_access_total, last_access_time,
  percentage_access_0, percentage_access_0_1, percentage_access_1_10, percentage_access_10_20,
  percentage_access_20_50, percentage_access_50_100, percentage_access_100
  FROM information_schema.tidb_index_usage
  WHERE index_name='book_title_uk'\G
  ```
  ```output
  *************************** 1. row ***************************
              table_schema: bookshop
                table_name: books
                index_name: book_title_uk
               query_total: 1
         rows_access_total: 1
          last_access_time: 2025-03-09 05:33:47
       percentage_access_0: 0
     percentage_access_0_1: 0
    percentage_access_1_10: 1
   percentage_access_10_20: 0
   percentage_access_20_50: 0
  percentage_access_50_100: 0
     percentage_access_100: 0
  1 row in set (0.01 sec)
  ```

+ `type = 'Science & Technology'`という述語ではインデックスを使用できないことを確認します。

  ```sql
  EXPLAIN ANALYZE SELECT stock, price FROM bookshop.books
  WHERE type = 'Science & Technology';
  ```
  ```output
  +---------------------------+---------+---------+-----------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------+-----------+------+
  | id                        | estRows | actRows | task      | access object | execution info                                                                                                                                                                                                                                                                                                                            | operator info                                   | memory    | disk |
  +---------------------------+---------+---------+-----------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------+-----------+------+
  | TableReader_11            | 16.00   | 16      | root      | partition:all | time:1.5ms, loops:2, RU:4.149196, cop_task: {num: 8, max: 1.33ms, min: 955.8µs, avg: 1.17ms, p95: 1.33ms, max_proc_keys: 13, p95_proc_keys: 13, tot_proc: 662.4µs, tot_wait: 4.26ms, copr_cache_hit_ratio: 0.00, build_task_duration: 23.7µs, max_distsql_concurrency: 8}, rpc_info:{Cop:{num_rpc:8, total_time:9.31ms}}                  | data:Projection_5                               | 994 Bytes | N/A  |
  | └─Projection_5            | 16.00   | 16      | cop[tikv] |               | tikv_task:{proc max:0s, min:0s, avg: 0s, p80:0s, p95:0s, iters:8, tasks:8}, scan_detail: {total_process_keys: 100, total_process_keys_size: 8414, total_keys: 108, get_snapshot_time: 4.02ms, rocksdb: {key_skipped_count: 100, block: {}}}, time_detail: {total_process_time: 662.4µs, total_wait_time: 4.26ms, tikv_wall_time: 6.22ms}  | bookshop.books.stock, bookshop.books.price      | N/A       | N/A  |
  |   └─Selection_10          | 16.00   | 16      | cop[tikv] |               | tikv_task:{proc max:0s, min:0s, avg: 0s, p80:0s, p95:0s, iters:8, tasks:8}                                                                                                                                                                                                                                                                | eq(bookshop.books.type, "Science & Technology") | N/A       | N/A  |
  |     └─TableFullScan_9     | 100.00  | 100     | cop[tikv] | table:books   | tikv_task:{proc max:0s, min:0s, avg: 0s, p80:0s, p95:0s, iters:8, tasks:8}                                                                                                                                                                                                                                                                | keep order:false                                | N/A       | N/A  |
  +---------------------------+---------+---------+-----------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------+-----------+------+
  4 rows in set (0.00 sec)
  ```

+ この時点で、CBOが`TableFullScan`を選択したもう一つの理由は、データセットが小さすぎる（テーブルには100行しかない）ためだと考えるかもしれません。その通りです。データセットが十分に大きくない場合、`IndexLookUp`と`TableFullScan`の間で選択する際に明確なパフォーマンスの違いはありません。しかし、実行計画はCBOの決定を反映しています。CBOの決定をテストするために別の実験を行いましょう。

+ 今回は列の組み合わせ`(type, title)`に新しいグローバル一意インデックスを作成します。`type`が先頭の列です。

  ```sql
  CREATE UNIQUE INDEX book_type_uk ON bookshop.books (type, title) GLOBAL;
  ANALYZE TABLE bookshop.books;
  ```
  ```output
  Query OK, 0 rows affected (6.02 sec)

  Query OK, 0 rows affected, 8 warnings (0.82 sec)
  ```

+ 再度`type = 'Science & Technology'`述語クエリを実行します。今回は`book_type_uk`と`book_title_uk`の2つのインデックスがありますが、CBOはどのアクセス方法を選択するでしょうか？`book_title_uk`は可能性が低く、選択は`book_type_uk`と`TableFullScan`の間で行われます。すでに知っているように、結果セットの行は全体の16％を占めています。クエリの選択性は、CBOがインデックスアクセスパスを好むのに最適ではないかもしれません。

  ```sql
  SELECT stock, price FROM bookshop.books
  WHERE type = 'Science & Technology';
  ```
  ```output
  +------------+--------+
  | stock      | price  |
  +------------+--------+
  |      30000 |  56.56 |
  |    5555555 | 104.12 |
  |          6 | 187.77 |
  | 2147483647 | 101.02 |
  |         30 | 195.32 |
  |        141 |  13.20 |
  |        138 |  94.34 |
  |         38 | 138.97 |
  |        990 | 197.15 |
  |       4981 | 133.59 |
  |    9888998 | 106.51 |
  |         66 |  13.00 |
  |        147 | 185.76 |
  |          6 | 109.95 |
  |         20 |  75.24 |
  |        288 | 197.48 |
  +------------+--------+
  16 rows in set (0.00 sec)
  ```

+ 再度`tidb_index_usage`テーブルを参照します。`book_type_uk`のカウンタが増加していないことがわかります。特に`percentage_access_10_20`カウンタが0であることがわかります。これは、前のクエリでインデックスが使用されなかったことを意味します。しかし、`books`テーブルに多くの行があり、選択性が16％のままであれば、CBOは同じクエリに対してインデックスアクセスパスを選択するかもしれません。

  ```sql
  SELECT table_schema, table_name, index_name, query_total, rows_access_total, last_access_time,
  percentage_access_0, percentage_access_0_1, percentage_access_1_10, percentage_access_10_20,
  percentage_access_20_50, percentage_access_50_100, percentage_access_100
  FROM information_schema.tidb_index_usage
  WHERE index_name in ('book_title_uk','book_type_uk')\G
  ```
  ```output
  *************************** 1. row ***************************
              table_schema: bookshop
                table_name: books
                index_name: book_title_uk
               query_total: 1
         rows_access_total: 1
          last_access_time: 2025-03-09 05:33:47
       percentage_access_0: 0
     percentage_access_0_1: 0
    percentage_access_1_10: 1
   percentage_access_10_20: 0
   percentage_access_20_50: 0
  percentage_access_50_100: 0
     percentage_access_100: 0
  *************************** 2. row ***************************
              table_schema: bookshop
                table_name: books
                index_name: book_type_uk
               query_total: 0
         rows_access_total: 0
          last_access_time: NULL
       percentage_access_0: 0
     percentage_access_0_1: 0
    percentage_access_1_10: 0
   percentage_access_10_20: 0
   percentage_access_20_50: 0
  percentage_access_50_100: 0
     percentage_access_100: 0
  2 rows in set (0.01 sec)
  ```

+ 一般的なルールとして、TiDBでは選択性が10％未満であることがインデックスアクセスパスに適した条件と見なされますが、これはすべてのケースに対する絶対的なルールではありません。

### 手順 2. インデックス使用統計に基づいて未使用のインデックスを削除する

+ 無駄なインデックスは、DML操作（INSERT、DELETE、UPDATEなど）および一部のDDL操作を遅くするだけです。それを削除することが正しい行動です。

+ `tidb_index_usage`テーブルはどのインデックスが使用されているか、されていないかを教えてくれるので、TiDBはDBAやDevOpsエンジニアに無駄なインデックスを教えるための`sys.schema_unused_indexes`という興味深いインメモリテーブルを提供します。クラスタの再起動時にクリアされます。`sys.schema_unused_indexes`に依存する前に、少なくとも1つのTiDBノードが十分な時間稼働していることを確認する必要があります。

+ `books`テーブルの未使用インデックスを見つけます。

  ```sql
  SELECT * FROM sys.schema_unused_indexes
  WHERE object_schema='bookshop' AND object_name='books';
  ```
  ```output
  +---------------+-------------+--------------+
  | object_schema | object_name | index_name   |
  +---------------+-------------+--------------+
  | bookshop      | books       | book_type_uk |
  +---------------+-------------+--------------+
  1 row in set (0.01 sec)
  ```

+ 未使用のインデックスをCBOに対して不可視にします。CBOはそのようなインデックスを実行計画で使用しません。実際のシナリオでは、システムのパフォーマンス低下を監視する必要があります。

  ```sql
  ALTER TABLE bookshop.books ALTER INDEX book_type_uk INVISIBLE;
  ```
  ```output
  Query OK, 0 rows affected (0.51 sec)
  ```

+ 一定のビジネスサイクルが完了し、不可視インデックスに関連するパフォーマンス低下が観察されない場合は、それを削除できます。

  ```sql
  ALTER TABLE bookshop.books DROP INDEX book_type_uk;
  ```
  ```output
  Query OK, 0 rows affected (0.51 sec)
  ```

+ インデックス使用統計から支援を受けて運用決定を行う方法を理解したので、次のモジュールに進んでください。

## モジュール 5: ベクトル検索 (v8.5.0)

AIの進歩と高次元空間での効率的なデータ処理の必要性により、TiDBはベクトルデータ型とデータベース内ベクトル検索をサポートしています。

このモジュールでは、ベクトルデータ型を持つテーブルを作成し、ベクトルデータを含むレコードを挿入し、ベクトル検索のパフォーマンスを向上させるためにHNSWインデックスを使用します。

+ TiDBをベクトルストアとして使用することで、すぐに次のような利点が得られます：
  + 効率的な近似最近傍検索。
  + 計画的および非計画的なダウンタイムがゼロで無制限のスケール。
  + リレーショナルデータとベクトルデータの統一ストレージ、極めて大規模なボリュームにスケーラブル。

### 手順 1. ベクトルデータ型を持つテストテーブルを作成する

+ ベクトルデータ型を持つテーブルを作成し、ランダムデータ（32768行）を挿入します。テーブル名は`vs_example.vector_table`です。出力に示されているように、ベクトルデータ型の列は`embedding`です。

  ```sql
  CREATE DATABASE vs_example;
  USE vs_example;
  DROP SEQUENCE IF EXISTS vs_seq1;
  CREATE SEQUENCE vs_seq1;
  DROP TABLE IF EXISTS vector_table;
  CREATE TABLE vector_table (
      id BIGINT AUTO_RANDOM PRIMARY KEY,
      doc TEXT,
      embedding VECTOR(3)
  );
  SOURCE populate_vector_table.sql
  DESC vector_table;
  SELECT COUNT(1) FROM vector_table;
  SHOW CREATE TABLE vector_table\G
  ```
  ```output
  ...
  Query OK, 16384 rows affected (0.06 sec)
  Records: 16384 Duplicates: 0  Warnings: 0
  Query OK, 0 rows affected (0.10 sec)

  +-----------+-----------+------+------+---------+-------+
  | Field     | Type      | Null | Key  | Default | Extra |
  +-----------+-----------+------+------+---------+-------+
  | id        | bigint    | NO   | PRI  | NULL    |       |
  | doc       | text      | YES  |      | NULL    |       |
  | embedding | vector(3) | YES  |      | NULL    |       |
  +-----------+-----------+------+------+---------+-------+
  3 rows in set (0.00 sec)

  +----------+
  | COUNT(1) |
  +----------+
  |    32768 |
  +----------+
  1 row in set (0.02 sec)

  *************************** 1. row ***************************
         Table: vector_table
  Create Table: CREATE TABLE `vector_table` (
    `id` bigint NOT NULL /*T![auto_rand] AUTO_RANDOM(5) */,
    `doc` text DEFAULT NULL,
    `embedding` vector(3) DEFAULT NULL,
    PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![auto_rand_base] AUTO_RANDOM_BASE=625585 */
  1 row in set (0.01 sec)
  ```

### 手順 2. K-Nearest Neighbor 検索を実行し、実行計画を確認する

+ 次のクエリを確認します。これは、コサイン距離を使用した典型的なK-nearest neighbor検索を示しています。

+ アルゴリズムは、データセット内の参照ノード`[0.10752534,0.3524283,0.46134815]`に最も近いノード（上位3つ）を探します。ステップ1でランダムデータを使用したため、結果はサンプル出力とは異なります。

  ```sql
  SELECT doc, embedding
  FROM vector_table
  ORDER BY VEC_COSINE_DISTANCE(embedding, '[0.10752534,0.3524283,0.46134815]')
  LIMIT 3;
  ```
  ```output
  +-------+-----------------------------------+
  | doc   | embedding                         |
  +-------+-----------------------------------+
  | 3343  | [0.10953534,0.3534283,0.46134815] |
  | 20717 | [0.18479975,0.59234893,0.7714548] |
  | 19597 | [0.20609,0.6679932,0.8865328]     |
  +-------+-----------------------------------+
  3 rows in set (0.02 sec)
  ```

+ 実行計画を確認して、クエリで`FullTableScan`が使用されていることを確認します。テーブル`vector_table`には32768行しかないため、応答時間は非常に迅速ですが、特に数十億行を扱う場合の潜在的なパフォーマンスへの影響を考慮することが重要です。`explain`出力の最下部の操作に注目してください。推定行数（`estRows`）は`3.00`よりもはるかに大きい数値です。

  ```sql
  EXPLAIN
  SELECT doc, embedding
  FROM vector_table
  ORDER BY VEC_COSINE_DISTANCE(embedding, '[0.10752534,0.3524283,0.46134815]')
  LIMIT 3;
  ```
  ```output
  +--------------------------------+----------+-----------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+
  | id                             | estRows  | task      | access object      | operator info                                                                                                                                      |
  +--------------------------------+----------+-----------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+
  | Projection_14                  | 3.00     | root      |                    | vs_example.vector_table.doc, vs_example.vector_table.embedding                                                                                     |
  | └─TopN_7                       | 3.00     | root      |                    | Column#4, offset:0, count:3                                                                                                                        |
  |   └─Projection_15              | 3.00     | root      |                    | vs_example.vector_table.doc, vs_example.vector_table.embedding, vec_cosine_distance(vs_example.vector_table.embedding, [0.11,0.35,0.46])->Column#4 |
  |     └─TableReader_13           | 3.00     | root      |                    | data:TopN_12                                                                                                                                       |
  |       └─TopN_12                | 3.00     | cop[tikv] |                    | vec_cosine_distance(vs_example.vector_table.embedding, [0.11,0.35,0.46]), offset:0, count:3                                                        |
  |         └─TableFullScan_11     | 30179.00 | cop[tikv] | table:vector_table | keep order:false                                                                                                                                   |
  +--------------------------------+----------+-----------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+
  6 rows in set (0.00 sec)
  ```

### 手順 3. ベクトル列に HNSW インデックスを追加する

+ TiFlashレプリカを有効にします。

  ```sql
  ALTER TABLE vector_table SET TIFLASH REPLICA 1;
  ```

+ 列`embedding`にHNSWベクトルインデックスを作成します。HNSWインデックスは、近似精度（ほとんどの場合98％以上）で優れたパフォーマンスを提供します。

  ```sql
  CREATE VECTOR INDEX embedding_idx ON vector_table ((VEC_COSINE_DISTANCE(embedding))) USING HNSW;
  ```

+ 列が完全にインデックス化されていることを確認します。`ROWS_STABLE_INDEXED`が`32768`であることを確認します。

  ```sql
  SELECT * FROM information_schema.tiflash_indexes
  WHERE tidb_table='vector_table'\G
  ```
  ```output
  *************************** 1. row ***************************
            TIDB_DATABASE: vs_example
               TIDB_TABLE: vector_table
                 TABLE_ID: 114
              COLUMN_NAME: embedding
               INDEX_NAME: embedding_idx
                COLUMN_ID: 3
                 INDEX_ID: 1
               INDEX_KIND: HNSW
      ROWS_STABLE_INDEXED: 32768
  ROWS_STABLE_NOT_INDEXED: 0
       ROWS_DELTA_INDEXED: 0
   ROWS_DELTA_NOT_INDEXED: 0
            ERROR_MESSAGE: 
         TIFLASH_INSTANCE: 10.90.4.131:3930
  1 row in set (0.00 sec)
  ```

### 手順 4. インデックス付きテーブルに対して K-Nearest Neighbor クエリを実行する

+ テーブル`vector_table`に対して同じk-nearest neighbors検索を実行します。

  ```sql
  SELECT doc, embedding
  FROM vector_table
  ORDER BY VEC_COSINE_DISTANCE(embedding, '[0.10752534,0.3524283,0.46134815]')
  LIMIT 3;
  ```
  ```output
  +-------+------------------------------------+
  | doc   | embedding                          |
  +-------+------------------------------------+
  | 3343  | [0.10953534,0.3534283,0.46134815]  |
  | 20717 | [0.18479975,0.59234893,0.7714548]  |
  | 24641 | [0.21949558,0.73159087,0.94375384] |
  +-------+------------------------------------+
  3 rows in set (0.03 sec)
  ```

### 手順 5. 実行計画を確認する

+ 実行計画を確認して、クエリで近似最近傍インデックス（`access object`列の`index:embedding_idx(embedding)`オブジェクトと`operator info`列の`annIndex:COSINE`オペレーター）が使用されていることを確認します。大規模に運用される本番環境では、テーブル`vector_table`がこのクエリを提供する際に`vector_table_noidx`よりも優れたパフォーマンスを発揮することが期待されます。`explain`出力の最下部の操作に注目してください。推定行数（`estRows`）は`3.00`のみであり、ステップ2の`TableFullScan`よりもはるかに効率的です。インデックス最適化に加えて、`task`列の`mpp[tiflash]`タスクも見つけることができます。これは、TiDBによって実装されたカラムナストレージクエリ最適化です。

  ```sql
  EXPLAIN
  SELECT doc, embedding
  FROM vector_table
  ORDER BY VEC_COSINE_DISTANCE(embedding, '[0.10752534,0.3524283,0.46134815]')
  LIMIT 3;
  ```
  ```output
  +--------------------------------------+---------+--------------+----------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+
  | id                                   | estRows | task         | access object                                      | operator info                                                                                                                                      |
  +--------------------------------------+---------+--------------+----------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+
  | Projection_28                        | 3.00    | root         |                                                    | vs_example.vector_table.doc, vs_example.vector_table.embedding                                                                                     |
  | └─TopN_10                            | 3.00    | root         |                                                    | Column#5, offset:0, count:3                                                                                                                        |
  |   └─Projection_29                    | 3.00    | root         |                                                    | vs_example.vector_table.doc, vs_example.vector_table.embedding, vec_cosine_distance(vs_example.vector_table.embedding, [0.11,0.35,0.46])->Column#5 |
  |     └─TableReader_25                 | 3.00    | root         |                                                    | MppVersion: 2, data:ExchangeSender_24                                                                                                              |
  |       └─ExchangeSender_24            | 3.00    | mpp[tiflash] |                                                    | ExchangeType: PassThrough                                                                                                                          |
  |         └─Projection_26              | 3.00    | mpp[tiflash] |                                                    | vs_example.vector_table.doc, vs_example.vector_table.embedding                                                                                     |
  |           └─TopN_23                  | 3.00    | mpp[tiflash] |                                                    | Column#4, offset:0, count:3                                                                                                                        |
  |             └─Projection_27          | 3.00    | mpp[tiflash] |                                                    | vs_example.vector_table.doc, vs_example.vector_table.embedding, vec_cosine_distance(vs_example.vector_table.embedding, [0.11,0.35,0.46])->Column#4 |
  |               └─TableFullScan_22     | 3.00    | mpp[tiflash] | table:vector_table, index:embedding_idx(embedding) | keep order:false, annIndex:COSINE(embedding..[0.11,0.35,0.46], limit:3)                                                                            |
  +--------------------------------------+---------+--------------+----------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+
  9 rows in set (0.00 sec)
  ```

+ 前述のように、HNSWインデックスはTiFlashに依存しています。TiDBのカラムナストレージTiFlashに不慣れな場合は、他のハンズオン演習 - Getting Started with TiDB Cloud Serverlessを確認するか、自己ペースのeラーニング[Introduction to TiDB](https://www.pingcap.com/education/course-introduction-to-tidb/)に登録してください。次のモジュールに進んでください。

+ このモジュールの3次元ベクトルとは異なり、実際のシナリオでは、埋め込みモデルによって生成された1536次元のベクトルなど、はるかに多くの次元を持つベクトルが関与することがよくあります。これらのトピックに焦点を当てたハンズオン演習も提供しています - [Generative AI at TiDB](https://labs.tidb.io/labs?category=Generative+AI+at+TiDB)。

+ 次のモジュールに進んでください。

## モジュール 6: TiKV MVCC インメモリエンジン (v8.5.0)

マルチバージョン同時実行制御（MVCC）は、現代のデータベースの重要な側面であり、整合性を損なうことなく同時アクセスを可能にします。TiKV MVCCインメモリエンジンは、MVCCの履歴バージョンを大量にスキャンする必要があるクエリのパフォーマンスを向上させるために主に使用されます。最適化されたクエリでも、多くのMVCCバージョンをスキャンするために遅いクエリになることがあります。

TiKV MVCCインメモリエンジンは、次のシナリオでクエリパフォーマンスを向上させるのに適しています：

+ 頻繁に更新または削除されるレコードをクエリする必要があるアプリケーション。
+ TiDBで履歴バージョンを長期間保持するために[tidb_gc_life_time](https://docs.pingcap.com/tidb/stable/system-variable-reference/#tidb_gc_life_time)を調整する必要があるアプリケーション。

### 手順 1. デフォルトの in-memory-engine 設定を確認する

+ TiDBに接続します。

  ```bash
  mysql -h ${HOST_TIPROXY1_PRIVATE_IP} --port 6000 -u root
  ```
  ```output
  mysql> 
  ```

+ TiKV MVCC `in-memory-engine`がデフォルトでオフになっていることを確認します。

  ```sql
  SHOW CONFIG WHERE type='tikv' AND name='in-memory-engine.enable';
  ```
  ```output
  +------+-------------------+-------------------------+-------+
  | Type | Instance          | Name                    | Value |
  +------+-------------------+-------------------------+-------+
  | tikv | 10.90.1.41:20160  | in-memory-engine.enable | false |
  | tikv | 10.90.1.223:20160 | in-memory-engine.enable | false |
  | tikv | 10.90.1.231:20160 | in-memory-engine.enable | false |
  +------+-------------------+-------------------------+-------+
  3 rows in set (0.01 sec)
  ```

+ この演習の期間中に以前のバージョンを保持できるように`tidb_gc_life_time`を増やします。

  ```sql
  SET GLOBAL tidb_gc_life_time='1440m';
  ```

### 手順 2. in-memory-engine 設定が OFF の状態での動作を確認する

+ 同じスキーマを持つ2つのテストテーブル`ime_test1`と`ime_test2`を設定します。各テーブルには`content`列にインデックスがあります。

  ```sql
  DROP TABLE IF EXISTS test.ime_test1;
  DROP TABLE IF EXISTS test.ime_test2;
  CREATE TABLE test.ime_test1 (
      `id` BIGINT AUTO_RANDOM PRIMARY KEY,
      `content` INT,
      `created_at` DATETIME,
      `updated_at` DATETIME,
      `version` INT,
      KEY content_idx (`content`)
  );
  CREATE TABLE test.ime_test2 (
      `id` BIGINT AUTO_RANDOM PRIMARY KEY,
      `content` INT,
      `created_at` DATETIME,
      `updated_at` DATETIME,
      `version` INT,
      KEY content_idx (`content`)
  );
  ```

+ 両方のテーブルに1行を挿入します。

  ```sql
  INSERT INTO test.ime_test1 (
      content, created_at, updated_at, version
  ) VALUES (
      FLOOR(1 + (RAND() * 99999999)), NOW(), NOW(), 1
  );
  INSERT INTO test.ime_test2 (
      content, created_at, updated_at, version
  ) VALUES (
      FLOOR(1 + (RAND() * 99999999)), NOW(), NOW(), 1
  );
  SELECT * FROM test.ime_test1;
  SELECT * FROM test.ime_test2;
  EXIT
  ```

+ 両方のテーブルを32768行に拡張します。

  ```bash
  cat > double.sql <<EOF
  INSERT INTO test.ime_test1 (
      content, created_at, updated_at, version
  )
  SELECT FLOOR(1 + (RAND() * 99999999)), NOW(), NOW(), 1 FROM test.ime_test1;
  INSERT INTO test.ime_test2 (
      content, created_at, updated_at, version
  )
  SELECT FLOOR(1 + (RAND() * 99999999)), NOW(), NOW(), 1 FROM test.ime_test2;
  EOF
  cat > double.sh <<EOF
  mysql -h ${HOST_TIPROXY1_PRIVATE_IP} --port 6000 -u root < double.sql
  EOF
  chmod +x double.sh
  for c in {1..15}; do
  ./double.sh
  done;
  ```

+ `test.ime_test2`テーブルの各行に対して100バージョンを生成し、`content`列に対して100回の更新操作を実行します。私たちの演習環境では（パフォーマンスが最適化されていないため）、約2分かかります。

  ```bash
  cat > update_hog.sql <<EOF
  UPDATE test.ime_test2
  SET content = FLOOR(1 + (RAND() * 99999999)), version = version + 1;
  EOF
  cat > update_hog.sh <<EOF
  mysql -h ${HOST_TIPROXY1_PRIVATE_IP} --port 6000 -u root < update_hog.sql
  EOF
  chmod +x update_hog.sh
  for c in {1..100}; do
  ./update_hog.sh
  done;
  ```

+ 再度TiDBに接続します。

  ```bash
  mysql -h ${HOST_TIPROXY1_PRIVATE_IP} --port 6000 -u root
  ```
  ```output
  mysql> 
  ```

+ テーブル`test.ime_test1`と`test.ime_test2`に対してフルテーブルスキャンを実行する2つのSQLステートメントを実行します。

  ```sql
  SELECT COUNT(*) FROM test.ime_test1;
  SELECT COUNT(*) FROM test.ime_test2;
  EXIT
  ```
  ```output
  +----------+
  | COUNT(*) |
  +----------+
  |    32768 |
  +----------+
  1 row in set (0.03 sec)

  +----------+
  | COUNT(*) |
  +----------+
  |    32768 |
  +----------+
  1 row in set (1.09 sec)

  Bye
  ```

+ [TiDB PD Dashboard](http://${LAB:HOST_PD1_PUBLIC_IP}:2379/dashboard)にアクセスし、ユーザー名`root`でログインします。現時点ではパスワードは不要です。`SQL Statements`パネルに移動し、表示時間を`Recent 5 min`に変更します。

  ![60%](https://lab-static.pingcap.com/quick-demo/ime-off-dashboard-time-1.png)

+ データベースを`test`に選択します。

  ![60%](https://lab-static.pingcap.com/quick-demo/ime-off-dashboard-db-2.png)

+ 表示される操作を`Select`と`Update`に設定します。

  ![60%](https://lab-static.pingcap.com/quick-demo/ime-off-dashboard-kind-3.png)

+ ダッシュボードの右上隅で`Columns`を選択し、メニュー項目`Mean Visible Version Per Query`と`Mean Meet Versions Per Query`をチェックし、`Query`ボタンをクリックします。

  ![60%](https://lab-static.pingcap.com/quick-demo/ime-off-dashboard-columns-4.png)

+ クエリ結果から、テーブル`test.ime_test1`のスキャンでは、`Mean Visible Version Per Query`が`Mean Meet Versions Per Query`と等しいことがわかります。これは、処理するMVCCスナップショットがないことを示しています。
対照的に、テーブル`test.ime_test2`のスキャンでは、`Mean Meet Versions Per Query`が`Mean Visible Version Per Query`よりもはるかに高いことがわかります。これは、スキャン中に大量のMVCCスナップショット処理が行われていることを示しています。これは、`Total Latency`と`Mean Latency`列からも明らかです。同じ行数をスキャンする場合、実行時間が異なり、テーブル`test.ime_test2`のスキャンではMVCCスナップショットのスキャンに多くの時間がかかっています。

  ![95%](https://lab-static.pingcap.com/quick-demo/ime-off-dashboard-sql-5.png)

+ [Grafana Prometheus Dashboard](http://${LAB:HOST_MONITOR1_PUBLIC_IP}:3000) をクリックし、ユーザー名とパスワード（両方とも「admin」）を使用してログインします。`Home` > `tidb-demo` > `tidb-demo-TikV-Details` > `In Memory Engine`に移動し、`Region Cache Hit`，`Region Cache Hit Rate`，`Region Cache Miss Reason`，`Memory Usage`および`Region Count`に関するパネルまでスクロールします。

  ![90%](https://lab-static.pingcap.com/quick-demo/ime-off-grafana-no-data-1.png)

+ 現在、これらのパネルは`No data`を表示しています。これは、現在のデータベースクラスタでTiKV MVCCインメモリエンジンが有効になっていないためです。`Absolute time range`を`Last 5 minutes`に変更します。

  ![60%](https://lab-static.pingcap.com/quick-demo/ime-off-grafana-time-range-2.png)

+ `Refresh time`を`10s`に変更します。

  ![60%](https://lab-static.pingcap.com/quick-demo/ime-off-grafana-refresh-time-3.png)

### 手順 3. TiKV MVCC インメモリエンジンを有効にする

+ [ターミナル 1](ssh://monitor1-ssh#1) に戻り、`tiup cluster edit-config`を使用して`vi`でTiDBクラスタの構成を開きます。

  ```bash
  tiup cluster edit-config tidb-demo
  ```

+ TiKV MVCCインメモリエンジンを有効にするために、すべての`tikv_servers`に対して`in-memory-engine.capacity`を`1GiB`、`in-memory-engine.enable`を`true`、および`in-memory-engine.mvcc-amplification-threshold`を`100`に設定します。

+ 次のセクションを各TiKVの`os: linux`エントリの下にコピーし、`:wq`を使用して保存して終了します。

  ```yaml
    config:
      in-memory-engine.capacity: 1GiB
      in-memory-engine.enable: true
      in-memory-engine.mvcc-amplification-threshold: 50
  ``` 
  ```output
  ...
  tikv_servers:
  - host: 10.90.1.41
    ssh_port: 22
    port: 20160
    status_port: 20180
    deploy_dir: /tidb-deploy/tikv-20160
    data_dir: /tidb-data/tikv-20160
    log_dir: /tidb-deploy/tikv-20160/log
    arch: amd64
    os: linux
    config:
      in-memory-engine.capacity: 1GiB
      in-memory-engine.enable: true
      in-memory-engine.mvcc-amplification-threshold: 50
  - host: 10.90.1.223
    ssh_port: 22
    port: 20160
    status_port: 20180
    deploy_dir: /tidb-deploy/tikv-20160
    data_dir: /tidb-data/tikv-20160
    log_dir: /tidb-deploy/tikv-20160/log
    arch: amd64
    os: linux
    config:
      in-memory-engine.capacity: 1GiB
      in-memory-engine.enable: true
      in-memory-engine.mvcc-amplification-threshold: 50
  - host: 10.90.1.231
    ssh_port: 22
    port: 20160
    status_port: 20180
    deploy_dir: /tidb-deploy/tikv-20160
    data_dir: /tidb-data/tikv-20160
    log_dir: /tidb-deploy/tikv-20160/log
    arch: amd64
    os: linux
    config:
      in-memory-engine.capacity: 1GiB
      in-memory-engine.enable: true
      in-memory-engine.mvcc-amplification-threshold: 50
  ...
  ```

+ プロンプトに`y`と答えます。

  ```output
  Please check change highlight above, do you want to apply the change? [y/N]:(default=N) y
  Applying changes...
  Applied successfully, please use `tiup cluster reload tidb-demo [-N <nodes>] [-R <roles>]` to reload config.
  ```

+ `tiup cluster reload`を実行して構成をリロードします。

  ```bash
  tiup cluster reload tidb-demo --yes
  ```
  ```output
  ...
  Reloaded cluster `tidb-demo` successfully
  ```

+ 構成がリロードされた後、TiDBに接続します。

  ```bash
  mysql -h ${HOST_TIPROXY1_PRIVATE_IP} --port 6000 -u root
  ```
  ```output
  mysql> 
  ```

+ TiKV MVCC `in-memory-engine`が有効になっていることを確認します。

  ```sql
  SHOW CONFIG WHERE type='tikv' AND name='in-memory-engine.enable';
  EXIT
  ```
  ```output
  +------+-------------------+-------------------------+-------+
  | Type | Instance          | Name                    | Value |
  +------+-------------------+-------------------------+-------+
  | tikv | 10.90.1.41:20160  | in-memory-engine.enable | true  |
  | tikv | 10.90.1.223:20160 | in-memory-engine.enable | true  |
  | tikv | 10.90.1.231:20160 | in-memory-engine.enable | true  |
  +------+-------------------+-------------------------+-------+
  3 rows in set (0.02 sec)

  Bye
  ```

### 手順 4. in-memory-engine 設定が ON のときの動作を確認する

+ [Grafana Prometheus Dashboard](http://${LAB:HOST_MONITOR1_PUBLIC_IP}:3000) をクリックし、ユーザー名とパスワード（両方とも「admin」）を使用してログインします。`Home` > `tidb-demo` > `tidb-demo-TikV-Details` > `In Memory Engine`に移動し、`Region Cache Hit`，`Region Cache Hit Rate`，`Region Cache Miss Reason`，`Memory Usage`および`Region Count`に関するパネルまでスクロールします。現在、これらのパネルは`No data`を表示していませんが、`Region Cache Hit`、`Region Cache Hit Rate`、`Region Cache Miss Reason`、`Memory Usage`、および`Region Count`の値は非常に低く、ほぼゼロです。これは、現在MVCCスナップショットのクエリがないためです。

  ![90%](https://lab-static.pingcap.com/quick-demo/ime-on-grafana-no-query-1.png)

+ [ターミナル 1](ssh://monitor1-ssh#1) に戻り、コマンドラインから`test.ime_test1`テーブルに対して10,000回のスキャンを実行し、スキャンプロセス中に[Grafana Prometheus Dashboard](http://${LAB:HOST_MONITOR1_PUBLIC_IP}:3000) の`Home` > `tidb-demo` > `tidb-demo-TikV-Details` > `In Memory Engine`パネルを監視します。

  ```bash
  for i in `seq 10000`;
  do mysql -uroot -h ${HOST_TIPROXY1_PRIVATE_IP} -P 6000 -e "SELECT COUNT(*) FROM test.ime_test1;";
  done;
  ```

+ `test.ime_test1`テーブルのスキャンプロセス中、`tidb-demo-TikV-Details` > `In Memory Engine`パネルから、`Region Cache Hit`と`Region Cache Hit Rate`の両方にほとんど変化がないことが観察されます。（`Region Cache Hit`はデータがRegionキャッシュから正常に取得された回数を示し、`Region Cache Hit Rate`はRegionキャッシュのヒット率を表します。）これらの変化が少ない理由は、`test.ime_test1`テーブル自体にMVCCの履歴スナップショットがないためです。

  ![90%](https://lab-static.pingcap.com/quick-demo/ime-on-grafana-ime-test1.png)

+ `test.ime_test1`テーブルの10,000回のスキャンが終了した後、[ターミナル 1](ssh://monitor1-ssh#1) に戻り、コマンドラインから`test.ime_test2`テーブルに対してさらに50,000回のスキャンを実行し、再度スキャンプロセス中に[Grafana Prometheus Dashboard](http://${LAB:HOST_MONITOR1_PUBLIC_IP}:3000) の`Home` > `tidb-demo` > `tidb-demo-TikV-Details` > `In Memory Engine`パネルを監視します。

  ```bash
  for i in `seq 50000`;
  do mysql -uroot -h ${HOST_TIPROXY1_PRIVATE_IP} -P 6000 -e "SELECT COUNT(*) FROM test.ime_test2;";
  done;
  ```

+ `test.ime_test2`テーブルのスキャンプロセス中、`tidb-demo-TikV-Details` > `In Memory Engine`パネルから、`Region Cache Hit`と`Region Cache Hit Rate`の両方に大きな変化があることが観察されます。これは、`test.ime_test2`テーブル自体に大量のMVCC履歴スナップショットが含まれているためです。TiKV MVCCインメモリエンジンは、最新の書き込みMVCCバージョンをメモリにキャッシュし、TiDBとは独立したMVCC GCメカニズムを実装しています。その結果、スキャンプロセス中にTiKV MVCCインメモリエンジンのキャッシュから大量のデータが読み取られます。

  ![90%](https://lab-static.pingcap.com/quick-demo/ime-on-grafana-ime-test2.png)

+ TiKV MVCCインメモリエンジンがデータをタイムリーにキャッシュしない場合があります。この場合、上記の手順を繰り返し、再度`ime_test2`テーブルをスキャンし、監視パネルを観察してみてください。

## 結論

TiDB v8で導入された新機能の完全なリストについては、[TiDBリリースノート](https://docs.pingcap.com/tidb/stable/release-notes)をご覧ください。

```hbsptform
{
  "ui": {
    "titleLevel": 3
  },
  "portalId": "4466002",
  "formId": "62bfb32a-a60a-4f9d-b2dc-1107a93db568",
  "region": "na1",
  "sfdcCampaignId": "701RC00000NdFlAYAV"
}
```