Files
rxthinkcmf-wiki/RXThinkCMF_ThinkPhp6_EleVue/zh/tools/db-migrate.md
T
2026-09-29 09:49:04 +08:00

37 KiB
Raw Blame History

7.6 数据库迁移工具

:::tip 概述 db:migrate 是一个 CLI 命令行工具,支持在 MySQL / PostgreSQL / SQL Server / Oracle / SQLite 五种数据库之间任意组合迁移。完整迁移流程包含自动建库、建表结构(含索引、外键、注释、自增、collation)、分批迁移数据、重置自增序列、逐表行数校验 5 个步骤。数据迁移基于主键稳定排序,支持事务保护与自动回滚。目标库不存在时自动创建,支持含点号的数据库名。 :::

迁移流程

┌──────────────────────────────────────────────────────────────┐
│                   数据库迁移 5 步流程                         │
└──────────────────────────────────────────────────────────────┘

[1/5] 建目标库
│  目标库不存在时自动创建(所有驱动均支持)
│  自动从源库读取 collation 并应用(仅源与目标驱动相同时)
│  MySQL: CREATE DATABASE ... COLLATE | PgSQL: LC_COLLATE
│  SqlServer: CREATE DATABASE ... COLLATE | Oracle: CREATE USER
│  SQLite: 自动创建文件
▼
[2/5] 建表结构 + 索引 + 注释 + 外键
│  源库 getTableFields() → DbSchemaBuilder 生成 DDL → 目标库执行
│  自动迁移字段注释(MySQL: COMMENT 子句 | PgSQL/Oracle: COMMENT ON COLUMN | SqlServer: sp_addextendedproperty)
│  自动迁移表注释(MySQL: ALTER TABLE COMMENT | PgSQL/Oracle: COMMENT ON TABLE)
│  自动迁移非主键索引(UNIQUE INDEX / 普通 INDEX / 全文索引)
│  自动迁移外键约束(所有表创建后统一添加,避免引用顺序问题)
│  字段默认值完整保留(含空字符串 DEFAULT '',函数型默认值自动识别)
│  enum/set 类型自动迁移候选值(生成 CHECK 约束)
│  decimal/datetime 精度保留(如 decimal(10,2)、datetime(3))
│  排序规则(collation)自动迁移:表级 + 列级(MySQL/PostgreSQL)
│  Oracle 自增:自动创建序列+触发器
│  支持 --drop-target-first 先删后建
▼
[3/5] 逐表迁移数据(事务保护)
│  按 batch-size(默认 1000)分页读取,每页再拆为 100 行的 INSERT 语句
│  每个 batch-size 为一个独立事务,提交后即生效
│  当前批次失败时回滚,已提交的历史批次不受影响
│  基于主键 ORDER BY 稳定排序,避免分页漏行/重复
│  blob 字段使用 PDO::PARAM_LOB 绑定
│  SQL Server: 自动开关 IDENTITY_INSERT
▼
[4/5] 重置自增序列
│  PostgreSQL: SELECT setval 重置序列到 MAX(id)+1
│  SQL Server: DBCC CHECKIDENT 重置标识种子
│  Oracle: ALTER SEQUENCE 重置序列
│  MySQL / SQLite: 跳过(无需重置)
▼
[5/5] 逐表行数校验
   SELECT COUNT(*) 对比源和目标,输出 PASS/FAIL

命令格式

# =============================================================================
# 数据库迁移命令
# =============================================================================
# php think db:migrate → 调用 ThinkPHP 命令行的数据库迁移工具
# [选项]              → 通过 --key=value 格式传入源/目标数据库连接参数
# =============================================================================
php think db:migrate [选项]

必填选项

选项 说明
--src-type 源数据库驱动:mysql / pgsql / sqlsrv / oracle / sqlite
--src-database 源数据库名
--dst-type 目标数据库驱动:mysql / pgsql / sqlsrv / oracle / sqlite
--dst-database 目标数据库名

源连接选项

选项 默认值 说明
--src-host 127.0.0.1 源主机地址
--src-hostport 自动推断 源端口(mysql=3306, pgsql=5432, sqlsrv=1433, oracle=1521)
--src-username - 源用户名(SQLite 除外)
--src-password - 源密码(SQLite 除外)

目标连接选项

选项 默认值 说明
--dst-host 127.0.0.1 目标主机地址
--dst-hostport 自动推断 目标端口
--dst-username - 目标用户名(SQLite 除外)
--dst-password - 目标密码(SQLite 除外)

迁移选项

选项 默认值 说明
--drop-target-first 否 迁移前先删除目标库中的同名表
--batch-size 1000 每批读取的行数(影响内存占用与迁移速度)
--tables 全部 只迁移指定表(逗号分隔)

迁移命令签名

MySQL → PostgreSQL

