# SaaS アプリケーションのための実践的なデータベース機能

## 概要

SaaS (Software-as-a-Service) アプリケーションでは、多様なワークロードを持つ多数の顧客を処理するために、高いスケーラビリティとマルチテナント対応の機能が必要とされます。分散型 SQL データベースである TiDB は、SaaS アプリケーションの柔軟性、パフォーマンス、運用効率を向上させる実践的な機能を提供します。

このハンズオン演習では、SaaS 業界で高い価値を持つ TiDB の機能を中心に設計された 4 つのモジュールを探求します。これらのタスクには、数千のスキーマとテーブルの迅速な作成、シームレスなオンラインスキーマ変更、複数のスキーマにわたるクエリ実行を標準化するためのグローバル実行計画バインディングの適用が含まれます。

以下の図は、この演習の TiDB クラスターのデプロイメントトポロジーを示しています。

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

### 所要時間

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

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

## モジュール 1: 大量のスキーマとテーブルの迅速な作成

SaaS アプリケーションでは、異なるテナントに対して個別のスキーマやテーブルを提供することがよくあります。TiDB のスケーラビリティと効率的なメタデータ管理により、パフォーマンスと安定性を維持しながら、数千のスキーマとテーブルを迅速に作成することができます。

このモジュールでは、1,000 のスキーマを作成し、各スキーマに 10 のテーブルを含める、合計 10,000 テーブルの作成を試みます。

実際の環境では、問題なく 100 万テーブルまでスケールアップできます。今回は時間の制約により、生成を 1 万テーブルに制限します。

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


### 手順 2. TiDB クラスターの状態を確認する

+ クラスターの状態を取得します。
  
  ```bash
  tiup cluster display tidb-demo
  ```
  
  ```output
  Cluster type:       tidb
  Cluster name:       tidb-demo
  Cluster version:    v${LAB:TIDB_VERSION}
  Deploy user:        ec2-user
  SSH type:          builtin
  Dashboard URL:      http://54.188.180.4:2379/dashboard
  Grafana URL:        http://10.90.4.196:3000
  ID                 Role          Host         Ports        OS/Arch       Status  Data Dir                      Deploy Dir
  --                 ----          ----         -----        -------       ------  --------                      ----------
  10.90.4.196:9093   alertmanager  10.90.4.196  9093/9094    linux/x86_64  Up      /tidb-data/alertmanager-9093  /tidb-deploy/alertmanager-9093
  10.90.4.196:3000   grafana       10.90.4.196  3000         linux/x86_64  Up      -                             /tidb-deploy/grafana-3000
  10.90.2.26:2379    pd            10.90.2.26   2379/2380    linux/x86_64  Up|L    /tidb-data/pd-2379            /tidb-deploy/pd-2379
  10.90.4.196:9090   prometheus    10.90.4.196  9090/12020   linux/x86_64  Up      /tidb-data/prometheus-9090    /tidb-deploy/prometheus-9090
  10.90.1.149:4000   tidb          10.90.1.149  4000/10080   linux/x86_64  Up      -                             /tidb-deploy/tidb-4000
  10.90.3.216:20160  tikv          10.90.3.216  20160/20180  linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
  10.90.3.225:20160  tikv          10.90.3.225  20160/20180  linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
  10.90.3.95:20160   tikv          10.90.3.95   20160/20180  linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
  Total nodes: 8
  ```

### 手順 3. 大量のテーブル使用に必要な 2 つの変数を有効にする

+ データベースユーザー `root` として TiDB サーバーインスタンスに接続します。
  
  ```bash
  mysql -h ${HOST_DB1_PRIVATE_IP} -P 4000 -u root
  ```
  ```output
  mysql> 
  ```

+ テーブル作成高速化変数 `tidb_enable_fast_create_table` を確認します。`v8.5` 以降のデフォルト値は `ON` です。v8.4.0 以前のバージョンからアップグレードしたクラスターの場合、デフォルト値は `OFF` のままです。

  ```sql
  show variables like 'tidb_enable_fast_create_table';
  ```
  ```output
  +-------------------------------+-------+
  | Variable_name                 | Value |
  +-------------------------------+-------+
  | tidb_enable_fast_create_table | ON    |
  +-------------------------------+-------+
  1 row in set (0.00 sec)
  ```

+ スキーマキャッシュサイズ変数 `tidb_schema_cache_size` を確認します。単一クラスター内のテーブル数が 10 万を超える場合、Infoschema v2 Cache Operation メトリクスを監視し、キャッシュサイズの値（デフォルトは 512MB）を調整してキャッシュヒット率を 85% 以上に維持します。grafana にアクセスして、`TiDB` -> `Schema Load` -> `Infoschema v2 Cache Operation` & `Infoschema v2 Cache Size` のパスでメトリクスを確認できます。

  ```sql
  show variables like 'tidb_schema_cache_size';
  ```
  ```output
  +------------------------+-----------+
  | Variable_name          | Value     |
  +------------------------+-----------+
  | tidb_schema_cache_size | 536870912 |
  +------------------------+-----------+
  1 row in set (0.00 sec)
  ```

  ```sql
  EXIT;
  ```
  ```output
  mysql> EXIT;
  ```

### 手順 4. sysbench の設定を準備する

+ sysbench ツールを取得します。このモジュールでは、sysbench を使用してデータベースとテーブルを並列に作成します。

  ```bash
  git clone https://github.com/lilinghai/sysbench/
  ```
  
  ```output
  Cloning into 'sysbench'...
  remote: Enumerating objects: 10789, done.
  remote: Counting objects: 100% (271/271), done.
  remote: Compressing objects: 100% (172/172), done.
  remote: Total 10789 (delta 119), reused 107 (delta 94), pack-reused 10518 (from 4)
  Receiving objects: 100% (10789/10789), 5.37 MiB | 29.74 MiB/s, done.
  Resolving deltas: 100% (7534/7534), done.
  ```

+ `sysbench` の設定をセットアップします。このハンズオン演習では 64 スレッドを使用します。
  
  ```bash
  cat << EOF > sysbench_config
  mysql-host=${HOST_DB1_PRIVATE_IP}
  mysql-port=4000
  mysql-user=root
  mysql-password=
  mysql-db=test
  time=60
  threads=64
  report-interval=1
  EOF
  cat sysbench_config
  ```
  ```output
  mysql-host=10.90.1.125
  mysql-port=4000
  mysql-user=root
  mysql-password=
  mysql-db=test
  time=60
  threads=64
  report-interval=1
  ```

