关键在于确保迁移脚本幂等:用migrations表记录已执行脚本,每次运行前校验跳过;结合create table if not exists、alter table ... add column if not exists(mysql 8.0.19+)及insert ... select + where not exists避免重复操作;统一使用pdo并启用异常模式,生产环境禁用直接回滚ddl,采用向前兼容策略。

数据库迁移脚本不是“写完就能跑”,关键在于可重复执行、幂等、与部署流程对齐——PHP 项目里直接用原生 SQL 或 PDO 手动执行,比引入重型迁移工具更可控,也更容易排查问题。
怎么让迁移脚本支持多次运行不报错
核心是避免重复建表、重复加字段、重复插入初始化数据。MySQL 的 CREATE TABLE IF NOT EXISTS、ALTER TABLE ... ADD COLUMN IF NOT EXISTS(MySQL 8.0.19+)能解决一部分,但低版本得靠手动判断。
推荐做法:用一张 migrations 表记录已执行的脚本名和时间,每次执行前查表跳过已运行的:
CREATE TABLE IF NOT EXISTS migrations (
id INT AUTO_INCREMENT PRIMARY KEY,
script_name VARCHAR(255) NOT NULL UNIQUE,
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
在迁移脚本开头加检查逻辑:
$script = '20240501_add_user_status.php';
$stmt = $pdo->prepare("SELECT 1 FROM migrations WHERE script_name = ?");
$stmt->execute([$script]);
if ($stmt->fetch()) {
exit("Skipped: $script already executed\n");
}
// 执行实际变更...
$pdo->exec("ALTER TABLE users ADD COLUMN status TINYINT DEFAULT 1");
// 记录执行
$pdo->prepare("INSERT INTO migrations (script_name) VALUES (?)")->execute([$script]);
- 脚本名建议用时间前缀(如
20240501_add_user_status.php),保证顺序可排序 - 不要依赖
AUTO_INCREMENT主键做执行顺序判断,只靠文件名或显式version字段 -
INSERT INTO ... SELECT类操作也要加WHERE NOT EXISTS,否则初始化数据会重复插入
PHP 迁移脚本里该用 PDO 还是 mysqli
选 PDO。它支持预处理防注入、异常模式(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)、多数据库兼容(哪怕当前只用 MySQL),而且错误堆栈更清晰。
必须设置的连接选项:
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4",
]);
- 不用
mysqli_real_escape_string手动转义——所有参数走prepare/execute - 避免在迁移中用
mysql_connect等已废弃函数 - 如果脚本需在 CLI 和 Web 下都能跑,注意区分
$_SERVER['argv']和$_GET,CLI 模式下不要依赖 session 或 cookie
ALTER TABLE 在大表上卡住怎么办
MySQL 5.6+ 的 ALGORITHM=INPLACE 能避免锁表,但不是所有操作都支持。比如加索引、加列(非首列)、改列类型(部分情况)可以,但修改主键或全文索引仍会锁表。
实操建议:
- 先用
SHOW CREATE TABLE table_name确认当前引擎(InnoDB才支持 inplace) - 对百万级以上表,用
pt-online-schema-change(Percona Toolkit)替代直接ALTER,它通过影子表+触发器实现无锁变更 - 在迁移脚本里加超时控制:
set_time_limit(300),防止长时间阻塞部署流程 - 生产环境迁移前,务必在同规格从库上先试跑一遍
如何安全地回滚一个已执行的迁移
PHP 迁移脚本通常不内置回滚逻辑,因为 DDL 回滚成本高、语义模糊(比如删了列,原始数据已丢)。更现实的做法是:
- 每个迁移脚本配一个对应
_rollback.php文件,只包含逆向操作(如删列、删表),但仅用于开发/测试环境 - 生产环境原则上不做 DDL 回滚,而是用“向前兼容”策略:字段先加、旧代码忽略新字段,等全量发布后再删旧字段
- 如果真要删数据,必须加
WHERE条件且用SELECT COUNT(*)先验证影响行数,禁止无条件DELETE FROM - 所有删除类操作,建议先
RENAME TABLE备份原表(如users_20240501_bak),确认无误再删
真正容易被忽略的是环境隔离:开发、测试、预发、生产必须用不同数据库实例,迁移脚本里不能硬编码 host/port,而应读取 getenv('DB_HOST') 或配置文件——否则一次手抖执行错环境,没有后悔药。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











