## 模块 3: 将 Laravel 应用程序从 MySQL 迁移到 TiDB Cloud 上

在本模块中，参与者将完成 Laravel 应用从 MySQL 数据库到 TiDB Starter 数据库的迁移。

### 步骤 1. 更新 `.env` 文件中的 TiDB Cloud Starter 连接参数

在启动实验时你已经授权了 TiDB Cloud Starter，其连接参数已保存在 `.env` 文件中。现在你需要更新 `.env` 文件以使用 TiDB Cloud Starter 的连接参数。

+ 对 `.env` 文件进行以下更改：
  - 删除本地 MySQL 连接参数
  - 取消注释 TiDB Cloud 集群连接参数
  - 保存文件

  ```properties
  # tidb cloud cluster
  DB_PORT=${LAB:SERVERLESS_CLUSTER_PORT}
  DB_HOST=${LAB:SERVERLESS_CLUSTER_HOST}
  DB_USERNAME=${LAB:SERVERLESS_CLUSTER_USERNAME}
  DB_PASSWORD=${LAB:SERVERLESS_CLUSTER_PASSWORD}
  DB_DATABASE=${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}
  MYSQL_ATTR_SSL_CA=/etc/ssl/certs/ca-certificates.crt
  ...
  ```

### 步骤 2. 在 TiDB Cloud 中准备表定义

+ 在 VS Code 终端中，使用 `php artisan migrate` 命令创建 TiDB Cloud 中所需的表。

   ```bash
   php artisan migrate
   ```

   ```output
   INFO  Preparing database.

   Creating migration table ........................................................................................................... 843.87ms DONE

   INFO  Running migrations.

   0001_01_01_000000_create_users_table .................................................................................................... 15s DONE
   0001_01_01_000001_create_cache_table ..................................................................................................... 1s DONE
   0001_01_01_000002_create_jobs_table ...................................................................................................... 8s DONE
   2025_12_20_112907_create_transactions_table .............................................................................................. 7s DONE
   2025_12_20_120002_create_products_table .................................................................................................. 3s DONE
   2026_01_03_040247_create_product_type_table ........................................................................................ 547.59ms DONE
   2026_01_03_040331_create_pay_types_table ........................................................................................... 651.96ms DONE
   ```

### 步骤 3. 将表的 `AUTO_INCREMENT` 属性修改为 `AUTO_RANDOM`，以防止出现数据热点