### 手順 5. sysbench を使用して 10,000 テーブルを作成する

+ 1000 のデータベースを作成し、各データベースに 10 のテーブルを作成します。各テーブルには 100 の初期行があります。これは、SaaS アプリケーションが初期化される時点をシミュレートするために使用されます。多くのデータベースとテーブルを作成し、各スキーマは各テナントを表します。約 2 分かかりますが、演習の TiDB クラスターではマイクロインスタンスを使用しており、これは本番環境相当の環境ではないことに注意してください。

+ 実際の本番環境では、同じ速度で数百万のテーブルを作成できます。

  ```bash
  cd ~/sysbench/workloads/many_db_tables/
  sysbench oltp_read_write preparedb \
    --config-file=/home/ec2-user/sysbench_config --db_prefix=sbtest --dbs=1000 --tables=10 --table_size=100
  sysbench oltp_read_write preparetable \
    --config-file=/home/ec2-user/sysbench_config --db_prefix=sbtest --dbs=1000 --tables=10 --table_size=100
  sysbench oltp_read_write preparedata \
    --config-file=/home/ec2-user/sysbench_config --db_prefix=sbtest --dbs=1000 --tables=10 --table_size=100
  cd ~
  ```
  
  ```output
  sysbench 1.0.17 (using bundled LuaJIT 2.1.0-beta2)
  
  Initializing worker threads...
  ...
  Creating database 'sbtest15'...
  Creating database 'sbtest3'...
  Creating database 'sbtest10'...
  Creating database 'sbtest5'...
  ...
  Creating table sbtest2.sbtest2 ...
  Creating table sbtest6.sbtest2 ...
  Creating table sbtest7.sbtest2 ...
  Creating table sbtest5.sbtest2 ...
  Creating table sbtest1.sbtest2 ...
  Creating table sbtest2.sbtest1 ...
  Creating table sbtest8.sbtest1 ...
  Creating table sbtest1.sbtest1 ...
  Creating table sbtest6.sbtest1 ...
  Creating table sbtest7.sbtest1 ...
  Creating table sbtest4.sbtest2 ...
  ...
  Inserting 100 records into 'sbtest1.sbtest2'
  Inserting 100 records into 'sbtest1.sbtest1'
  Inserting 100 records into 'sbtest1.sbtest7'
  Inserting 100 records into 'sbtest1.sbtest3'
  Inserting 100 records into 'sbtest1.sbtest6'
  ...
  ```

### 手順 6. 作成したスキーマを確認する

+ データベースユーザー `root` として TiDB サーバーインスタンスに接続します。
  
  ```bash
  mysql -h ${HOST_DB1_PRIVATE_IP} -P 4000 -u root
  ```
  ```output
  mysql> 
  ```

+ スキーマを確認します。

  ```sql
  SHOW DATABASES;
  ```
  ```output
  +--------------------+
  | Database           |
  +--------------------+
  | INFORMATION_SCHEMA |
  | METRICS_SCHEMA     |
  | PERFORMANCE_SCHEMA |
  | mysql              |
  | sbtest1            |
  | sbtest10           |
  ...
  | sbtest987          |
  | sbtest988          |
  | sbtest989          |
  | sbtest99           |
  | sbtest990          |
  | sbtest991          |
  | sbtest992          |
  | sbtest993          |
  | sbtest994          |
  | sbtest995          |
  | sbtest996          |
  | sbtest997          |
  | sbtest998          |
  | sbtest999          |
  | sys                |
  | test               |
  +--------------------+
  1006 rows in set (0.00 sec)
  ```

+ テーブルを確認し、`sbtest1000` スキーマの任意のテーブルの行数を確認します。
  
  ```sql
  USE sbtest1000;
  SHOW TABLES;
  SELECT COUNT(1) FROM sbtest10;
  ```
  ```output
  Database changed
  +----------------------+
  | Tables_in_sbtest1000 |
  +----------------------+
  | sbtest1              |
  | sbtest10             |
  | sbtest2              |
  | sbtest3              |
  | sbtest4              |
  | sbtest5              |
  | sbtest6              |
  | sbtest7              |
  | sbtest8              |
  | sbtest9              |
  +----------------------+
  10 rows in set (0.00 sec)
  ...
  +----------+
  | count(1) |
  +----------+
  |      100 |
  +----------+
  1 row in set (0.00 sec)
  ```

+ 新しいテナントが SaaS アプリケーションの利用を始める際、TiDB ではメタデータの作成と初期データの投入が非常に高速です。
+ mysql クライアントを TiDB に接続したまま、次のモジュールに進みます。

## モジュール 2: クロススキーマ実行計画バインディング

SaaS ワークロードでは、テナント間でクエリパターンの変動が発生し、実行計画が一貫しないことがよくあります。TiDB の SQL Plan Management (SPM) は、グローバル実行計画バインディングを可能にし、異なるスキーマ間で安定した最適なクエリパフォーマンスを確保します。

### 手順 1. クロススキーマ実行計画バインディングを有効にする