# -----------------------------------------------------------------------------
# MySQL 迁移到 PostgreSQL
# -----------------------------------------------------------------------------
# --src-* → 源数据库连接参数(MySQL)
# --dst-* → 目标数据库连接参数(PostgreSQL)
# --drop-target-first → 迁移前先删除目标库中的同名表(避免表已存在报错)
php think db:migrate \
  --src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
  --src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
  --dst-type=pgsql --dst-host=127.0.0.1 --dst-hostport=5432 \
  --dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=postgres --dst-password=123456 \
  --drop-target-first

MySQL → SQL Server

# -----------------------------------------------------------------------------
# MySQL 迁移到 SQL Server
# -----------------------------------------------------------------------------
# --dst-type=sqlsrv → 目标驱动为 SQL Server
# --dst-hostport=1433 → SQL Server 默认端口
php think db:migrate \
  --src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
  --src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
  --dst-type=sqlsrv --dst-host=127.0.0.1 --dst-hostport=1433 \
  --dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=sa --dst-password=123456 \
  --drop-target-first

MySQL → SQLite

# -----------------------------------------------------------------------------
# MySQL 迁移到 SQLite
# -----------------------------------------------------------------------------
# --dst-type=sqlite → 目标驱动为 SQLite(无需用户名密码)
# --dst-database → SQLite 文件路径(文件不存在时自动创建)
php think db:migrate \
  --src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
  --src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
  --dst-type=sqlite --dst-database=runtime/rxthinkcmf.thinkphp6.elevue.db

MySQL → MySQL(同库复制)

# -----------------------------------------------------------------------------
# MySQL 同库复制(源和目标在同一 MySQL 实例,数据库名不同)
# -----------------------------------------------------------------------------
# 不带 --drop-target-first → 目标表已存在时跳过(不删除)
php think db:migrate \
  --src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
  --src-database=source_db --src-username=root --src-password=root \
  --dst-type=mysql --dst-host=127.0.0.1 --dst-hostport=3306 \
  --dst-database=target_db --dst-username=root --dst-password=root

PostgreSQL → MySQL

# -----------------------------------------------------------------------------
# PostgreSQL 迁移到 MySQL
# -----------------------------------------------------------------------------
php think db:migrate \
  --src-type=pgsql --src-host=127.0.0.1 --src-hostport=5432 \
  --src-database=rxthinkcmf.thinkphp6.elevue --src-username=postgres --src-password=123456 \
  --dst-type=mysql --dst-host=127.0.0.1 --dst-hostport=3306 \
  --dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=root --dst-password=root \
  --drop-target-first

SQL Server → MySQL

# -----------------------------------------------------------------------------
# SQL Server 迁移到 MySQL
# -----------------------------------------------------------------------------
php think db:migrate \
  --src-type=sqlsrv --src-host=127.0.0.1 --src-hostport=1433 \
  --src-database=rxthinkcmf.thinkphp6.elevue --src-username=sa --src-password=123456 \
  --dst-type=mysql --dst-host=127.0.0.1 --dst-hostport=3306 \
  --dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=root --dst-password=root \
  --drop-target-first

只迁移指定表

# -----------------------------------------------------------------------------
# 只迁移指定表(逗号分隔表名,不含前缀)
# -----------------------------------------------------------------------------
# --tables=think_user,think_role,think_menu → 只迁移这 3 张表
php think db:migrate \
  --src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
  --src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
  --dst-type=pgsql --dst-host=127.0.0.1 --dst-hostport=5432 \
  --dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=postgres --dst-password=123456 \
  --tables=think_user,think_role,think_menu \
  --drop-target-first

迁移输出日志

MySQL → PostgreSQL 完整日志

$ php think db:migrate \
    --src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
    --src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
    --dst-type=pgsql --dst-host=127.0.0.1 --dst-hostport=5432 \
    --dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=postgres --dst-password=123456 \
    --drop-target-first

==============================================================
[迁移开始] 源: mysql:rxthinkcmf.thinkphp6.elevue → 目标: pgsql:rxthinkcmf.thinkphp6.elevue
[迁移开始] 待迁移 25 张表
==============================================================
[·] [1/5] 建目标库...
  · 目标库 "rxthinkcmf.thinkphp6.elevue" 已就绪
[✓] [1/5] 建目标库 完成(耗时 0ms)
[·] [2/5] 建表结构(25 张表)...
[✓] [2/5] 建表结构 完成(耗时 440ms),25 张表,118 个索引,0 个外键
[·] [3/5] 逐表迁移数据...
  · [1/25] 正在迁移 think_article...
  [think_article] +13(累计 13)
  [✓ think_article] 共 13 行(耗时 10ms)
  · [2/25] 正在迁移 think_category...
  [think_category] +150(累计 150)
  [✓ think_category] 共 150 行(耗时 12ms)
  · [3/25] 正在迁移 think_city...
  [think_city] +1000(累计 1000)
  [think_city] +1000(累计 2000)
  [think_city] +1000(累计 3000)
  ...(大表分批输出进度,每批 1000 行)...
  [think_city] +562(累计 783562)
  [✓ think_city] 共 783562 行(耗时 10m10s)
  · [4/25] 正在迁移 think_config...
  [think_config] +1(累计 1)
  [✓ think_config] 共 1 行(耗时 11ms)
  ...(其余表类似)...
  · [25/25] 正在迁移 think_user_role...
  [think_user_role] +25(累计 25)
  [✓ think_user_role] 共 25 行(耗时 6ms)
