这个PHP无解深坑,你能解出来吗?(听说能解出来的都很秀)
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~
1. 问题背景
PHP Laravel框架中的db migration是比较常用的一个功能了。在每个版本迭代中,除了代码会变动之外,一般数据库的字段或者数据库表也会有些变动。因此在新版本上线时,除了发布新版代码,不可避免地要把数据库的变动也执行了。在没有db migration功能之前,我们的做法是把要变动库表的SQL语句写好(CREATE TABLE
,ALTER TABLE
等)存在一个sql文件中,然后在上线时连接数据库,将sql语句执行一遍。
这么做比较大的一个缺点是没有数据库的版本管理,万一上线失败,要回滚版本,还要把sql文件里的内容再写个反向的SQL(DROP TABLE
,DROP COLUMN
等)。这种方式也比较原始,在web开发中,我们总是希望尽量避免开发直接用原始的sql来操作数据库,出错风险很高,并且很有可能出现不可逆的错误,每次操作都要提心吊胆。
于是乎,PHP Laravel框架提供了db migration的功能,用代码来管理数据库。参考链接
2. 问题描述
在一个新的版本中,我将自己的数据库变更用如下方式记录
php artisan make:migration db_migration_for_new_version
这会在项目的database/migrations
目录下创建一个新的PHP文件,自己填入要变更的数据库内容
public function up {
Schema::create('a_new_table', function(Blueprint $table) {
$table->bigIncrements('id');
});
Schema::create('another_new_table', function(Blueprint $table) {
$table->bigIncrements('id');
$table->string('user', 64)->default(0)->comment('用户名');
// 这里模拟出现错误的情形
throw new \Exception("出现错误");
});
}
在上面这个例子中,我的本意是想要创建两个表格。然而在第一个表格创建完了以后,第二个表格出现错误导致创建失败了。按照正常流程,我在上线时应该执行如下指令创建表格
php artisan migrate
由于第二个表格创建失败,这时候上面的指令必然会报错。然而报错之后你应该怎么做呢?首先当然是把代码里出现错误的地方修正,然后应该怎么搞?此时数据库里面第一个表已经建好了,第二个表还没建。这时候你如果再执行php artisan migrate
会报错:你第一张表格已经创建,不可重复创建表格。你可能会感觉,我需要回滚一次,于是你可能会执行回滚操作php artisan migrate:rollback --step=1
。这里需要强调,此时千万别回滚!!!
因为刚才第一次执行migration出错,导致数据库并没有生成一个新的版本号。这时候如果回滚,那你回滚的是上个版本发布的时候做执行的数据库操作,而不是你刚刚执行的这个版本的数据库操作,这很可能是灾难性的,会导致你数据丢失。目前数据库最新版本是什么,可以参考数据库中migrations表的batch字段(这个表是laravel migration功能自动生成和管理的,并非业务表)。
总结一下这一无解深坑: db migration进行到一半时出错,此时只能手动操作数据库把已经执行的操作回滚掉,无法再通过artisan指令进行回滚
3. 为什么无解?
其实GitHub和StackOverflow上有很多人已经碰到了这个问题,但是答案都很悲观。
所有人的第一反应都是:可以开启事务操作么?将一次migration的所有操作视为一个整体,要么都成功,要么都失败可以么?很遗憾,不支持事务操作。在mysql里面,只有进行update、insert、delete这些常规操作时才可以有事务,而我们migration中执行的都是DDL(Data Definition Language)操作。这种建表(CREATE TABLE
)、修改表结构(ALTER TABLE
)的操作是无法回滚的,即使开启了事务也无法回滚(参考链接)。把DDL操作放在一个事务(Transaction)中,会导致事务自动的提交(参考链接),这往往不是我们代码逻辑所期望的结果。
4. 那该怎么办?
如果你已经碰到了这种问题,那没办法只得手动去一条一条看数据库发生了什么变化,然后自己执行反向操作。
目前只能想到一些预防此问题出现的办法。根据GitHub上的开发者建议,最好每一个CREATE TABLE
、ALTER TABLE
操作都是一个单独的migration。即每次migration只建一张表,或只改一个表结构,只做一个操作( 参考链接)……
还有一种办法是,把自己的建表、改表操作都放在一个try catch
结构中,一旦出现错误,直接调用migration文件中的down函数,把所做的操作回滚掉。不过这个需要注意up和down的兼容性。例如up中有ADD COLUMN
操作,而down中有DROP COLUMN
操作。在ADD COLLUMN
操作执行之前就出错,直接取执行down函数中的DROP COLUMN
,也会有可能报COLUMN不存在的错误。
总之,这个问题并没有十分完美的解决方案,堪称无解深坑,尤其要注意rollback操作不要乱做 ,不要为了弥补一个坑,给自己挖了更大的一个坑。
问答
PHP功能滥用?
相关阅读
一图弄懂ASCII、GB2312、GBK、GB18030编码
其实你不一定懂csv文件格式
【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识
此文已由作者授权腾讯云+社区发布,更多原文请点击
搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!
海量技术实践经验,尽在云加社区!
这个PHP无解深坑,你能解出来吗?(听说能解出来的都很秀)的更多相关文章
- android MultiDex multidex原理原理下遇见的N个深坑(二)
android MultiDex 原理下遇见的N个深坑(二) 这是在一个论坛看到的问题,其实你不知道MultiDex到底有多坑. 不了解的可以先看上篇文章:android MultiDex multi ...
- 在Web API中使用Swagger-UI开源组件(一个深坑的解决)
介绍: Swagger-Ui是一个非常棒的Web API说明帮助页,具体详情可自行Google和百度. 官网:http://swagger.io/ GitHub地址:https://github ...
- 拒绝深坑!记录找了多半天时间的C++编译失败的错误
采用新的源码,和原来的服务改动也不是很大,但是拒绝深坑啊,找了半天以为是源码的问题,结果倒好原来是环境的问题,还是要感谢一个神一样的人物的帮助 编译的时候一直出现undefined reference ...
- 2、使用Angular-CLI初始化Angular项目(踩过的深坑!!!)
1.step1:建一个放项目的文件夹,打开cmd,或vs code的终端,找到文件夹根目录 2.step2:初始化脚手架 初始化命令: ng new 项目名称 --skip-install 注意:-- ...
- Go语言第一深坑:interface 与 nil 的比较
interface简介 Go 语言以简单易上手而著称,它的语法非常简单,熟悉 C++,Java 的开发者只需要很短的时间就可以掌握 Go 语言的基本用法. interface 是 Go 语言里所提供的 ...
- 【Unity笔记】Terrain地形制作坍塌/深坑
Unity的Terrain组件在[set the terrain height]分页下,height高度为0时,可理解为该地形的海平面高度,此时就不能地形下榻.把height调到100,点击[flat ...
- vue经验 - 那些自己给自己挖的深坑
深坑场景:vue-异步请求数据,数据还没回来,页面却如饥似渴的准备好了的尴尬场景:问题原因和解决如下: 1.先说vuex中的store,一开始我为了偷懒是这么设置的,如下图: 然后我到了组件中直接这么 ...
- golang深坑记录
go深坑:1.gin.context.JSON,如果没有make数组时,数组返回为null,make后,数组为[]2.json.Number转int64类型 datatemp.(json.Number ...
- iOS 开发之照片框架详解之二 —— PhotoKit 详解(上)
转载自:http://kayosite.com/ios-development-and-detail-of-photo-framework-part-two.html 一. 概况 本文接着 iOS 开 ...
随机推荐
- Java菜鸟学习笔记(23)--继承篇(二):继承与组合
组合是什么 1.继承和组合都是一种随思想渗透而下的编码方式,其根本目的都是为了复用类,减少重复代码 2.要实现一个类的复用,可以分为组合语法和继承语法 3.组合就是通过将一个对象置于一个新类中,将其作 ...
- Docker技术:在微软云Azure上使用K8S
周末,受微软公司的邀请,参加微软主持的云容器培训会议,为参加培训的学院提供技术辅导,引导学员体验微软云端的DevOps实践. 说是辅导,实际上自己也学到了许多的内容,包括K8S集群.负载.Azure中 ...
- GitHub Android 开源项目汇总 (转)
转自:http://blog.csdn.net/ithomer/article/details/8882236 GitHub 上的开源项目不胜枚举,越来越多的开源项目正在迁移到GitHub平台上.基于 ...
- python3--django for 循环中,获取序号
功能需求:在前端页面中,for循环id会构不成连续的顺序号,所以要找到一种伪列的方式来根据数据量定义序号 因此就用到了在前端页面中的一个字段 forloop.counter,完美解决 <tbod ...
- FTP 作业整理
一.FTP 客户端 与服务器端(没有解决黏包问题的代码) 服务器端设置 import socket import json ADDR = () sk =socket.socket() sk.bind( ...
- activemq消息生产者与消息消费者简单例子
消息生产者HelloQueueProducer.java package activemq.test; import javax.jms.Connection;import javax.jms.Con ...
- 微信小程序与vueJs的异同
简而言之,所有的框架都是建立在原生javascript基础之上的,所以对于有一定js基础的同学来说,各种框架都是比较容易入手的,但不同的框架之间又有一定的差别,有时候切换使用时就会掉入坑了. 一.微信 ...
- spring json的支持
在spring中可以通过配置来实现对json的支持: 以下连接是看到的一篇对这方面内容讲解比较好的文章 http://www.cnblogs.com/fangjian0423/p/springMVC- ...
- [转][SQL]如何实现存储过程中动态加入条件---没想到语句可以这么巧妙
在存储过程过程中,如果要实现Select查询的where子句动态查询,可以用exec ( "select .... where" +@whereStr)这样的方式.但这样的话,感觉 ...
- JSON.stringify和JSON.parse的使用
JSON.stringify 函数 (JavaScript)将 JavaScript 值转换为 JavaScript 对象表示法 (Json) 字符串.JSON.stringify(value [, ...