+ 在 VS Code 终端中，通过 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 [${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}]>
   ```

+ 在 VS Code 的终端中，通过 MySQL 客户端查看表 `shop.products` 和表 `shop.transactions` 的定义。VS Code 会弹出窗口询问 “Are you sure you want to paste 2 lines of text into the terminal?”，请点击 “Paste”。

   ![204_3_3_1.png 85%](https://lab-static.pingcap.com/images/2026/2/4/e5c082347f448be9f8db4c13b4952a204feb5346.png)

   ```sql
   SHOW CREATE TABLE products\G
   SHOW CREATE TABLE transactions\G
   ```

   ```output
   MySQL [${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}]> SHOW CREATE TABLE products\G
   *************************** 1. row ***************************
         Table: products
   Create Table: CREATE TABLE `products` (
   `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
   `status` varchar(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'C',
   `name` varchar(80) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
   `introduction` text COLLATE utf8mb4_unicode_ci NOT NULL,
   `photo` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
   `price` decimal(10,2) NOT NULL DEFAULT '0',
   `remain_count` int(11) NOT NULL DEFAULT '0',
   `created_at` timestamp NULL DEFAULT NULL,
   `updated_at` timestamp NULL DEFAULT NULL,
   `product_type` int(11) NOT NULL DEFAULT '1',
   PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,
   KEY `idx_name_product` (`name`)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
   1 row in set (0.163 sec)

   MySQL [${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}]>
   MySQL [${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}]> SHOW CREATE TABLE transactions\G
   *************************** 1. row ***************************
         Table: transactions
   Create Table: CREATE TABLE `transactions` (
   `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
   `user_id` bigint(20) unsigned NOT NULL,
   `product_id` bigint(20) unsigned NOT NULL,
   `price` decimal(10,2) NOT NULL,
   `buy_count` int(11) NOT NULL,
   `total_price` decimal(18,2) NOT NULL,
   `created_at` timestamp NULL DEFAULT NULL,
   `updated_at` timestamp NULL DEFAULT NULL,
   `pay_type` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1',
   PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,
   KEY `user_transaction_idx` (`user_id`),
   KEY `product_transaction_idx` (`product_id`)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
   1 row in set (0.163 sec)
   ```

> [!TIP]
>
> + 由于 TiDB Starter 为分布式数据库架构，建议将表 `shop.products` 和 `shop.transaction` 的主键 `AUTO_INCREMENT` 属性改为 `AUTO_RANDOM`,这样可以避免热点的产生。
> + 注意，这一步是可选步骤，如果不执行也不会影响迁移结果，只是可能在后期会出现热点问题。
> + 另外，如果选择进行修改，必须将 2 张表删除重建，而无法使用文档中的类似 `ALTER TABLE t MODIFY COLUMN id BIGINT AUTO_RANDOM(5);`方法，原因有 2 个：
> + 第一个原因是对于 `id` 列，如果 `BIGINT` 类型的所有 64 位均有效，则由于 PHP 语言有 `PHP_INT_MAX` 设置对于整数位的限制，所以可能会在后面应用程序中会出现整数位数超出范围的错误。
> + 第二个原因，如果采用 id 列为类似 AUTO_RANDOM(3, 32) 的不到 64 位设置，`ALTER TABLE t MODIFY COLUMN` 命令会认为整数范围比原来缩小，可能造成丢失数据，所以会产生报错。

+ 在 VS Code 的终端中，通过 MySQL 客户端重建表 `shop.products` 和表 `shop.transactions`，将表 `shop.products` 和 `shop.transaction` 的主键 `AUTO_INCREMENT` 属性改为 `AUTO_RANDOM`。VS Code 会弹出窗口询问 “Are you sure you want to paste 33 lines of text into the terminal?”，请点击 “Paste”。

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

   ```sql
   DROP TABLE `products`;

   CREATE TABLE `products` (
   `id` bigint(20) unsigned NOT NULL /*T![auto_rand] AUTO_RANDOM(3, 32) */,
   `status` varchar(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'C',
   `name` varchar(80) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
   `introduction` text COLLATE utf8mb4_unicode_ci NOT NULL,
   `photo` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
   `price` decimal(10,2) NOT NULL DEFAULT '0',
   `remain_count` int(11) NOT NULL DEFAULT '0',
   `created_at` timestamp NULL DEFAULT NULL,
   `updated_at` timestamp NULL DEFAULT NULL,
   `product_type` int(11) NOT NULL DEFAULT '1',
   PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,
   KEY `idx_name_product` (`name`)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

   DROP TABLE `transactions`;

   CREATE TABLE `transactions` (
   `id` bigint(20) unsigned NOT NULL /*T![auto_rand] AUTO_RANDOM(3, 32) */,
   `user_id` bigint(20) unsigned NOT NULL,
   `product_id` bigint(20) unsigned NOT NULL,
   `price` decimal(10,2) NOT NULL,
   `buy_count` int(11) NOT NULL,
   `total_price` decimal(18,2) NOT NULL,
   `created_at` timestamp NULL DEFAULT NULL,
   `updated_at` timestamp NULL DEFAULT NULL,
   `pay_type` bigint(20) unsigned NOT NULL DEFAULT 1,
   PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,
   KEY `user_transaction_idx` (`user_id`),
   KEY `product_transaction_idx` (`product_id`)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
   ```

   ```output
   MySQL [${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}]>
   MySQL [${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}]> CREATE TABLE `products` (
      -> `id` bigint(20) unsigned NOT NULL /*T![auto_rand] AUTO_RANDOM(3, 32) */,
      -> `status` varchar(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'C',
      -> `name` varchar(80) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
      -> `introduction` text COLLATE utf8mb4_unicode_ci NOT NULL,
      -> `photo` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
      -> `price` decimal(10,2) NOT NULL DEFAULT '0',
      -> `remain_count` int(11) NOT NULL DEFAULT '0',
      -> `created_at` timestamp NULL DEFAULT NULL,
      -> `updated_at` timestamp NULL DEFAULT NULL,
      -> `product_type` int(11) NOT NULL DEFAULT '1',
      -> PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,
      -> KEY `idx_name_product` (`name`)
      -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
   Query OK, 0 rows affected, 1 warning (0.407 sec)

   MySQL [${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}]>
   MySQL [${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}]> DROP TABLE `transactions`;
   Query OK, 0 rows affected (0.559 sec)

   MySQL [${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}]>
   MySQL [${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}]> CREATE TABLE `transactions` (
      -> `id` bigint(20) unsigned NOT NULL /*T![auto_rand] AUTO_RANDOM(3, 32) */,
      -> `user_id` bigint(20) unsigned NOT NULL,
      -> `product_id` bigint(20) unsigned NOT NULL,
      -> `price` decimal(10,2) NOT NULL,
      -> `buy_count` int(11) NOT NULL,
      -> `total_price` decimal(18,2) NOT NULL,
      -> `created_at` timestamp NULL DEFAULT NULL,
      -> `updated_at` timestamp NULL DEFAULT NULL,
      -> `pay_type` bigint(20) unsigned NOT NULL DEFAULT 1,
      -> PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,
      -> KEY `user_transaction_idx` (`user_id`),
      -> KEY `product_transaction_idx` (`product_id`)
      -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
   Query OK, 0 rows affected, 1 warning (0.384 sec)

   ```

+ 在 VS Code 的终端中，退出 MySQL 客户端准备进行数据迁移。

   ```sql
   EXIT
   ```

   ```output
   MySQL [${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}]> EXIT
   Bye
   root@0c2c53eace8f:~/Mshop#
   ```

### 步骤 4. 数据迁移

+ 在 VS Code 的终端中，使用工具 dumpling 从 MySQL 数据库中将 shop 下的所需表的数据导出为 SQL 格式的数据文件。

   ```bash
   tiup dumpling -u lab -plabpass -h mysql -P 3306 -B ${LAB:SERVERLESS_CLUSTER_DATABASE_NAME} \
   -T ${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}.users \
   -T ${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}.products \
   -T ${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}.transactions \
   -T ${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}.product_type \
   -T ${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}.pay_type \
   --filetype sql \
   -o ./dumpling_output
   ```

   ```output
   Checking updates for component dumpling... Timedout (after 2s)
   The component `dumpling` version  is not installed; downloading from repository.
   ...
   [2026/01/21 05:10:11.018 +00:00] [INFO] [dump.go:276] ["All the dumping transactions have started. Start to unlock tables"]
   [2026/01/21 05:10:11.048 +00:00] [INFO] [collector.go:264] ["backup success summary"] [total-ranges=11] [ranges-succeed=11] [ranges-failed=0] [total-take=29.166482ms] [total-kv-size=3.933kB] [average-speed=134.8kB/s] [total-rows=35]
   [2026/01/21 05:10:11.049 +00:00] [INFO] [main.go:82] ["dump data successfully, dumpling will exit now"]
   ```

+ 在 VS Code 的终端中，使用工具 TiDB lightning 将从 MySQL 数据库中导出的数据导入到 TiDB Cloud 中。

   ```bash
   cat > .lightning_config <<EOF
   [mydumper]
   data-source-dir = "/root/Mshop/dumpling_output"

   [tidb]
   host = "${LAB:SERVERLESS_CLUSTER_HOST}"
   port = ${LAB:SERVERLESS_CLUSTER_PORT}
   user = "${LAB:SERVERLESS_CLUSTER_USERNAME}"
   password = "${LAB:SERVERLESS_CLUSTER_PASSWORD}"
   tls = "cluster"

   [security]
   ca-path = "/etc/ssl/certs/ca-certificates.crt"
   EOF

   tiup tidb-lightning --backend=tidb --check-requirements=false --config .lightning_config
   ```

   ```output
   Starting component tidb-lightning: /root/.tiup/components/tidb-lightning/v8.5.5/tidb-lightning --backend=tidb --check-requirements=false --config /dev/fd/63
   Verbose debug logs will be written to /tmp/lightning.log.2026-01-18T05.20.02Z

   tidb lightning exit successfully
   ```

### 步骤 5. 启动基于 TiDB Cloud 的 Laravel 应用

+ 在 VS Code 的终端中，运行命令 `composer run dev` 来启动运行在 TiDB Cloud 上的 Laravel 应用。

  ```bash
  composer run dev
  ```

  ```output
   > Composer\Config::disableProcessTimeout
   > @php artisan serve --host=0.0.0.0

      INFO  Server running on [http://0.0.0.0:8000].  

   Press Ctrl+C to stop the server
  ```

+ 在 VS Code 中将会弹出一个窗口，提示“Your application running on port 8000 is available. See all forwarded ports”，请点击“Open in Browser”。

  ![204_1_port_8000.png 85%](https://lab-static.pingcap.com/images/2026/2/5/4f681e3a7458c7631f2187c53100aa127e5bcaf4.png)

+ 接下来进入下一个模块。
