跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

Laravel如何做数据库迁移锁机制_Laravel避免多人同时迁移冲突【说明】

迁移前须创建InnoDB引擎的schema_lock表并实现抢占式锁,通过INSERT...SELECT原子操作加锁、shutdown函数释放,并在CI/CD中配置串行执行。 迁移执行前先检查
schema_lock
表是否存在 Laravel 本身不内置数据库迁移锁,多人同时运行
php artisan migrate
时,可能因并发写入
migrations
表或修改结构导致死锁、重复执行、表损坏。真正起作用的是你手动加的防护逻辑,不是框架默认行为。 常见错误现象:
SQLSTATE[HY000]: General error: 1205 Deadlock found when trying to get lock
,或者某次迁移只执行了一半、
migrations
表里多出两条相同
batch
记录。 上线前务必确认数据库中是否存在
schema_lock
(或类似名称)表;没有就建一个:
CREATE TABLE `schema_lock` ( `locked` TINYINT(1) NOT NULL DEFAULT '0', `locked_at` TIMESTAMP NULL, `locked_by` VARCHAR(255) NULL ) ENGINE=InnoDB;
该表必须是 InnoDB 引擎,MyISAM 不支持行级锁和事务,起不到保护作用 不要用
SELECT ... FOR UPDATE
直接锁整张
migrations
表——它会被其他迁移命令绕过,且影响正常部署流程 在
AppServiceProvider
的
boot()
里加轻量级抢占式锁 不能依赖 Artisan 命令启动后再判断,因为
migrate
命令初始化阶段就会读取迁移文件、连接数据库,锁必须在任何迁移操作前生效。 使用
INSERT ... SELECT ... FROM DUAL WHERE NOT EXISTS
实现原子抢占,比
SELECT + INSERT
两步更安全: 在
app/Providers/AppServiceProvider.php
的
boot()
方法开头插入:
if (app()->runningInConsole() && in_array('migrate', $_SERVER['argv'] ?? [])) { $locked = DB::insert("INSERT INTO schema_lock (locked, locked_at, locked_by) SELECT 1, NOW(), ? FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM schema_lock WHERE locked = 1)", [gethostname()]); if (!$locked) { $this->warn('Migration lock held by another process. Aborting.'); exit(1); } }
注意:MySQL 8.0+ 支持
DUAL
,MariaDB 也兼容;低版本 MySQL 可改用
(SELECT 1)
子查询替代 别用
cache
或文件锁——部署环境常为无共享文件系统,Redis 缓存也可能跨实例失效 迁移成功后必须显式释放
schema_lock
锁不是自动消失的。如果进程崩溃、超时中断、或人为
Ctrl+C
,锁会一直挂着,后续所有迁移都会被拦住。 Laravel 13.2.0 PHP中文网提供Laravel 13.2.0版本下载,Laravel框架 是基于 PHP 8.3+ 的高性能框架,官方推荐通过 Composer 安装。它内置 AI SDK、JSON:API Resources 及原生向量搜索,支持属性驱动开发与队列路由,大幅提升开发效率。相比旧版,13.2.0 优化了缓存 TTL 管理与实时通信,无需 Redis 即可横向扩展。作为现代 Web 开发首选,它兼顾安全与极速体验,助您快速构建企业级应用。 下载 释放动作不能放在 migration 文件里(太晚),也不能只靠 finally(Artisan 异常退出时不一定触发)。最稳妥的方式是注册
register_shutdown_function
: 在上面那段抢占逻辑之后追加:
register_shutdown_function(function () { if (app()->runningInConsole() && in_array('migrate', $_SERVER['argv'] ?? [])) { DB::statement("DELETE FROM schema_lock WHERE locked = 1"); } });
这个函数会在脚本终止时执行,包括 fatal error、exit()、正常结束三种情况 不要用
DB::transaction()
包裹整个迁移流程——迁移命令本身已含事务控制,再套一层反而增加死锁风险 释放语句用
DELETE
而非
UPDATE
,避免残留脏数据 CI/CD 环境要额外防重跑和跨节点冲突 GitLab CI、GitHub Actions 这类并行流水线,可能多个 job 同时拉代码、同时执行
migrate
,光靠单机锁没用。 关键点在于:锁必须是全局唯一的,且由调度层统一协调。 把迁移步骤设为「串行任务」:GitLab CI 中加
resource_group: migrations
;GitHub Actions 用
concurrency
键限制同一组 job 并发 禁止在多个 deploy 节点上都配置自动迁移——只允许主 deploy 节点执行,其他节点跳过:
if [[ $(hostname) == "deploy-prod-01" ]]; then php artisan migrate; fi
若用 Kubernetes,可在 Job manifest 中加
parallelism: 1
和
completions: 1
,确保仅一个 Pod 执行 别信“加个 sleep 随机延迟就能错开”——概率性方案在高频率部署下迟早出事 真正难的不是写几行 SQL 加锁,而是让锁在各种异常路径下都不漏、不僵、不误杀。比如 SIGKILL 无法被捕获,这时候只能靠外部监控定期清理陈旧锁记录——这事得单独写个运维脚本,别指望 PHP 自己兜底。

相关文章