[✓] [3/5] 逐表迁移数据 完成(耗时 10m10s),共 785215 行
[·] [4/5] 重置自增序列...
[✓] [4/5] 重置自增序列 完成(耗时 23ms)
[·] [5/5] 逐表行数校验...
  [think_article] source=13 target=13 PASS
  [think_category] source=150 target=150 PASS
  [think_city] source=783562 target=783562 PASS
  [think_config] source=1 target=1 PASS
  ...(其余表类似)...
  [think_user_role] source=25 target=25 PASS
[✓] [5/5] 逐表行数校验 完成(耗时 452ms),25 张表全部 PASS
==============================================================
[迁移完成] 成功:785215 行 / 25 张表 / 总耗时 10m11s
==============================================================

MySQL → SQL Server 日志摘要

$ php think db:migrate \
    --src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
    --src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
    --dst-type=sqlsrv --dst-host=127.0.0.1 --dst-hostport=1433 \
    --dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=sa --dst-password=123456 \
    --drop-target-first

==============================================================
[迁移开始] 源: mysql:rxthinkcmf.thinkphp6.elevue → 目标: sqlsrv:rxthinkcmf.thinkphp6.elevue
[迁移开始] 待迁移 25 张表
==============================================================
[·] [1/5] 建目标库...
  · 目标库 "rxthinkcmf.thinkphp6.elevue" 已就绪
[✓] [1/5] 建目标库 完成(耗时 0ms)
[·] [2/5] 建表结构(25 张表)...
[✓] [2/5] 建表结构 完成(耗时 1.5s),25 张表,118 个索引,0 个外键
[·] [3/5] 逐表迁移数据...
  ...(逐表迁移,SQL Server 每行耗时较 MySQL/SQLite 更长)...
[✓] [3/5] 逐表迁移数据 完成(耗时 11m48s),共 785231 行
[·] [4/5] 重置自增序列...
[✓] [4/5] 重置自增序列 完成(耗时 52ms)
[·] [5/5] 逐表行数校验...
  ...(25 张表全部 PASS)...
[✓] [5/5] 逐表行数校验 完成(耗时 681ms),25 张表全部 PASS
==============================================================
[迁移完成] 成功:785231 行 / 25 张表 / 总耗时 11m50s
==============================================================

MySQL → Oracle 日志摘要

$ php think db:migrate \
    --src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
    --src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
    --dst-type=oracle --dst-host=127.0.0.1 --dst-hostport=1521 \
    --dst-database=rxthinkcmf --dst-username=system --dst-password=123456 \
    --drop-target-first

==============================================================
[迁移开始] 源: mysql:rxthinkcmf.thinkphp6.elevue → 目标: oracle:rxthinkcmf.thinkphp6.elevue
[迁移开始] 待迁移 25 张表
==============================================================
[·] [1/5] 建目标库...
  · 目标库 "rxthinkcmf" 已就绪
[✓] [1/5] 建目标库 完成(耗时 0ms)
[·] [2/5] 建表结构(25 张表)...
  · 为 think_article 创建序列+触发器(自增列 id)
  · 为 think_city 创建序列+触发器(自增列 id)
  ...(其余含自增列的表类似)...
[✓] [2/5] 建表结构 完成(耗时 2.1s),25 张表,118 个索引,0 个外键
[·] [3/5] 逐表迁移数据...
  · [1/25] 正在迁移 think_article...
  [think_article] +13(累计 13)
  [✓ think_article] 共 13 行(耗时 45ms)
  ...(Oracle INSERT 耗时较 MySQL/SQLite 更长)...
[✓] [3/5] 逐表迁移数据 完成(耗时 15m30s),共 785215 行
[·] [4/5] 重置自增序列...
  · 重置序列: think_article_id_seq → 14
  · 重置序列: think_city_id_seq → 783563
  ...(其余序列类似)...
[✓] [4/5] 重置自增序列 完成(耗时 180ms)
[·] [5/5] 逐表行数校验...
  ...(25 张表全部 PASS)...
[✓] [5/5] 逐表行数校验 完成(耗时 820ms),25 张表全部 PASS
==============================================================
[迁移完成] 成功:785215 行 / 25 张表 / 总耗时 15m33s
==============================================================