+ システム変数 [`tidb_opt_enable_fuzzy_binding`](https://docs.pingcap.com/tidb/stable/system-variables/#tidb_opt_enable_fuzzy_binding-new-in-v760) を `ON` に設定して、グローバルと現在のセッションの両方でクロススキーマ実行計画バインディングを有効にします。

  ```sql
  SHOW VARIABLES LIKE 'tidb_opt_enable_fuzzy_binding';
  ```
  ```output
  +-------------------------------+-------+
  | Variable_name                 | Value |
  +-------------------------------+-------+
  | tidb_opt_enable_fuzzy_binding | OFF   |
  +-------------------------------+-------+
  1 row in set (0.00 sec)
  ```

  ```sql
  SET GLOBAL tidb_opt_enable_fuzzy_binding=1;
  SET tidb_opt_enable_fuzzy_binding=1;
  SHOW VARIABLES like 'tidb_opt_enable_fuzzy_binding';
  ```
  ```output
  +-------------------------------+-------+
  | Variable_name                 | Value |
  +-------------------------------+-------+
  | tidb_opt_enable_fuzzy_binding | ON    |
  +-------------------------------+-------+
  ```

### 手順 2. クロススキーマ実行計画バインディングを作成する

+ モジュール 1 で、1000 のスキーマを作成し、それらすべてに同じ定義の `sbtest1` と `sbtest2` という名前のテーブルがあります。実行計画バインディングのターゲットスキーマとして `sbtest1` と `sbtest999` を選択します。
  
  ```sql
  SHOW CREATE TABLE sbtest1.sbtest1\G
  SHOW CREATE TABLE sbtest999.sbtest1\G
  ```
  
  ```output
  *************************** 1. row ***************************
         Table: sbtest1
  Create Table: CREATE TABLE `sbtest1` (
    `id` int NOT NULL AUTO_INCREMENT,
    `k` int NOT NULL DEFAULT '0',
    `c` char(120) NOT NULL DEFAULT '',
    `pad` char(60) NOT NULL DEFAULT '',
    PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,
    KEY `k_1` (`k`)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=30001
  1 row in set (0.00 sec)

  *************************** 1. row ***************************
         Table: sbtest1
  Create Table: CREATE TABLE `sbtest1` (
    `id` int NOT NULL AUTO_INCREMENT,
    `k` int NOT NULL DEFAULT '0',
    `c` char(120) NOT NULL DEFAULT '',
    `pad` char(60) NOT NULL DEFAULT '',
    PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,
    KEY `k_1` (`k`)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=30001
  1 row in set (0.01 sec)
  ```

+ 複数のスキーマ（この場合は `sbtest1` と `sbtest999`）にわたる下記の SQL の実行計画をサンプリングします。

  ```sql
  EXPLAIN SELECT COUNT(*) AS num 
    FROM sbtest1.sbtest1 AS t1,sbtest1.sbtest2 AS t2 
    WHERE t1.id=t2.id AND t1.id>10 AND t1.id<100;
  EXPLAIN SELECT COUNT(*) AS num 
    FROM sbtest999.sbtest1 AS t1,sbtest999.sbtest2 AS t2 
    WHERE t1.id=t2.id AND t1.id>10 AND t1.id<100;
  ```

  ```output
  +-------------------------------+---------+-----------+---------------+-----------------------------------------------------------------------+
  | id                            | estRows | task      | access object | operator info                                                         |
  +-------------------------------+---------+-----------+---------------+-----------------------------------------------------------------------+
  | StreamAgg_10                  | 1.00    | root      |               | funcs:count(1)->Column#9                                              |
  | └─MergeJoin_44                | 3.12    | root      |               | inner join, left key:sbtest1.sbtest1.id, right key:sbtest1.sbtest2.id |
  |   ├─TableReader_39(Build)     | 2.50    | root      |               | data:TableRangeScan_38                                                |
  |   │ └─TableRangeScan_38       | 2.50    | cop[tikv] | table:t2      | range:(10,100), keep order:true, stats:partial[id:unInitialized]      |
  |   └─TableReader_37(Probe)     | 2.50    | root      |               | data:TableRangeScan_36                                                |
  |     └─TableRangeScan_36       | 2.50    | cop[tikv] | table:t1      | range:(10,100), keep order:true, stats:partial[id:unInitialized]      |
  +-------------------------------+---------+-----------+---------------+-----------------------------------------------------------------------+


  +-------------------------------+---------+-----------+---------------+---------------------------------------------------------------------------+
  | id                            | estRows | task      | access object | operator info                                                             |
  +-------------------------------+---------+-----------+---------------+---------------------------------------------------------------------------+
  | StreamAgg_10                  | 1.00    | root      |               | funcs:count(1)->Column#9                                                  |
  | └─MergeJoin_44                | 3.12    | root      |               | inner join, left key:sbtest999.sbtest1.id, right key:sbtest999.sbtest2.id |
  |   ├─TableReader_39(Build)     | 2.50    | root      |               | data:TableRangeScan_38                                                    |
  |   │ └─TableRangeScan_38       | 2.50    | cop[tikv] | table:t2      | range:(10,100), keep order:true, stats:pseudo                             |
  |   └─TableReader_37(Probe)     | 2.50    | root      |               | data:TableRangeScan_36                                                    |
  |     └─TableRangeScan_36       | 2.50    | cop[tikv] | table:t1      | range:(10,100), keep order:true, stats:pseudo                             |
  +-------------------------------+---------+-----------+---------------+---------------------------------------------------------------------------+
  ```

+ `sbtest1` と `sbtest999` スキーマの上記のクエリのデフォルト実行計画が同じであり、テーブル結合に `MergeJoin` を使用していることを確認します。

+ ここで DBA は、上記のクエリのパフォーマンスを向上させるために、すべてのスキーマで `MergeJoin` の代わりに `HashJoin` メソッドを使用することを決定します。`CREATE GLOBAL BINDING` ステートメントでは、`*.sbtest2` と `*.sbtest1` の `*` はすべてのスキーマを表し、実行計画バインディングがすべてのスキーマでこのクエリを実行する際に有効になることを意味します。

  ```sql
  CREATE GLOBAL BINDING USING 
    SELECT /*+ HASH_JOIN(t1, t2) */ COUNT(*) AS num FROM *.sbtest1 AS t1, *.sbtest2 AS t2 WHERE t1.id=t2.id AND t1.id>10 AND t1.id<100;
  CREATE SESSION BINDING USING 
    SELECT /*+ HASH_JOIN(t1, t2) */ COUNT(*) AS num FROM *.sbtest1 AS t1, *.sbtest2 AS t2 WHERE t1.id=t2.id AND t1.id>10 AND t1.id<100;
  ```
  ```output
  Query OK, 0 rows affected (0.02 sec)
  Query OK, 0 rows affected (0.00 sec)
  ```

+ バインディング設定を確認します。`Default_db` が空であることを確認します。これは、この実行計画がすべてのスキーマで有効であることを示しています。

  ```sql
  SHOW BINDINGS\G
  ```
  ```output
  *************************** 1. row ***************************
  Original_sql: select * from ( ? . `sbtest1` as `t1` ) join `*` . `sbtest2` as `t2` where `t1` . `id` = `t2` . `id` and `t1` . `id` > ? and `t1` . `id` < ?
      Bind_sql: SELECT /*+ HASH_JOIN(`t1`, `t2`)*/ * FROM (`*`.`sbtest1` AS `t1`) JOIN `*`.`sbtest2` AS `t2` WHERE `t1`.`id` = `t2`.`id` AND `t1`.`id` > 10 AND `t1`.`id` < 100
    Default_db:
        Status: enabled
   Create_time: 2025-02-24 09:33:23.736
   Update_time: 2025-02-24 09:33:23.736
       Charset: utf8
     Collation: utf8_general_ci
        Source: manual
    Sql_digest: 107feec0308c4e216b296a9b5ca03638bf400cb17157f3df35d5dd52460a10ef
   Plan_digest:
  1 row in set (0.00 sec)
  ```

+ バインディング結果を確認します。3つのスキーマ `sbtest1`、`sbtest666`、`sbtest999` からのサンプルクエリで、CBO がバインディングで指定された `HashJoin` を選択していることを確認します。
  
  ```sql
  EXPLAIN SELECT COUNT(*) AS num 
    FROM sbtest1.sbtest1 AS t1,sbtest1.sbtest2 AS t2 
    WHERE t1.id=t2.id AND t1.id>10 AND t1.id<100;
  EXPLAIN SELECT COUNT(*) AS num 
    FROM sbtest666.sbtest1 AS t1,sbtest666.sbtest2 AS t2 
    WHERE t1.id=t2.id AND t1.id>10 AND t1.id<100;
  EXPLAIN SELECT COUNT(*) AS num 
    FROM sbtest999.sbtest1 AS t1,sbtest999.sbtest2 AS t2 
    WHERE t1.id=t2.id AND t1.id>10 AND t1.id<100;
  ```
  ```output
  +-------------------------------+---------+-----------+---------------+----------------------------------------------------------------+
  | id                            | estRows | task      | access object | operator info                                                  |
  +-------------------------------+---------+-----------+---------------+----------------------------------------------------------------+
  | StreamAgg_10                  | 1.00    | root      |               | funcs:count(1)->Column#9                                       |
  | └─HashJoin_63                 | 3.12    | root      |               | inner join, equal:[eq(sbtest1.sbtest1.id, sbtest1.sbtest2.id)] |
  |   ├─TableReader_39(Build)     | 2.50    | root      |               | data:TableRangeScan_38                                         |
  |   │ └─TableRangeScan_38       | 2.50    | cop[tikv] | table:t2      | range:(10,100), keep order:false, stats:pseudo                 |
  |   └─TableReader_37(Probe)     | 2.50    | root      |               | data:TableRangeScan_36                                         |
  |     └─TableRangeScan_36       | 2.50    | cop[tikv] | table:t1      | range:(10,100), keep order:false, stats:pseudo                 |
  +-------------------------------+---------+-----------+---------------+----------------------------------------------------------------+

  +-------------------------------+---------+-----------+---------------+--------------------------------------------------------------------+
  | id                            | estRows | task      | access object | operator info                                                      |
  +-------------------------------+---------+-----------+---------------+--------------------------------------------------------------------+
  | id                            | estRows | task      | access object | operator info                                                      |
  +-------------------------------+---------+-----------+---------------+--------------------------------------------------------------------+
  | StreamAgg_10                  | 1.00    | root      |               | funcs:count(1)->Column#9                                           |
  | └─HashJoin_63                 | 3.12    | root      |               | inner join, equal:[eq(sbtest666.sbtest1.id, sbtest666.sbtest2.id)] |
  |   ├─TableReader_39(Build)     | 2.50    | root      |               | data:TableRangeScan_38                                             |
  |   │ └─TableRangeScan_38       | 2.50    | cop[tikv] | table:t2      | range:(10,100), keep order:false, stats:pseudo                     |
  |   └─TableReader_37(Probe)     | 2.50    | root      |               | data:TableRangeScan_36                                             |
  |     └─TableRangeScan_36       | 2.50    | cop[tikv] | table:t1      | range:(10,100), keep order:false, stats:pseudo                     |
  +-------------------------------+---------+-----------+---------------+--------------------------------------------------------------------+

  +-------------------------------+---------+-----------+---------------+--------------------------------------------------------------------+
  | id                            | estRows | task      | access object | operator info                                                      |
  +-------------------------------+---------+-----------+---------------+--------------------------------------------------------------------+
  | StreamAgg_10                  | 1.00    | root      |               | funcs:count(1)->Column#9                                           |
  | └─HashJoin_63                 | 3.12    | root      |               | inner join, equal:[eq(sbtest999.sbtest1.id, sbtest999.sbtest2.id)] |
  |   ├─TableReader_39(Build)     | 2.50    | root      |               | data:TableRangeScan_38                                             |
  |   │ └─TableRangeScan_38       | 2.50    | cop[tikv] | table:t2      | range:(10,100), keep order:false, stats:pseudo                     |
  |   └─TableReader_37(Probe)     | 2.50    | root      |               | data:TableRangeScan_36                                             |
  |     └─TableRangeScan_36       | 2.50    | cop[tikv] | table:t1      | range:(10,100), keep order:false, stats:pseudo                     |
  +-------------------------------+---------+-----------+---------------+--------------------------------------------------------------------+
  ```

### 手順 3. クロススキーマ実行計画バインディングの削除

+ 既存のバインディングを確認します。後で使用するため、バインディングの SQL ダイジェストに注意してください。これは `Sql_digest` フィールドに表示されます。

  ```sql
  SHOW BINDINGS\G
  ```

  ```output
  Original_sql: select count ( ? ) as `num` from ( ? . `sbtest1` as `t1` ) join `*` . `sbtest2` as `t2` where `t1` . `id` = `t2` . `id` and `t1` . `id` > ? and `t1` . `id` < ?
      Bind_sql: SELECT /*+ HASH_JOIN(`t1`, `t2`)*/ count(1) AS `num` FROM (`*`.`sbtest1` AS `t1`) JOIN `*`.`sbtest2` AS `t2` WHERE `t1`.`id` = `t2`.`id` AND `t1`.`id` > 10 AND `t1`.`id` < 100
    Default_db:
        Status: enabled
   Create_time: 2025-02-24 09:49:15.265
   Update_time: 2025-02-24 09:49:15.265
       Charset: utf8
     Collation: utf8_general_ci
        Source: manual
    Sql_digest: 4ea75bf0f868417b7a8536ed2c2a9e016197db36d38697e69a15533768fc9910
   Plan_digest:
  ```

+ バインディングを削除します

  ```sql
  DROP GLOBAL BINDING FOR SQL DIGEST '4ea75bf0f868417b7a8536ed2c2a9e016197db36d38697e69a15533768fc9910';
  DROP SESSION BINDING FOR SQL DIGEST '4ea75bf0f868417b7a8536ed2c2a9e016197db36d38697e69a15533768fc9910';
  ```
  ```output
  Query OK, 1 row affected (0.01 sec)
  ```

+ バインディングが削除されたことを確認します
  
  ```sql
  SHOW BINDINGS\G
  ```
  ```output
  Empty set (0.00 sec)
  ```

+ クロススキーマ実行計画バインディングを使用することで、単一のバインディング定義で複数のスキーマのクエリパフォーマンスを向上させることができます。この例では、1000 のスキーマに対して有効です。

+ mysql クライアントを TiDB サーバーインスタンスに接続したまま、次のモジュールに進んでください。

## モジュール 3: マルチテナント環境でのシステムリソース管理

このモジュールでは、リソース制御を使用して TiDB 内のリソースグループを効果的に管理する方法を説明します。これにより、異なるデータベースユーザーが制御された必要なリソースを受け取ることができます。そのため、異なるユーザーが所有するワークロードを事前に指定された優先度に分類することができます。TiDB リソース制御は、`RU_PER_SEC`、`PRIORITY`、`BURSTABLE` などのリソースグループ属性を利用して、DBA がシステムリソース制御を実現するのに役立ちます。この演習では `RU_PER_SEC` のみを使用します。

### 手順 1. データベースマルチテナント設定

+ データベースレベルで 2 つのテナントとして機能する 2 つのデータベースユーザーを作成します。`t999` はスキーマ `sbtest999` にアクセスし、`t1000` はスキーマ `sbtest1000` にアクセスします。
+ `t1000` と `sbtest1000` がプレミアムテナント構成であり、`t999` と `sbtest999` が無料ティアテナント構成であると仮定しましょう。

  ```sql
  CREATE USER 't999'@'%' IDENTIFIED BY 'tidb';
  CREATE USER 't1000'@'%' IDENTIFIED BY 'tidb';
  GRANT ALL PRIVILEGES ON sbtest999.*  TO 't999'@'%';
  GRANT ALL PRIVILEGES ON sbtest1000.*  TO 't1000'@'%';
  ```
  ```output
  Query OK, 0 rows affected (0.02 sec)

  Query OK, 0 rows affected (0.01 sec)

  Query OK, 0 rows affected (0.02 sec)

  Query OK, 0 rows affected (0.02 sec)
  ```

+ SQL インターフェースを終了します。

  ```sql
  EXIT
  ```

+ ここをクリック - [ターミナル 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/
    ```

+ これで、リモート Linux に接続された 2 つのターミナルが用意されました。

### 手順 2. 両方の接続からサンプルワークロードを開始し、ベースラインを観察する

+ **ターミナル 1** と **ターミナル 2** の両方でサンプルワークロードを開始します。サンプルワークロードは約 1 分間実行されます。

+ 以下のコマンドを [ターミナル 1](ssh://monitor1-ssh#1) に **コピー** します。これは、ユーザー `t999` としてスキーマ `sbtest999` でサンプルワークロードを実行します。実行せずに、[ターミナル 2](ssh://monitor1-ssh#2) に切り替えてください。

  ```bash
  clear
  cat << EOF > t999_sysbench_config
  mysql-host=${HOST_DB1_PRIVATE_IP}
  mysql-port=4000
  mysql-user=t999
  mysql-password=tidb
  mysql-db=sbtest999
  time=60
  threads=1
  report-interval=1
  EOF
  sysbench oltp_read_write run --config-file=t999_sysbench_config --tables=10 --table_size=100
  ```

+ 以下のコマンドを [ターミナル 2](ssh://monitor1-ssh#2) に **コピー** します。これは、ユーザー `t1000` としてスキーマ `sbtest1000` でサンプルワークロードを実行します。実行しないでください。

  ```bash
  clear
  cat << EOF > t1000_sysbench_config
  mysql-host=${HOST_DB1_PRIVATE_IP}
  mysql-port=4000
  mysql-user=t1000
  mysql-password=tidb
  mysql-db=sbtest1000
  time=60
  threads=1
  report-interval=1
  EOF
  sysbench oltp_read_write run --config-file=t1000_sysbench_config --tables=10 --table_size=100
  ```

+ 両方のターミナルが同じワークロードを実行する準備ができたので、[ターミナル 1](ssh://monitor1-ssh#1) で <kbd>ENTER</kbd> キーを押し、すぐに [ターミナル 2](ssh://monitor1-ssh#2) に切り替えて、再度 <kbd>ENTER</kbd> を押します。

+ ワークロードは 1 分間実行されます。両方のターミナルからの sysbench 出力を比較します。`tps` と `qps` を確認すると、以下のサンプル出力のように類似していることがわかります。

+ [ターミナル 1](ssh://monitor1-ssh#1) では、トランザクションは約 `79.95 per sec`、クエリは `1598.91 per sec` です。

  ```output
  ...
  [ 55s ] thds: 1 tps: 83.00 qps: 1655.99 (r/w/o: 1157.99/332.00/166.00) lat (ms,95%): 15.00 err/s: 0.00 reconn/s: 0.00
  [ 56s ] thds: 1 tps: 74.00 qps: 1486.01 (r/w/o: 1042.01/296.00/148.00) lat (ms,95%): 16.12 err/s: 0.00 reconn/s: 0.00
  [ 57s ] thds: 1 tps: 78.00 qps: 1551.99 (r/w/o: 1083.99/312.00/156.00) lat (ms,95%): 15.83 err/s: 0.00 reconn/s: 0.00
  [ 58s ] thds: 1 tps: 77.00 qps: 1555.00 (r/w/o: 1092.00/309.00/154.00) lat (ms,95%): 15.55 err/s: 0.00 reconn/s: 0.00
  [ 59s ] thds: 1 tps: 79.00 qps: 1567.00 (r/w/o: 1094.00/315.00/158.00) lat (ms,95%): 15.55 err/s: 0.00 reconn/s: 0.00
  [ 60s ] thds: 1 tps: 78.00 qps: 1557.99 (r/w/o: 1090.00/312.00/156.00) lat (ms,95%): 14.46 err/s: 0.00 reconn/s: 0.00
  SQL statistics:
      queries performed:
          read:                            67172
          write:                           19192
          other:                           9596
          total:                           95960
      transactions:                        4798   (79.95 per sec.)
      queries:                             95960  (1598.91 per sec.)
      ignored errors:                      0      (0.00 per sec.)
      reconnects:                          0      (0.00 per sec.)

  General statistics:
      total time:                          60.0146s
      total number of events:              4798

  Latency (ms):
           min:                                    8.86
           avg:                                   12.51
           max:                                   28.92
           95th percentile:                       15.83
           sum:                                60008.24

  Threads fairness:
      events (avg/stddev):           4798.0000/0.00
      execution time (avg/stddev):   60.0082/0.00
  ```

+ [ターミナル 2](ssh://monitor1-ssh#2) では、トランザクションは約 `80.57 per sec`、クエリは `1611.43 per sec` です。

  ```output
  ...
  [ 55s ] thds: 1 tps: 81.00 qps: 1609.01 (r/w/o: 1124.01/323.00/162.00) lat (ms,95%): 14.73 err/s: 0.00 reconn/s: 0.00
  [ 56s ] thds: 1 tps: 76.00 qps: 1534.04 (r/w/o: 1074.03/308.01/152.00) lat (ms,95%): 16.41 err/s: 0.00 reconn/s: 0.00
  [ 57s ] thds: 1 tps: 83.00 qps: 1660.01 (r/w/o: 1162.01/332.00/166.00) lat (ms,95%): 14.21 err/s: 0.00 reconn/s: 0.00
  [ 58s ] thds: 1 tps: 82.00 qps: 1639.99 (r/w/o: 1147.99/328.00/164.00) lat (ms,95%): 15.27 err/s: 0.00 reconn/s: 0.00
  [ 59s ] thds: 1 tps: 87.00 qps: 1739.99 (r/w/o: 1217.99/348.00/174.00) lat (ms,95%): 13.70 err/s: 0.00 reconn/s: 0.00
  [ 60s ] thds: 1 tps: 84.00 qps: 1672.99 (r/w/o: 1172.99/332.00/168.00) lat (ms,95%): 17.01 err/s: 0.00 reconn/s: 0.00
  SQL statistics:
      queries performed:
          read:                            67690
          write:                           19340
          other:                           9670
          total:                           96700
      transactions:                        4835   (80.57 per sec.)
      queries:                             96700  (1611.43 per sec.)
      ignored errors:                      0      (0.00 per sec.)
      reconnects:                          0      (0.00 per sec.)

  General statistics:
      total time:                          60.0074s
      total number of events:              4835

  Latency (ms):
           min:                                    8.53
           avg:                                   12.41
           max:                                   35.83
           95th percentile:                       15.83
           sum:                                60000.84

  Threads fairness:
      events (avg/stddev):           4835.0000/0.00
      execution time (avg/stddev):   60.0008/0.00
  ```

### 手順 3. 異なるテナントに RU クォータを設定する

+ ここで興味深いことが起こります。システムが圧迫されているとします。スケールアウトを決定する前に、プレミアムユーザー（t1000 など）により多くのシステムリソースを与え、無料ティアテナント（t999 など）には少なくしたいとします。

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

  ```bash
  mysql -h ${HOST_DB1_PRIVATE_IP} -P 4000 -u root
  ```
  ```output
  mysql> 
  ```

+ ユーザー `t999` とユーザー `t1000` に対して、[リクエストユニット（RU）per second](https://docs.pingcap.com/tidb/stable/tidb-resource-control-ru-groups/#what-is-request-unit-ru) で異なるクォータを持つリソースグループを設定します。`t999` は `1000`、`t1000` は `20000` を取得します。

  ```sql
  DROP RESOURCE GROUP IF EXISTS free_tier;
  DROP RESOURCE GROUP IF EXISTS premium;
  CREATE RESOURCE GROUP free_tier RU_PER_SEC = 1000;
  CREATE RESOURCE GROUP premium RU_PER_SEC = 20000;
  ALTER USER t999 RESOURCE GROUP free_tier;
  ALTER USER t1000 RESOURCE GROUP premium;
  SELECT * FROM information_schema.resource_groups;
  SELECT Host, user, User_attributes FROM mysql.user WHERE user IN ('t999','t1000');
  EXIT
  ```
  ```output
  +-----------+------------+----------+-----------+-------------+------------+
  | NAME      | RU_PER_SEC | PRIORITY | BURSTABLE | QUERY_LIMIT | BACKGROUND |
  +-----------+------------+----------+-----------+-------------+------------+
  | default   | UNLIMITED  | MEDIUM   | YES       | NULL        | NULL       |
  | free_tier | 1000       | MEDIUM   | NO        | NULL        | NULL       |
  | premium   | 20000      | MEDIUM   | NO        | NULL        | NULL       |
  +-----------+------------+----------+-----------+-------------+------------+
  3 rows in set (0.00 sec)
  +------+-------+---------------------------------+
  | Host | user  | User_attributes                 |
  +------+-------+---------------------------------+
  | %    | t999  | {"resource_group": "free_tier"} |
  | %    | t1000 | {"resource_group": "premium"}   |
  +------+-------+---------------------------------+
  2 rows in set (0.00 sec)
  ```

### 手順 4. サンプルワークロードを再度開始し、結果を観察する

+ 以下のコマンドを [ターミナル 1](ssh://monitor1-ssh#1) に **コピー** します。これは、ユーザー `t999` としてスキーマ `sbtest999` でサンプルワークロードを実行します。実行せずに、[ターミナル 2](ssh://monitor1-ssh#2) に切り替えてください。

  ```bash
  clear
  cat << EOF > t999_sysbench_config
  mysql-host=${HOST_DB1_PRIVATE_IP}
  mysql-port=4000
  mysql-user=t999
  mysql-password=tidb
  mysql-db=sbtest999
  time=60
  threads=1
  report-interval=1
  EOF
  sysbench oltp_read_write run --config-file=t999_sysbench_config --tables=10 --table_size=100
  ```

+ 以下のコマンドを [ターミナル 2](ssh://monitor1-ssh#2) に **コピー** します。これは、ユーザー `t1000` としてスキーマ `sbtest1000` でサンプルワークロードを実行します。実行しないでください。

  ```bash
  clear
  cat << EOF > t1000_sysbench_config
  mysql-host=${HOST_DB1_PRIVATE_IP}
  mysql-port=4000
  mysql-user=t1000
  mysql-password=tidb
  mysql-db=sbtest1000
  time=60
  threads=1
  report-interval=1
  EOF
  sysbench oltp_read_write run --config-file=t1000_sysbench_config --tables=10 --table_size=100
  ```

+ 両方のターミナルが同じワークロードを実行する準備ができたので、[ターミナル 1](ssh://monitor1-ssh#1) で <kbd>ENTER</kbd> キーを押し、すぐに [ターミナル 2](ssh://monitor1-ssh#2) に切り替えて、再度 <kbd>ENTER</kbd> を押します。

+ 両方のターミナルからの sysbench 出力を比較します。`tps` と `qps` を確認すると、今回は同じではありません。

+ [ターミナル 1](ssh://monitor1-ssh#1) では、無料ティアテナントのトランザクションは約 `20.41 per sec`、クエリは `408.21 per sec` です。

  ```output
  ...
  [ 55s ] thds: 1 tps: 20.00 qps: 403.00 (r/w/o: 282.00/81.00/40.00) lat (ms,95%): 54.83 err/s: 0.00 reconn/s: 0.00
  [ 56s ] thds: 1 tps: 20.00 qps: 401.00 (r/w/o: 280.00/81.00/40.00) lat (ms,95%): 54.83 err/s: 0.00 reconn/s: 0.00
  [ 57s ] thds: 1 tps: 20.00 qps: 384.00 (r/w/o: 266.00/78.00/40.00) lat (ms,95%): 55.82 err/s: 0.00 reconn/s: 0.00
  [ 58s ] thds: 1 tps: 20.00 qps: 414.99 (r/w/o: 294.00/81.00/40.00) lat (ms,95%): 54.83 err/s: 0.00 reconn/s: 0.00
  [ 59s ] thds: 1 tps: 19.00 qps: 383.01 (r/w/o: 266.00/79.00/38.00) lat (ms,95%): 55.82 err/s: 0.00 reconn/s: 0.00
  [ 60s ] thds: 1 tps: 20.00 qps: 400.00 (r/w/o: 280.00/80.00/40.00) lat (ms,95%): 55.82 err/s: 0.00 reconn/s: 0.00
  SQL statistics:
      queries performed:
          read:                            17150
          write:                           4900
          other:                           2450
          total:                           24500
      transactions:                        1225   (20.41 per sec.)
      queries:                             24500  (408.21 per sec.)
      ignored errors:                      0      (0.00 per sec.)
      reconnects:                          0      (0.00 per sec.)

  General statistics:
      total time:                          60.0175s
      total number of events:              1225

  Latency (ms):
           min:                                   11.11
           avg:                                   48.99
           max:                                   58.21
           95th percentile:                       55.82
           sum:                                60014.80

  Threads fairness:
      events (avg/stddev):           1225.0000/0.00
      execution time (avg/stddev):   60.0148/0.00
  ```

+ [ターミナル 2](ssh://monitor1-ssh#2) では、プレミアムテナントのトランザクションは約 `82.60 per sec`、クエリは `1652.09 per sec` です。

  ```output
  ...
  [ 55s ] thds: 1 tps: 81.00 qps: 1617.00 (r/w/o: 1132.00/323.00/162.00) lat (ms,95%): 15.55 err/s: 0.00 reconn/s: 0.00
  [ 56s ] thds: 1 tps: 82.00 qps: 1635.00 (r/w/o: 1143.00/328.00/164.00) lat (ms,95%): 14.21 err/s: 0.00 reconn/s: 0.00
  [ 57s ] thds: 1 tps: 82.00 qps: 1645.00 (r/w/o: 1153.00/328.00/164.00) lat (ms,95%): 14.21 err/s: 0.00 reconn/s: 0.00
  [ 58s ] thds: 1 tps: 89.00 qps: 1774.00 (r/w/o: 1240.00/356.00/178.00) lat (ms,95%): 12.98 err/s: 0.00 reconn/s: 0.00
  [ 59s ] thds: 1 tps: 81.00 qps: 1622.00 (r/w/o: 1136.00/324.00/162.00) lat (ms,95%): 15.55 err/s: 0.00 reconn/s: 0.00
  [ 60s ] thds: 1 tps: 84.00 qps: 1685.00 (r/w/o: 1181.00/336.00/168.00) lat (ms,95%): 14.73 err/s: 0.00 reconn/s: 0.00
  SQL statistics:
      queries performed:
          read:                            69398
          write:                           19828
          other:                           9914
          total:                           99140
      transactions:                        4957   (82.60 per sec.)
      queries:                             99140  (1652.09 per sec.)
      ignored errors:                      0      (0.00 per sec.)
      reconnects:                          0      (0.00 per sec.)

  General statistics:
      total time:                          60.0076s
      total number of events:              4957

  Latency (ms):
           min:                                    8.78
           avg:                                   12.10
           max:                                   38.10
           95th percentile:                       15.27
           sum:                                60001.22

  Threads fairness:
      events (avg/stddev):           4957.0000/0.00
      execution time (avg/stddev):   60.0012/0.00
  ```

+ これでモジュール 3 は終了です。[ターミナル 1](ssh://monitor1-ssh#1) のワークロードが意図的に遅くされていることに注意してください。`RU_PER_SEC` に加えて、リソース制御は DBA 向けに `PRIORITY` や `BURSTABLE` などの他の設定可能な属性を提供します。これらすべてを活用する方法の詳細については、[リソース制御を使用した混合ワークロードの管理](https://labs.tidb.io/labs/demo_006) をご覧ください。次のモジュールに進んでください。

## モジュール 4: オンラインスキーマ変更

オンラインスキーマ変更は、中断のないアップタイムと継続的な可用性がシームレスなユーザーエクスペリエンスを確保するために重要な SaaS 環境では特に重要です。このモジュールでは、同じテーブルで同時に DML 挿入が発生している間に、ビジネス変更をサポートするために既存のテーブルに新しい列を追加する DDL 操作を実行します。DDL 操作が進行中の DML によってブロックされないことを観察し、DML を実行している Java アプリケーションが TiDB のオンライン DDL 機能を認識するように設計されていることを確認します。スキーマ変更を期待し、シームレスに処理するようにプログラムされています。

### 手順 1. sysbench によって作成されたスキーマを確認する

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

  ```bash
  mysql -h ${HOST_DB1_PRIVATE_IP} -P 4000 -u root
  ```
  ```output
  mysql> 
  ```

+ テーブル `sbtest1000.sbtest9` の定義を確認します。テーブルには 4 つの列が定義されており、`id` が主キーです。

  ```sql
  DELETE FROM sbtest1000.sbtest9;
  DESC sbtest1000.sbtest9;
  ```
  ```output
  +-------+-----------+------+------+---------+----------------+
  | Field | Type      | Null | Key  | Default | Extra          |
  +-------+-----------+------+------+---------+----------------+
  | id    | int       | NO   | PRI  | NULL    | auto_increment |
  | k     | int       | NO   | MUL  | 0       |                |
  | c     | char(120) | NO   |      |         |                |
  | pad   | char(60)  | NO   |      |         |                |
  +-------+-----------+------+------+---------+----------------+
  4 rows in set (0.00 sec)
  ```

### 手順 2. オンラインスキーマ変更の準備

+ メタデータロックをオフにして、DDL ステートメントが同じテーブルで進行中の DML トランザクションによってブロックされないようにします。

  ```sql
  SET GLOBAL tidb_enable_metadata_lock = OFF;
  SHOW VARIABLES LIKE "tidb_enable_metadata_lock";
  EXIT
  ```

+ 提供されたミニ Java プログラムを確認します。これは単純な挿入操作を無限に実行します。そして最も重要なことは、エラー `8028` を期待し、トランザクションを再試行できることです。つまり、TiDB のオンラインスキーマ変更動作と互換性があります。エラーコード `8028`（メッセージ「Information schema is changed」）は、プログラムがトランザクションをコミットしようとしたときに、トランザクション開始時とは異なるスキーマ定義を検出した場合に発生します。

  ```bash
  cat InsertJob.java | egrep -A1 -B1 8028 
  ```
  ```output
          } catch (SQLException e) {
              if (e.getErrorCode() == 8028) {
                  System.out.println("Schema mutation encountered, retry ...");
  ```

### 手順 3. 挿入ワークロードを開始する

+ [ターミナル 1](ssh://monitor1-ssh#1) で、以下のコマンドを実行して挿入ワークロードを開始します。これは停止しません。実行し続けてください。次のステップに進んでください。

  ```bash
  rm -f InsertJob.class
  javac -cp .:mysql-connector-java-5.1.36-bin.jar InsertJob.java
  sleep 1
  java -cp .:mysql-connector-java-5.1.36-bin.jar InsertJob ${HOST_DB1_PRIVATE_IP} 4000 t1000
  ```
  ```output
  Inserting 0
  Inserting 1
  Inserting 2
  Inserting 3
  Inserting 4
  Inserting 5
  Inserting 6
  Inserting 7
  ...
  ```

### 手順 4. オンラインスキーマ変更を実行する

+ 特定のビジネス要件に基づいて、テーブル `sbtest1000.sbtest9` に新しい列 `ed VARCHAR(10) DEFAULT 'N/A'` を追加することを決定します。

+ [ターミナル 2](ssh://monitor1-ssh#2) に切り替えて、TiDB サーバーインスタンスに接続します。

  ```bash
  mysql -h ${HOST_DB1_PRIVATE_IP} -P 4000 -u root
  ```
  ```output
  mysql> 
  ```

+ テーブルを 2 回確認し、挿入ワークロードが動作していて行数が増加していることを確認します。

  ```sql
  SELECT COUNT(1) FROM sbtest1000.sbtest9;
  SELECT SLEEP(2);
  SELECT COUNT(1) FROM sbtest1000.sbtest9;
  ```
  ```output
  +----------+
  | COUNT(1) |
  +----------+
  |       14 |
  +----------+
  1 row in set (0.00 sec)

  +----------+
  | SLEEP(2) |
  +----------+
  |        0 |
  +----------+
  1 row in set (2.00 sec)

  +----------+
  | COUNT(1) |
  +----------+
  |       18 |
  +----------+
  1 row in set (0.00 sec)
  ```

+ `sbtest1000.sbtest9` に新しい列を追加する DDL を実行します。

  ```sql
  ALTER TABLE sbtest1000.sbtest9 ADD COLUMN (ed VARCHAR(10) DEFAULT 'N/A');
  ```
  ```output
  Query OK, 0 rows affected (0.34 sec)
  ```

+ [ターミナル 1](ssh://monitor1-ssh#1) に戻って出力を観察します。`Schema mutation encountered, retry ...` という出力が期待され、ワークロードが中断されないことを確認します。

  ```output
  ...
  Inserting 50
  Inserting 51
  Schema mutation encountered, retry ...
  Inserting 52
  Inserting 53
  Inserting 54
  Inserting 55
  Inserting 56
  ...
  ```

+ [ターミナル 2](ssh://monitor1-ssh#2) に戻って、行数がまだ増加していることを確認します。

  ```sql
  SELECT COUNT(1) FROM sbtest1000.sbtest9;
  SELECT SLEEP(2);
  SELECT COUNT(1) FROM sbtest1000.sbtest9;
  ```
  ```output
  +----------+
  | COUNT(1) |
  +----------+
  |       71 |
  +----------+
  1 row in set (0.01 sec)

  +----------+
  | SLEEP(2) |
  +----------+
  |        0 |
  +----------+
  1 row in set (2.00 sec)

  +----------+
  | COUNT(1) |
  +----------+
  |       74 |
  +----------+
  1 row in set (0.00 sec)
  ```

+ テーブル定義が変更されたことを確認します。列 `ed varchar(10)` が新しい列であることに注意してください。

  ```sql
  DESC sbtest1000.sbtest9;
  ```
  ```output
  +-------+-------------+------+------+---------+----------------+
  | Field | Type        | Null | Key  | Default | Extra          |
  +-------+-------------+------+------+---------+----------------+
  | id    | int         | NO   | PRI  | NULL    | auto_increment |
  | k     | int         | NO   | MUL  | 0       |                |
  | c     | char(120)   | NO   |      |         |                |
  | pad   | char(60)    | NO   |      |         |                |
  | ed    | varchar(10) | YES  |      | N/A     |                |
  +-------+-------------+------+------+---------+----------------+
  5 rows in set (0.00 sec)
  ```

+ ここまで、TiDB のオンライン DDL 機能を確認しました。これにより、操作を停止することなくスキーマ変更が可能になります。[ターミナル 1](ssh://monitor1-ssh#1) に移動し、<kbd>CTRL-C</kbd> を押してください。その後、両方のターミナルを終了してください。

## 結論

TiDB は SaaS アプリケーションを直感的にサポートします。TiDB は多くの SaaS 企業に広く歓迎されています。

TiDB では、数千、さらには数百万のスキーマとテーブルを迅速に作成でき、パフォーマンスに影響を与えません。メタデータの作成とシードデータの投入は TiDB で非常に高速です。TiDB の SQL プラン管理（SPM）を使用して、複数のスキーマで安定した最適なクエリプランが常に使用されることを保証できます。TiDB のリソース制御機能はリソース使用量を分離できるため、常により高い優先度を与えることでプレミアム顧客をより良くサポートできます。もう「うるさい隣人」はいません。最後に、TiDB はオンラインスキーマ変更もサポートします。たとえば、DDL 操作は進行中の DML によってブロックされません。したがって、SaaS ビジネスは常にオンラインです。