返回博客

ThinkPHP Migration 跑到远端库时 Unknown database 的真相

某天需要给一张放在远程 MySQL 的业务表追加唯一索引,迁移文件写完,php think migrate:run 一跑:

PDOException: SQLSTATE[42000]: Syntax error or access violation:
1049 Unknown database 'remote_biz'

看着这行报错我愣了一下——config/database.php 里明明配了这个连接,为什么 Migration 说不认识?

现象

项目里有两个 MySQL 连接:默认连接指向本机 127.0.0.1,另一个命名连接 remote_biz 指向远端服务器 x.x.x.x。迁移文件用了跨库语法:

$this->execute("ALTER TABLE `remote_biz`.`orders` ADD UNIQUE INDEX `uk_no` (`order_no`)");

报错指向 1049 Unknown database 'remote_biz',但连接配置就在那里,没有任何拼写错误。

根因

ThinkPHP 8.x 的 Migration 底层是 Phinx。Phinx 有自己独立的数据库适配器,它只认 config/database.php 里的默认连接default 键指向的那一条)。

$this->execute()$this->table()$this->query() 这些 Phinx 方法全部走这个默认适配器,完全绕过了 ThinkPHP 的多连接路由机制

当你在 SQL 里写 remote_biz.orders,Phinx 会把这条 SQL 原样发给本地 MySQL 去执行。本地 MySQL 上根本没有 remote_biz 这个数据库——它在另一台服务器上——于是报 1049 Unknown database

坑:Phinx 的 $this->execute() 不走 ThinkPHP 的命名连接

remote_biz 是 ThinkPHP 的连接别名,不是本地 MySQL 实例里的数据库名。跨服务器的跨库语法 db_name.table_name 只在同一 MySQL 实例内有效,跨主机完全无法工作。Phinx 适配器与 ThinkPHP Db 门面是两套独立的连接池,互不感知。

正解

在迁移文件里直接用 think\facade\Db::connect() 拿到命名连接,再在这个连接上执行 SQL:

<?php
use think\migration\Migrator;
use think\facade\Db;
 
class AddUniqueIndexToOrders extends Migrator
{
    public function up(): void
    {
        // 先清理存量重复数据,否则加唯一索引会失败
        $duplicates = Db::connect('remote_biz')->query(
            "SELECT order_no, COUNT(*) as cnt
             FROM orders
             GROUP BY order_no
             HAVING cnt > 1"
        );
 
        foreach ($duplicates as $dup) {
            Db::connect('remote_biz')->execute(
                "DELETE FROM orders
                 WHERE order_no = ? AND id NOT IN
                   (SELECT id FROM (SELECT MAX(id) AS id FROM orders WHERE order_no = ?) t)",
                [$dup['order_no'], $dup['order_no']]
            );
        }
 
        // 在远端连接上执行 DDL,不加库名前缀
        Db::connect('remote_biz')->execute(
            "ALTER TABLE `orders` ADD UNIQUE INDEX `uk_order_no` (`order_no`)"
        );
    }
 
    public function down(): void
    {
        Db::connect('remote_biz')->execute(
            "ALTER TABLE `orders` DROP INDEX `uk_order_no`"
        );
    }
}

关键点:SQL 里不再写 remote_biz.orders,只写表名 orders,因为连接本身已经路由到正确的服务器和数据库,不需要再加库名前缀。

正解:用 Db::connect() 代替 $this->execute()

非默认连接的所有 DDL/DML 都走 Db::connect('connection_name')->execute()。Migration 的元数据(哪些迁移跑过)仍然写入默认库的 migrations 表,回滚追踪不受影响。$this->table() 同样受限,只要目标不是默认连接,一律换成 Db::connect()

验证

php think migrate:run
# == 20260315120000 AddUniqueIndexToOrders: migrated (took 0.18s)

连到远端 MySQL 确认:

SHOW INDEX FROM orders WHERE Key_name = 'uk_order_no';
-- 能查到说明索引已正确建在远端库上

一句话外卖

Phinx 的所有 $this->* 方法只走默认连接,非默认连接的 ThinkPHP Migration 必须改用 Db::connect('name') 手动路由。