:::tip Oracle 迁移要点

  • 建库:Oracle 通过 CREATE USER 创建用户,库名即用户名,需要 DBA 权限
  • 自增列:自动创建序列(_seq)和触发器(_trg),插入时自动填充
  • 序列重置:迁移完成后通过 ALTER SEQUENCE ... RESTART 重置到 MAX+1
  • 分页:使用 ROW_NUMBER() OVER (ORDER BY 主键) 保证稳定排序
  • 字段名:Oracle 默认大写,工具自动处理大小写转换 :::

MySQL → SQLite 日志摘要

$ php think db:migrate \
  --src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
  --src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
  --dst-type=sqlite --dst-database=runtime/rxthinkcmf.thinkphp6.elevue.db

==============================================================
[迁移开始] 源: mysql:rxthinkcmf.thinkphp6.elevue → 目标: sqlite:runtime/rxthinkcmf.thinkphp6.elevue.db
[迁移开始] 待迁移 25 张表
==============================================================
[·] [1/5] 建目标库...
  · 目标库 "runtime/rxthinkcmf.thinkphp6.elevue.db" 已就绪
[✓] [1/5] 建目标库 完成(耗时 0ms)
[·] [2/5] 建表结构(25 张表)...
[✓] [2/5] 建表结构 完成(耗时 1s),25 张表,118 个索引,0 个外键
[·] [3/5] 逐表迁移数据...
  ...(SQLite 写入速度最快)...
[✓] [3/5] 逐表迁移数据 完成(耗时 9m44s),共 785235 行
[·] [4/5] 重置自增序列...
  · 当前驱动 (sqlite) 无需重置序列,跳过
[✓] [4/5] 重置自增序列 完成(耗时 0ms)
[·] [5/5] 逐表行数校验...
  ...(25 张表全部 PASS)...
[✓] [5/5] 逐表行数校验 完成(耗时 427ms),25 张表全部 PASS
==============================================================
[迁移完成] 成功:785235 行 / 25 张表 / 总耗时 9m46s
==============================================================

只迁移指定表日志

$ php think db:migrate \
    --src-type=mysql --src-host=127.0.0.1 --src-hostport=3306 \
    --src-database=rxthinkcmf.thinkphp6.elevue --src-username=root --src-password=root \
    --dst-type=pgsql --dst-host=127.0.0.1 --dst-hostport=5432 \
    --dst-database=rxthinkcmf.thinkphp6.elevue --dst-username=postgres --dst-password=123456 \
    --tables=think_user,think_role,think_menu \
    --drop-target-first

==============================================================
[迁移开始] 源: mysql:rxthinkcmf.thinkphp6.elevue → 目标: pgsql:rxthinkcmf.thinkphp6.elevue
[迁移开始] 待迁移 3 张表
==============================================================
[·] [1/5] 建目标库...
  · 目标库 "rxthinkcmf.thinkphp6.elevue" 已就绪
[✓] [1/5] 建目标库 完成(耗时 0ms)
[·] [2/5] 建表结构(3 张表)...
  · 已删除目标表: think_user
  · 已删除目标表: think_role
  · 已删除目标表: think_menu
[✓] [2/5] 建表结构 完成(耗时 85ms),3 张表,5 个索引,0 个外键
[·] [3/5] 逐表迁移数据...
  · [1/3] 正在迁移 think_user...
  [think_user] +22(累计 22)
  [✓ think_user] 共 22 行(耗时 9ms)
  · [2/3] 正在迁移 think_role...
  [think_role] +24(累计 24)
  [✓ think_role] 共 24 行(耗时 5ms)
  · [3/3] 正在迁移 think_menu...
  [think_menu] +228(累计 228)
  [✓ think_menu] 共 228 行(耗时 12ms)
[✓] [3/5] 逐表迁移数据 完成(耗时 30ms),共 274 行
[·] [4/5] 重置自增序列...
[✓] [4/5] 重置自增序列 完成(耗时 25ms)
[·] [5/5] 逐表行数校验...
  [think_user] source=22 target=22 PASS
  [think_role] source=24 target=24 PASS
  [think_menu] source=228 target=228 PASS
[✓] [5/5] 逐表行数校验 完成(耗时 60ms),3 张表全部 PASS
==============================================================
[迁移完成] 成功:274 行 / 3 张表 / 总耗时 250ms
==============================================================

类型映射

源类型 MySQL PostgreSQL SqlServer Oracle SQLite
bigint bigint bigint bigint NUMBER(19) INTEGER
int int integer int NUMBER(10) INTEGER
smallint smallint smallint smallint NUMBER(5) INTEGER
tinyint tinyint smallint tinyint NUMBER(3) INTEGER
decimal decimal numeric decimal NUMBER REAL
float float real float BINARY_FLOAT REAL
double double double precision float BINARY_DOUBLE REAL
varchar varchar varchar nvarchar VARCHAR2 TEXT
char char char nchar CHAR TEXT
text text text ntext CLOB TEXT
blob blob bytea varbinary BLOB BLOB
datetime datetime timestamp datetime TIMESTAMP TEXT
timestamp timestamp timestamp datetime TIMESTAMP TEXT
date date date date DATE TEXT
boolean tinyint(1) boolean bit NUMBER(1) INTEGER
json json jsonb ntext CLOB TEXT

