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

TiDB はオンライン DDL を介してオンラインスキーマ変更を実現できます。これにより、ユーザーはアプリケーションを停止せずに、カラムの追加や削除、カラムプロパティの変更、インデックスの追加や削除、テーブルの名前変更などのテーブル定義の変更をダウンタイムなしでオンラインで実行できます。原理の詳細については、[TiDB における DDL 実行のベストプラクティス](https://docs.pingcap.com/tidb/stable/ddl-introduction)を参照してください。

このモジュールでは、アプリケーションの実行中に `SHOP`.`TRANSACTIONS` テーブルに `Discount` カラムを追加します。

また、このモジュール完了後に [Introduction to TiDB Cloud Starter](https://labs.tidb.io/labs/demo_203) の「Module 5: Evolving the Schema with Non-Blocking Online DDL」でオンライン DDL の基礎を復習することもできます。

### 手順 1. 「Transactions List」ページとデータベースのテーブルを確認する

+ [Transaction List](${LAB:HTTPS_REVERSE_PROXY_URL}?next=${LAB:"/vscode/proxy/8000/transactions"|urlencode}) で「Transactions list」ページにアクセスします。このラボでは、テーブルに `Discount` カラムを追加することを目指します。

![204_5_1_1.png 85%](https://lab-static.pingcap.com/images/2026/1/22/9e0c98ea3fd1a12d505fd8f2c4aae194176e2136.png)

+ VS Code で、**新しいターミナル**を開きます（以降「VS Code ターミナル 2」と呼びます）。

![204_5_1_2.png 85%](https://lab-static.pingcap.com/images/2026/1/22/d3c60da79b95bd149d9bde24611fe3996e42d374.png)

+ VS Code ターミナル 2 で、`php artisan model:show Transaction` コマンドを実行して、Model `APP/Model/Transaction.php` に対応するテーブル `SHOP`.`TRANSACTIONS` の定義を確認します。

  ```bash
  php artisan model:show Transaction
  ```

  ```output
  App\Models\Transaction .....................................................
  Database ............................................................. mysql
  Table ......................................................... transactions

  Attributes ..................................................... type / cast
  id unique, fillable .............................. bigint(20) unsigned / int
  user_id fillable ....................................... bigint(20) unsigned
  product_id fillable .................................... bigint(20) unsigned
  price fillable ............................................... decimal(10,2)
  buy_count fillable ................................................. int(11)
  total_price fillable ......................................... decimal(18,2)
  created_at nullable ................................... timestamp / datetime
  updated_at nullable ................................... timestamp / datetime
  pay_type fillable ............................................... bigint(20)

  Relations ..................................................................
  Product HasOne ...................................... App\Models\Product
  User HasOne ................................................ App\Models\User
  PayType HasOne .......................................... App\Models\PayType

  Events .....................................................................

  Observers ..................................................................
  ```

+ または、VS Code ターミナル 2 で `php artisan db:table transactions` コマンドを使用して、テーブル `SHOP`.`TRANSACTIONS` の定義を確認し、`Discount` カラムが存在しないことを確認します。

  ```bash
  php artisan db:table transactions
  ```

  ```output
  shop.transactions ..........................................................
  Columns .................................................................. 9
  Size ............................................................... 2.02 KB
  Engine .............................................................. InnoDB
  Collation ............................................... utf8mb4_unicode_ci

  Column ................................................................ Type
  id bigint .............................................. bigint(20) unsigned
  user_id bigint ......................................... bigint(20) unsigned
  product_id bigint ...................................... bigint(20) unsigned
  price decimal ................................................ decimal(10,2)
  buy_count int ...................................................... int(11)
  total_price decimal .......................................... decimal(18,2)
  created_at timestamp, nullable ................................... timestamp
  updated_at timestamp, nullable ................................... timestamp
  pay_type bigint ............................................... 1 bigint(20)

  Index ......................................................................
  primary id .................................................. btree, primary
  user_transaction_idx user_id ......................................... btree
  product_transaction_idx product_id ................................... btree
  ```

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

+ VS Code ターミナル 2 で、MySQL クライアントを使用して TiDB Cloud に接続します。

   ```bash
   mysql --comments -u ${LAB:SERVERLESS_CLUSTER_USERNAME} \
   -h ${LAB:SERVERLESS_CLUSTER_HOST} -P ${LAB:SERVERLESS_CLUSTER_PORT} \
   --ssl \
   --ssl-ca=/etc/ssl/certs/ca-certificates.crt \
   --ssl-verify-server-cert \
   -p${LAB:SERVERLESS_CLUSTER_PASSWORD} \
   ${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}
   ```

   ```output
   Your MySQL connection id is 61317775
   Server version: 8.0.11-TiDB-v7.5.2-serverless TiDB Server (Apache License 2.0) Community Edition, MySQL 8.0 compatible

   Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

   Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

   MySQL [shop]>
   ```

+ VS Code ターミナル 2 で、システム変数 `tidb_enable_metadata_lock` の値を確認するコマンドを実行します。デフォルト値は `ON` です。

  ```sql
  SHOW GLOBAL VARIABLES LIKE 'tidb_enable_metadata_lock';
  ```

  ```output
  MySQL [shop]> SHOW GLOBAL VARIABLES LIKE 'tidb_enable_metadata_lock';
  +---------------------------+-------+
  | Variable_name             | Value |
  +---------------------------+-------+
  | tidb_enable_metadata_lock | ON    |
  +---------------------------+-------+
  1 row in set (0.011 sec)
  ```

+ システム変数 `tidb_enable_metadata_lock` の値を `OFF` に変更します。VS Code が「Are you sure you want to paste 2 lines of text into the terminal?」というウィンドウをポップアップ表示したら、「Paste」をクリックしてください。

   ![204_5_2_1.png 85%](https://lab-static.pingcap.com/images/2026/2/6/f59a9fb8fb4b13e9f37efd228ef37c04b1da3a1f.png)

  ```sql
  SET GLOBAL tidb_enable_metadata_lock = OFF;
  SHOW GLOBAL VARIABLES LIKE 'tidb_enable_metadata_lock';
  ```

  ```output
  MySQL [shop]> SET GLOBAL tidb_enable_metadata_lock = OFF;
  Query OK, 0 rows affected (0.016 sec)

  MySQL [shop]> SHOW GLOBAL VARIABLES LIKE 'tidb_enable_metadata_lock';
  +---------------------------+-------+
  | Variable_name             | Value |
  +---------------------------+-------+
  | tidb_enable_metadata_lock | OFF   |
  +---------------------------+-------+
  1 row in set (0.011 sec)
  ```

+ 注: システム変数 `tidb_enable_metadata_lock` が `ON`（デフォルト値）の場合、すべての DDL コマンドは関連テーブルの DML ステートメントの完了を待つ必要があります。この待機期間中、DDL コマンドはブロックされます。これは MySQL の動作と一致しています。
システム変数 `tidb_enable_metadata_lock` が `OFF` の場合のみ、DDL コマンドの実行は DML コマンドの影響を受けません。
詳細については、[Introduction to TiDB Cloud Starter](/labs/demo_203) の「Module 5: Evolving the Schema with Non-Blocking Online DDL」を参照してください。

+ VS Code ターミナル 2 で、MySQL クライアントを終了します。

   ```sql
   EXIT
   ```

   ```output
   MySQL [shop]> EXIT
   Bye
   root@0c2c53eace8f:~/Mshop#
   ```

+ Controller `app/Http/Controllers/ProductsController.php` の関数 `public function buy(Request $request, Product $product)` を変更します。この関数は購入トランザクション機能を実装しており、スキーマ変更プロセス中に繰り返し実行される可能性があります。変数 `$OnlineSchemaChange` の初期値を `false` から `true` に変更し、ファイルを保存してください。この変更は即座に反映されます。

  ```php
  /**
  * Process the products purchase.
  */
  public function buy(Request $request, Product $product)
  {
    try {
        //Validate
        $validated = $request->validate(rules: [
            'buy_count' => 'required|integer|min:1',
            'pay_with' => 'required|integer|min:1',
        ]);
    } catch(ValidationException $e) {

        return redirect('/products/' . $product->id)
            ->withErrors($validated)
            ->withInput();
    }

    /**
    * If $OnlineSchemaChange is true, execute function $this->executeTransactionWithRetry(...) to prevent from the
    * error 'Error 8028: Information schema is changed during the execution of the statement' by retry for purchase transaction.
    * If $OnlineSchemaChange is true, exectue function $this->executeTransaction(...) for purchase transaction without retry.
    */
    $OnlineSchemaChange = true;

    if($OnlineSchemaChange) {

        //Execute the logic of purchase transaction.
        return $this->executeTransactionWithRetry($request, $product, $validated);
    } else {
        //Use recursive functions to handle retry logic of purchase transaction.
        return $this->executeTransaction($request, $product, $validated);

    }
  }
  ```

+ 上記の変更の理由は、`SHOP`.`TRANSACTIONS` テーブルでオンライン DDL が実行を開始すると、DDL コマンドの前に開始され現在 `SHOP`.`TRANSACTIONS` で実行中のトランザクションが、テーブル構造の変更により失敗する可能性があるためです（ERROR 8028: Information schema is changed）。そのため、アプリケーションに別の関数 `private function executeTransactionWithRetry($request, $product, $validated, $retryCount = 0)` を導入し、リトライによってこの問題を解決し、すべてのトランザクションが正常に実行されるようにしています。詳細については、[Introduction to TiDB Cloud Starter](https://labs.tidb.io/labs/demo_203) の「Module 5: Evolving the Schema with Non-Blocking Online DDL」を参照してください。

### 手順 3. アプリケーションから継続的な変更を実行する

- [Book Management](${LAB:HTTPS_REVERSE_PROXY_URL}?next=${LAB:"/vscode/proxy/8000/products/manage"|urlencode}) をクリックして管理ページにアクセスし、`Start auto change` ボタンをクリックします。これ以降、ページは自動的にランダムにトランザクションと在庫補充を実行します。観察しやすくするため、書籍名 `Waterfalls` のみが更新されます。

![start-auto-change.jpg 85%](https://lab-static.pingcap.com/images/2026/2/9/e3dd6c1002045ae88ebf7280a575de63a34aa830.jpg)

> [!TIP]
> すでにブラウザで [Book Management](${LAB:HTTPS_REVERSE_PROXY_URL}?next=${LAB:"/vscode/proxy/8000/products/manage"|urlencode}) ページを開いている場合は、`Start auto change` ボタンをクリックする前にウェブページを更新してください。

- ページを開いたまま、次の手順に進んでください。

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

+ VS Code ターミナル 2 で、`php artisan make:migration…` コマンドを使用して、`SHOP`.`TRANSACTIONS` テーブルに `Discount` カラムを追加するためのマイグレーションファイルを生成します。

  ```bash
  php artisan make:migration add_discount_to_transactions_table --table=transactions
  ```

  ```output
  INFO  Migration [database/migrations/2026_01_22_171252_add_discount_to_transactions_table.php] created successfully.
  ```

+ VS Code で、`/database/migrations/` 内に新しく生成されたコードファイル `2026_01_22_171252_add_discount_to_transactions_table.php` を見つけます。ファイル名はマニュアルの例とは異なる場合があります。形式は `xxxx_xx_xx_xxxxxx_add_discount_to_transactions_table.php` です。

![205_5_3_1.png 85%](https://lab-static.pingcap.com/images/2026/1/22/c95ec6163c9498ad30ac3f16926650d4130b641d.png)

+ ファイル `/database/migrations/2026_01_22_171252_add_discount_to_transactions_table.php` を更新して、`Discount` カラムの定義とロールバック操作を追加し、ファイルを保存します。ファイル名はマニュアルの例とは異なる場合があります。形式は `xxxx_xx_xx_xxxxxx_add_discount_to_transactions_table.php` です。

  ```php
  <?php

  use Illuminate\Database\Migrations\Migration;
  use Illuminate\Database\Schema\Blueprint;
  use Illuminate\Support\Facades\Schema;

  return new class extends Migration
  {
    /**
      * Run the migrations.
      */
    public function up(): void
    {
        Schema::table('transactions', function (Blueprint $table) {
            //
            $table->decimal('discount', 5,2)->default(0.0);
        });
    }

    /**
      * Reverse the migrations.
      */
    public function down(): void
    {
        Schema::table('transactions', function (Blueprint $table) {
            //
            $table->dropColumn('discount');
        });
    }
  };
  ```

+ ターミナルに戻り、`php artisan migration` コマンドを使用して `SHOP`.`TRANSACTIONS` テーブルのオンラインスキーマ変更を実行します。実行中のトランザクションはスキーマ変更をブロックしません。

  ```bash
  php artisan migrate
  ```

  ```output
  INFO  Running migrations.

  2026_01_22_171252_add_discount_to_transactions_table ................................ 845.16ms DONE
  ```

+ [Book Management](${LAB:HTTPS_REVERSE_PROXY_URL}?next=${LAB:"/vscode/proxy/8000/products/manage"|urlencode}) ページを確認してください。実行中のトランザクションはスキーマ変更によりブロックされていません。

+ ターミナルで引き続き `php artisan db:table transactions` コマンドを使用して、`SHOP`.`TRANSACTIONS` テーブルの定義を確認します。`Discount` カラムが追加されており、プロセス全体を通してテーブルの変更によるトランザクションの失敗がなかったことが確認できます。

  ```bash
  php artisan db:table transactions
  ```

  ```output
  shop.transactions ..........................................................
  Columns ................................................................. 10
  Size ............................................................... 2.09 KB
  Engine .............................................................. InnoDB
  Collation ............................................... utf8mb4_unicode_ci

  Column ................................................................ Type
  id bigint .............................................. bigint(20) unsigned
  user_id bigint ......................................... bigint(20) unsigned
  product_id bigint ...................................... bigint(20) unsigned
  price decimal ................................................ decimal(10,2)
  buy_count int ...................................................... int(11)
  total_price decimal .......................................... decimal(18,2)
  created_at timestamp, nullable ................................... timestamp
  updated_at timestamp, nullable ................................... timestamp
  pay_type bigint ............................................... 1 bigint(20)
  discount decimal ................................................ 0 decimal(5,2)

  Index ......................................................................
  primary id .................................................. btree, primary
  user_transaction_idx user_id ......................................... btree
  product_transaction_idx product_id ................................... btree
  ```

### 手順 5. ビューを変更して新しいトランザクションリストページを確認する

+ VS Code で、Controller `APP/Http/Controllers/TransactionController.php` の関数 `public function index()` を変更します。この関数は「Transactions list」機能を実装しています。変数 `$hasDiscount` の初期値を `false` から `true` に変更し、ファイルを保存してください。

  ```php
  /**
  * Display a listing of the resource.
  */
  public function index()
  {
  //
  // Rows per page.
  $row_per_page = 10;

  // Get the transactions of this page.
  $TransactionPaginate = Transaction::OrderBy('created_at', 'desc')
  ->OrderBy('user_id','desc')
  ->with('Product')
  ->with('User')
  ->with('PayType')
  ->paginate($row_per_page);

  // Set the title and data for view.
  $binding = [
      'title' => __('shop.transaction.Transactions-list'),
      'TransactionPaginate'=> $TransactionPaginate,
  ];

  /**
  * If $hasDiscount is true, it will use the view 'Transactions.listTransactionSave.blade.php' with 'Discount' column in the its table to list the transactions.
  * If $hasDiscount is false, it will use the view 'Transactions.listTransaction.blade.php' without 'Discount' column in the its table to list the transactions.
  */
  $hasDiscount = true;
  if($hasDiscount) {
      // Sent the Transactions data to view with the column 'Discount'.
      return view('transactions.listTransactionSave', $binding);
  }
  else {
      // Sent the Transactions data to view without the column 'Discount'.
      return view('transactions.listTransaction', $binding);
  }
  ```

+ 変更後、トランザクションリストは View `resource/views/Transactions.listTransactionSave.blade.php` によって描画されます。[Transaction List](${LAB:HTTPS_REVERSE_PROXY_URL}?next=${LAB:"/vscode/proxy/8000/transactions"|urlencode}) で「Transactions list」ページに再度アクセスすると、`Discount` カラムが表示されていることが確認できます。

![204_5_4_1.png 85%](https://lab-static.pingcap.com/images/2026/1/22/c09c1264220e704401c4f1d4af194610c3b7a3d2.png)

+ これでこのモジュールは完了です。
