1. 程式人生 > >這個PHP無解深坑,你能解出來嗎?(聽說能解出來的都很秀)

這個PHP無解深坑,你能解出來嗎?(聽說能解出來的都很秀)

也會 所有 彌補 ini 事務 migration 兩個 cloud public

歡迎大家前往騰訊雲+社區,獲取更多騰訊海量技術實踐幹貨哦~

本文由horstxu發表於雲+社區專欄

1. 問題背景

PHP Laravel框架中的db migration是比較常用的一個功能了。在每個版本叠代中,除了代碼會變動之外,一般數據庫的字段或者數據庫表也會有些變動。因此在新版本上線時,除了發布新版代碼,不可避免地要把數據庫的變動也執行了。在沒有db migration功能之前,我們的做法是把要變動庫表的SQL語句寫好(CREATE TABLEALTER 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 TABLEALTER 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無解深坑,你能解出來嗎?(聽說能解出來的都很秀)