:::tip 精度保留

  • decimal(10,2) → PostgreSQL numeric(10,2)(精度不丢失)
  • datetime(3) → PostgreSQL timestamp(3)(毫秒精度保留)
  • enum('a','b','c') → PostgreSQL varchar + CHECK (col IN ('a','b','c'))(候选值保留) :::

各驱动特殊处理

驱动 建库方式 分页排序 自增语法 索引 外键 字段注释 表注释 collation 序列重置
MySQL CREATE DATABASE ... COLLATE 主键 ORDER BY AUTO_INCREMENT ✅ ✅ ✅ COMMENT ✅ ✅ 库/表/列 跳过
PostgreSQL LC_COLLATE 主键 ORDER BY GENERATED BY DEFAULT AS IDENTITY ✅ ✅ ✅ COMMENT ON COLUMN ✅ ✅ 库/列 ✅ setval
SQL Server COLLATE 主键 ORDER BY IDENTITY(1,1) ✅ ✅ ✅ sp_addextendedproperty ✅ ✅ 库级 ✅ DBCC CHECKIDENT
Oracle CREATE USER + GRANT ROW_NUMBER() 序列+触发器 ✅ ✅ ✅ COMMENT ON COLUMN ✅ ❌ ✅ ALTER SEQUENCE
SQLite 自动创建文件 主键 ORDER BY INTEGER PRIMARY KEY AUTOINCREMENT ✅ ✅ ❌ ❌ ❌ 跳过

PostgreSQL 特殊处理

建库

迁移到 PostgreSQL 时,工具会自动连接 postgres 维护库执行:

-- 创建目标数据库(UTF-8 编码)
-- 若数据库已存在则跳过
-- LC_COLLATE / LC_CTYPE:仅当源与目标驱动相同时,从源库读取 collation 并应用
CREATE DATABASE "数据库名" WITH ENCODING 'UTF8'

:::warning PgSQL 数据库不存在的判定 PostgreSQL 数据库不存在时返回的 SQLSTATE 可能是 3D000(明确不存在)或 08006(连接异常,中文环境下错误信息可能乱码)。工具会主动连接 postgres 维护库查询 pg_database 确认目标库是否真的不存在,避免误判。 :::

序列重置

迁移完成后,对每张有自增主键的表分两步重置序列:

// 第一步:通过 pg_get_serial_sequence() 获取列关联的实际序列名
// 兼容 serial(序列名如 table_id_seq)和 IDENTITY(序列名由系统自动生成)两种风格
$seqName = $dstPdo->query(
    "SELECT pg_get_serial_sequence('表名', '字段名') AS s"
)->fetch()['s'];

// 第二步:查询当前最大值,将序列重置到 max+1
// setval 的第三个参数 false 表示下一个 nextval 返回给定值
$maxId = $dstPdo->query(
    "SELECT COALESCE(MAX(\"字段名\"), 0) AS m FROM \"表名\""
)->fetch()['m'];
$dstPdo->exec("SELECT setval({$quotedSeq}, {$maxId} + 1, false)");

SQL Server 特殊处理

建库

-- 连接 master 库,创建目标数据库
-- COLLATE:仅当源与目标驱动相同时,从源库读取 collation 并应用
CREATE DATABASE [数据库名]

:::warning SQL Server "登录失败"的判定 SQL Server 数据库不存在时返回 SQLSTATE 28000 并提示"登录失败",容易与密码错误混淆。工具会尝试连接 master 库验证凭据:若 master 连接成功说明凭据正确、是数据库不存在;若 master 也失败则为密码错误。 :::

自增列迁移

数据迁移时自动处理 IDENTITY_INSERT:

-- 迁移前开启(允许显式写入自增列的值)
SET IDENTITY_INSERT [表名] ON
-- 批量 INSERT ...
-- 迁移后关闭
SET IDENTITY_INSERT [表名] OFF

序列重置

-- 重置标识种子到指定值,下一次插入自动加一
-- 第一个参数:表名(带方括号)
-- 第三个参数:RESEED 值(当前最大 ID)
DBCC CHECKIDENT ('[表名]', RESEED, {$maxId})

Oracle 特殊处理

建库

Oracle 的"建库"实际上是创建用户并授权。工具自动连接 Oracle 实例(默认服务名 XE),执行:

-- 创建用户(库名即用户名,密码与连接密码一致)
CREATE USER "用户名" IDENTIFIED BY "密码" DEFAULT TABLESPACE USERS;

-- 授权:CONNECT(连接)、RESOURCE(建表等基础对象操作)
GRANT CONNECT, RESOURCE TO "用户名";

:::warning Oracle 连接串 Oracle 的 DSN 格式为 oci:dbname=//host:port/service_name,其中 service_name 默认为 XE(Express Edition)。生产环境需根据实际的 Oracle 服务名修改,可通过 lsnrctl status 查看。 :::

自增列

Oracle 没有原生自增语法,工具自动创建序列+触发器实现等效功能:

-- 创建序列(从 1 开始,每次递增 1,不缓存)
-- 序列名规则:表名_列名_SEQ(大写)
CREATE SEQUENCE "TABLE_ID_SEQ" START WITH 1 INCREMENT BY 1 NOCACHE;

-- 创建触发器(插入前自动取序列值填充自增列)
-- 触发器名规则:表名_列名_TRG(大写)
-- IF :NEW.COL IS NULL 判断:允许显式传值,仅当未指定时才自动取序列
-- :NEW 引用的列名必须大写且不带引号
CREATE OR REPLACE TRIGGER "TABLE_ID_TRG"
BEFORE INSERT ON "TABLE" FOR EACH ROW
BEGIN
  IF :NEW.ID IS NULL THEN
    SELECT "TABLE_ID_SEQ".NEXTVAL INTO :NEW.ID FROM dual;
  END IF;
END;

序列重置

-- 重置序列到 MAX(id)+1
-- ALTER SEQUENCE ... RESTART:将序列起点设置为指定值,下一次 nextval 返回该值
ALTER SEQUENCE "TABLE_ID_SEQ" RESTART START WITH 1234;

:::warning Oracle 建库权限 Oracle 建库实际上是通过 CREATE USER + GRANT CONNECT, RESOURCE 创建用户。需要 DBA 权限或具有 CREATE USER 权限的账号。连接串中的服务名默认为 XE(Express Edition),正式环境需根据实际配置修改。 :::

驱动安装与环境配置

迁移工具基于 PDO 直连数据库,各驱动需要对应的 PDO 扩展。XAMPP 默认只自带 MySQL、PostgreSQL 和 SQLite 驱动,SQL Server 和 Oracle 需手动安装。

运行以下命令检查已安装的驱动:

# 检查已安装的 PDO 驱动(-m 列出扩展,findstr /i 不区分大小写过滤)
D:\xampp8.2\php\php.exe -m | findstr /i pdo

MySQL(XAMPP 默认自带)

无需额外操作。验证:

# 检查 MySQL 驱动是否已加载
D:\xampp8.2\php\php.exe -m | findstr mysql

PostgreSQL(XAMPP 默认自带)

无需额外操作。验证:

# 检查 PostgreSQL 驱动是否已加载
D:\xampp8.2\php\php.exe -m | findstr pgsql

若未安装,编辑 D:\xampp8.2\php\php.ini,找到 ;extension=pdo_pgsql 去掉分号启用。

SQL Server(需手动安装)

XAMPP 默认不带 SQL Server 驱动,需要安装两个组件。

:::warning 先决条件 安装 ODBC 驱动前,系统需要 Microsoft Visual C++ Redistributable。大多数 Windows 已预装,如果安装失败,先安装它再重试。 :::

步骤 1:安装 Microsoft ODBC Driver for SQL Server

这是底层通信组件,sqlsrv 扩展依赖它,必须安装。

步骤 2:下载 PHP 扩展 DLL

从微软官方 GitHub 下载:

解压后目录结构(平铺,无子目录):

Windows_5.12.0RTW/
├── php_pdo_sqlsrv_81_nts_x64.dll
├── php_pdo_sqlsrv_81_ts_x64.dll
├── php_pdo_sqlsrv_82_nts_x64.dll
├── php_pdo_sqlsrv_82_ts_x64.dll   ← PHP 8.2 TS x64
├── php_pdo_sqlsrv_83_nts_x64.dll
├── php_pdo_sqlsrv_83_ts_x64.dll
├── php_sqlsrv_81_nts_x64.dll
├── php_sqlsrv_81_ts_x64.dll
├── php_sqlsrv_82_nts_x64.dll
├── php_sqlsrv_82_ts_x64.dll       ← PHP 8.2 TS x64
├── php_sqlsrv_83_nts_x64.dll
├── php_sqlsrv_83_ts_x64.dll
└── ...(另有 x86 版本)

文件命名规则:php_{驱动}_{PHP版本}_{线程安全}_{架构}.dll

  • 驱动:sqlsrv 或 pdo_sqlsrv
  • PHP 版本:81 / 82 / 83
  • 线程安全:ts(Thread Safe)/ nts(Non-Thread Safe)
  • 架构:x64 / x86

将以下两个文件复制到 D:\xampp8.2\php\ext\:

  • php_sqlsrv_82_ts_x64.dll
  • php_pdo_sqlsrv_82_ts_x64.dll

:::tip 版本匹配(PHP 8.2 + TS + x64)

  • PHP 版本:82 = PHP 8.2
  • 线程安全:XAMPP 默认 TS(运行 php -v 确认,输出含 Thread Safety => enabled)
  • 架构:x64(64 位系统) :::

步骤 3:在 php.ini 中启用扩展

编辑 D:\xampp8.2\php\php.ini(注意不是 apache\bin\php.ini):

  1. 启用前置扩展:找到 ;extension=mbstring 去掉分号(sqlsrv 依赖 mbstring)
  2. 在 mbstring 之后添加以下两行(文件名必须与 ext 目录中的实际文件一致):
; SQL Server PHP 扩展(必须在 mbstring 之后加载)
extension=php_sqlsrv_82_ts_x64.dll          ; SQL Server 核心驱动
extension=php_pdo_sqlsrv_82_ts_x64.dll      ; SQL Server PDO 驱动(迁移工具使用)
  1. 保存后重启 XAMPP 中的 Apache 服务(命令行验证无需重启 Apache,只需重新运行 php 命令)

步骤 4:验证安装

# 检查 SQL Server 驱动是否已加载(应看到 sqlsrv 和 pdo_sqlsrv 两行)
D:\xampp8.2\php\php.exe -m | findstr sqlsrv

应看到 sqlsrv 和 pdo_sqlsrv 两行输出。

Oracle(需手动配置)

XAMPP 8.2 已自带 php_pdo_oci.dll(位于 D:\xampp8.2\php\ext\)。迁移工具基于 PDO 连接数据库,需要 pdo_oci 扩展,只需安装 Oracle Instant Client 并在 php.ini 中启用 pdo_oci 即可。

步骤 1:安装 Oracle Instant Client

pdo_oci 扩展依赖 Oracle 客户端库,必须先安装。

  • 下载地址:https://www.oracle.com/database/technologies/instant-client/downloads.html
  • 版本选择:下载 Basic Package,选择 x64 版本,推荐 19c(与 XAMPP 自带的 DLL 对应)
  • 安装:
    1. 下载 instantclient-basic-windows.x64-19.20.0.0.0dbru.zip
    2. 解压到固定路径,如 C:\oracle\instantclient_19_20(⚠️ 避免空格与中文)
    3. 配置系统环境变量:
      • 将 C:\oracle\instantclient_19_20 添加到系统 PATH
      • 新建系统变量 TNS_ADMIN = C:\oracle\instantclient_19_20(可选,用于 tnsnames.ora)
    4. 重启终端或重启电脑使环境变量生效

:::warning 安装 VC++ 运行库 Oracle Instant Client 19c 依赖 VC++ 2015-2022 Redistributable,缺少会导致 DLL 加载失败。 下载地址:https://aka.ms/vc14/vc_redist.x64.exe :::

步骤 2:在 php.ini 中启用扩展

编辑 D:\xampp8.2\php\php.ini(注意不是 apache\bin\php.ini):

找到以下行:

;extension=pdo_oci              ; Oracle PDO 驱动(默认被注释)

去掉前面的分号,改为:

extension=pdo_oci               ; 启用 Oracle PDO 驱动(迁移工具依赖此扩展)

保存后重启 XAMPP(完全关闭再启动,不是只重启 Apache)。

步骤 3:验证安装

命令行验证:

# 检查 pdo_oci 扩展是否已加载
D:\xampp8.2\php\php.exe -m | findstr -i "pdo_oci"

# 列出所有可用的 PDO 驱动(应包含 oci)
D:\xampp8.2\php\php.exe -r "print_r(PDO::getAvailableDrivers());"

应看到 pdo_oci 输出,且 PDO::getAvailableDrivers() 返回的数组中包含 oci。

phpinfo() 验证:创建 <?php phpinfo(); ?> 放入 htdocs,浏览器访问后搜索 pdo_oci,能看到 PDO drivers 中有 oci 且 Oracle Instant Client 版本号显示即成功。

SQLite(XAMPP 默认自带)

无需额外操作。验证:

# 检查 SQLite 驱动是否已加载
D:\xampp8.2\php\php.exe -m | findstr sqlite

驱动安装速查表

驱动 XAMPP 默认 安装方式 验证命令
pdo_mysql ✅ 自带 无需安装 php -m | findstr mysql
pdo_pgsql ✅ 自带 php.ini 启用 php -m | findstr pgsql
pdo_sqlsrv ❌ 需安装 ODBC 驱动 + PECL 扩展 php -m | findstr sqlsrv
pdo_oci ⚠️ DLL 自带,需启用 InstantClient + VC++ 运行库 + php.ini 启用 php -m | findstr pdo_oci
pdo_sqlite ✅ 自带 无需安装 php -m | findstr sqlite

相关代码

文件 说明
app/command/DbMigrateCommand.php CLI 命令入口(参数解析、退出码)
app/service/DbMigrateService.php 迁移引擎服务(连接、建库、建表、数据迁移、序列重置、行数校验)
app/service/DbSchemaBuilder.php DDL 生成器(类型映射、CREATE TABLE / INDEX / FK / COMMENT 语句生成)
config/console.php 命令注册

注意事项

:::warning 迁移前检查

  1. 确保目标数据库用户有 CREATE TABLE / INSERT / CREATE INDEX / ALTER TABLE(外键)权限
  2. PostgreSQL 迁移还需要 CREATE DATABASE 权限
  3. SQL Server 迁移需要 sa 或有 CREATE DATABASE 权限的用户
  4. Oracle 迁移需要 DBA 权限或具有 CREATE USER / CREATE SEQUENCE / CREATE TRIGGER 权限的账号
  5. --drop-target-first 会删除目标表的全部数据(含索引、外键和注释),请谨慎使用
  6. 大表迁移建议调大 --batch-size(如 5000)以提高效率
  7. 迁移过程中如遇中断,已提交的 --batch-size 批次数据不会回滚,建议使用 --drop-target-first 重试
  8. 含点号的数据库名(如 rxthinkcmf.thinkphp6.elevue)各驱动均已适配
  9. 索引、外键和注释在建表阶段同步迁移,使用 --drop-target-first 重建时会一并重建
  10. 外键约束在所有表创建后统一添加,避免引用顺序问题
  11. 命令退出码:成功返回 0,失败或行数校验未通过返回 1(可用于 CI/CD 判断) :::

常见问题

Q: 迁移失败后如何重试?

A: 使用 --drop-target-first 选项重新执行,会先删除目标表再重建。

Q: 如何只迁移部分表?

A: 使用 --tables 选项指定表名,逗号分隔:

php think db:migrate ... --tables=think_user,think_role

Q: 大表迁移很慢怎么办?

A: 调大 --batch-size,如 --batch-size=5000。默认 1000 行一批,增大批量可减少数据库交互次数。

Q: 目标库不存在会自动创建吗?

A: 会。所有驱动(MySQL / PostgreSQL / SQL Server / Oracle / SQLite)在目标库不存在时都会自动创建,无需手动建库。建库时会自动从源库读取 collation 并应用(仅当源与目标驱动相同时)。

Q: PostgreSQL 报 SQLSTATE 08006 错误怎么办?

A: 08006 是连接异常的通用错误码,在中文环境下错误信息可能乱码。工具会主动连接 postgres 维护库查询 pg_database 确认目标库是否不存在。如果确认不存在会自动建库;如果维护库也连不上,请检查 PostgreSQL 服务是否启动、端口是否正确、密码是否正确。

Q: SQL Server 报"登录失败"怎么办?

A: SQL Server 数据库不存在时会返回"登录失败"(SQLSTATE 28000),工具会自动连接 master 库验证凭据并区分。如果确实是密码错误,请检查 --dst-password。

Q: 索引、外键、注释、默认值会迁移吗?

A: 会。迁移工具在建表阶段会同步迁移以下元数据:

  • 索引:自动读取源表的非主键索引(UNIQUE / 普通 / 全文),在目标表创建后执行 CREATE INDEX
  • 外键:自动读取源表的外键约束,在所有表创建后统一添加 ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY,避免引用顺序问题
  • 字段注释:MySQL 目标直接写入 COMMENT 子句;PostgreSQL / Oracle 使用 COMMENT ON COLUMN;SQL Server 使用 sp_addextendedproperty
  • 表注释:MySQL 使用 ALTER TABLE COMMENT;PostgreSQL / Oracle 使用 COMMENT ON TABLE
  • 默认值:完整保留,包括空字符串 DEFAULT ''、数值默认值、CURRENT_TIMESTAMP、CURRENT_DATE、函数调用(如 uuid_generate_v4())等
  • enum/set:自动迁移候选值,生成 CHECK 约束
  • decimal/datetime 精度:保留精度参数(如 decimal(10,2) → numeric(10,2),datetime(3) → timestamp(3))
  • collation:建库时自动应用源库 collation(仅同驱动时);建表时迁移表级和列级 collation(MySQL / PostgreSQL)

Q: 数据迁移的可靠性如何保证?

A: 迁移工具有以下保障机制:

  • 事务保护:每个 --batch-size(默认 1000 行)为一个独立事务,提交后即生效;当前批次失败时回滚,已提交的历史批次不受影响
  • 稳定排序:分页查询基于主键列 ORDER BY,确保大表分批迁移不会漏行或重复
  • 退出码:命令成功返回 0,失败或行数校验未通过返回 1,可用于 CI/CD 自动化判断
  • 行数校验:迁移完成后逐表对比源和目标的行数,输出 PASS/FAIL

Q: Oracle 自增列如何处理?

A: Oracle 没有原生自增语法。迁移工具会自动为自增列创建序列(CREATE SEQUENCE)和触发器(CREATE TRIGGER),实现等效的自增功能。

Q: 含点号的数据库名支持吗?

A: 支持。如 rxthinkcmf.thinkphp6.elevue 这类含点号的库名,各驱动均已适配。

Q: SQLite 作为目标有什么限制?

A: SQLite 不支持字段注释、表注释和 collation。迁移完成后数据完整,索引和外键正常迁移。