資料庫版本控制完全指南

edithfang發表於2015-01-14

當資料庫變更發生時,能否從自動化中獲得更大的敏捷性,以較少的資源實現較多的功能,正是那些具有高度競爭力的世界級企業在芸芸眾生中脫穎而出的關鍵因素。

如果你的競爭對手能夠更快地、並且交付質量更好的特性,那麼 你必然會失去市場份額。敏捷開發方法的出現正是為了在應對不斷變化的需求的情況下快速地發展,在有限的資源下也能夠確保理想的質量。

重量級釋出的方式已經過時了,為了每次更新或釋出要等上足足六個月,這種方式無異於自掘墳墓。敏捷開發方法減少了每次釋出的範圍,換取的是更快地完成每個變更,並且將每個變更的影響降至最低。對於技術公司與IT部門來說,必須以敏捷性來保證對不斷變化的業務需求的支援。

接下來的一個邏輯步驟是將開發與運維相結合,即採用DevOps方法。

為了在敏捷的Sprint釋出中有效地應用DevOps,你需要實現部署與流程自動化,自動構建內部的開發與QA環境,以及生產環境。否則的話,你只能選擇手動實現部署與釋出的每個步驟與流程,這就很可能產生人為的錯誤,而且也無法頻繁地重複這一過程。

實現自動化依賴於版本控制系統,它能夠管理所有等待構建並部署到下一個環境的軟體資產。



構建流程的第一個步驟是清理工作空間,並從版本控制庫中換取相應的檔案。這一重要的步驟避免了流程外的變更。如果開發者直接將變更儲存到構建伺服器的工作空間,而不是將變更籤入版本控制庫中,那麼這種變更仍然有可能產生。這個例子聽起來似乎有些可笑,因為開發者都明白,如果不把變更籤入到版本控制庫中,這些變更就會丟失,因為技術手段保證了流程的正確性。這一步驟同時也避免了將尚未完成的變更包含至構建過程中,只有通過正確的簽入流程提交至版本控制庫中的變更才會加入構建過程。版本控制庫在這裡成為了唯一的信賴源

資料庫是關鍵的部件

如今多數的IT應用程式都包括數量眾多的部件,使用了多種不同的技術:移動、ASP、PHP、應用程式伺服器、Citrix及資料庫等等。為了讓應用程式正確執行,必須讓這些元件相互配合。下面舉個例子,如果在某張表中加入了一個新的列,或是在某個儲存過程中加入了一個新的引數,那麼為了讓功能正常執行,其它所有的應用程式元件也必須與結構的變化進行同步。一旦同步過程出錯,應用程式在呼叫儲存過程時使用了錯誤的引數,或者是在插入資料時遺漏了新的列,應用程式就會出錯。

資料庫元件的獨特性將它與其它元件區別開來:

  • 資料庫不僅僅只是SQL指令碼,還包括了表結構、在儲存過程中用資料庫語言編寫的程式碼、在引用表或配置表中所存放的內容,以及物件之間的依賴等等。
  • 資料庫是集中式的資源,多個開發者可以在同樣的物件上進行工作,因此必須對他們的工作進行同步,以避免程式碼相互覆蓋。
  • 資料庫變更的部署不像拷貝與替換舊版本的二進位制檔案那麼簡單,在將資料庫從版本A轉換至版本B的過程中,需要在保留業務資料的同時轉換為新的結構。
  • 資料庫程式碼直接存在於資料庫中,並且可以在任何環境中進行直接修改。這一點就與其它的元件不同,它們都是在構建伺服器中的某個乾淨的工作空間中進行編譯的。

必須滿足的需求

在管理資料庫變更時,需要克服一系列的困難,你必須做到以下幾點:

  • 確保所有的資料庫程式碼都被涵蓋(結構、程式碼、引用內容和授權等等)
  • 確保以版本控制庫作為唯一的信任源
  • 確保被執行的部署指令碼在執行時能夠正確判斷環境狀態
  • 確保部署指令碼能夠正確處理與合併衝突
  • 只為相關的變更生成部署指令碼
  • 確保部署指令碼瞭解資料庫的依賴項


對於在開發階段,以及內部部署(開發環境與QA環境)或部署到生產伺服器時的資料庫變更管理,有四種通用的管理方式。

  • 建立開發階段生成的SQL變更指令碼
  • 建立一個變更日誌的跟蹤系統
  • 建立簡單的比較與同步機制
  • 建立一個資料庫執行變更管理解決方案

建立開發階段生成的SQL變更指令碼

管理資料庫變更的最基本方式,就是將所有變更命令儲存在一個或一系列指令碼中,並且在基於檔案的版本控制系統中對它們進行管理。以此保證在一個單一的儲存庫中儲存所有的應用程式元件資產。對於開發者來說,將資料庫變更進行簽入可以使用的功能類似於他們簽入.NET或Java的變更時的功能,例如將變更與變更原因(變更請求、缺陷編號、使用者故事等等)相關聯。對於當前流行的各種基於檔案的版本控制方案來說,基本上都能夠在多個開發者對同一個檔案進行變更的時候發出合併的警告。

但讓我們來看一下,這個解決方案是否真正能夠克服資料庫方面的各種挑戰,並且避免各種可能的風險呢:

  • 確保所有的資料庫程式碼都被涵蓋 —— 由於開發者或DBA編寫了指令碼,因此他們能夠確保所有資料庫程式碼都被涵蓋。
  • 確保以版本控制庫作為唯一的信任源 —— 並非如此。因為開發者和DBA能夠直接登入(任何環境的)資料庫,並且直接在資料庫中進行變更。


手動編寫的SQL指令碼

對於部署指令碼的變更,例如釋出內容範圍的變更、分支合併及重複勞動等等必須手動完成,並且需要額外的測試。

這種情況下需要維護兩種型別的指令碼,即對這次釋出的建立指令碼,以及針對某些特定變更的變更指令碼。對於同樣的變更需要維護兩種指令碼,這已是災難開始的前兆了。

  • 確保被執行的部署指令碼在執行時能夠正確判斷環境狀態 —— 這一點取決於開發者,以及指令碼編寫的方式。如果指令碼本身只包括相關的變更命令,那麼它對於執行時的環境狀態就一無所知。這就意味著即使某個列已經存在,它也會試圖再次新增這個列。而如果要編寫能夠在執行期判斷環境狀態的指令碼,會極大地提升指令碼開發工作的複雜性。
  • 確保部署指令碼能夠正確處理與合併衝突 —— 雖然基於檔案的版本管制系統提供了合併衝突的功能,但這一點對於資料庫來說意義不大,因為版本管理庫裡的內容未必是百分之百準確的,因此也無法充當唯一的信任源。指令碼或許覆蓋了另一個團隊所做的某個hot fix,而這種錯誤不會留下任何痕跡。
  • 只為相關的變更生成部署指令碼 —— 指令碼的建立是屬於開發過程的一部分。根據所佈置的任務,如果要確保指令碼中只包括相關的、並且經過授權的變更,必需對指令碼進行改動,而這進一步提高了部署時的風險,並且也會浪費時間。
  • 確保部署指令碼瞭解資料庫的依賴項 —— 開發者必須在編寫指令碼時留意到資料庫的依賴項。如果只使用一個唯一的指令碼,那麼變更通常是按順序從指令碼的最後加入的,這就有可能導致對相同的物件進行多次變更。而且如果要使用多個指令碼,那麼指令碼的執行順序則至關重要,並且必須手動維護。


結論:這種基本方式不僅無法克服資料庫的各種挑戰,並且極易出錯,也極耗時間,並且需要引入一個額外的系統以跟蹤被執行的指令碼。

建立一個變更日誌的跟蹤系統

另一種常見的方式是使用XML檔案作為一種抽象的語言,對變更進行描述並對執行過程進行追蹤。這方面最常見的開源解決方案就是Liquibase。

Liquibase使用XML檔案將邏輯變更從物理變更中分離出來,並且允許開發者在不瞭解資料庫的特定指令的情況下編寫變更。在執行期間,它會將XML轉化為特定的RDBMS語言以執行這些變更。所有的變更將被組合到一個變更日誌中,日誌可以作為一個單獨的XML檔案存在,也可以是由一個包含了變更順序的主XML檔案所引用的多個XML檔案共同實現。

可以用現有的基於檔案的版本控制系統儲存這些XML檔案,這一點與基本方式的好處是相同的。此外,通過Liquibase的執行跟蹤能力,還能夠了解到哪些變更日誌是已經被部署過,不應該再次執行的,以及哪些是尚未部署而等待執行的。

那麼讓我們來看一下,Liquibase是否解決了這些挑戰呢:

  • 確保所有的資料庫程式碼都被涵蓋 ——Liquibase中的XML檔案不支援對引用內容變更的管理,必須由外部的擴充套件功能進行處理,這就很可能導致某些變更被遺忘。
  • 確保以版本控制庫作為唯一的信任源 ——Liquibase本身沒有任何版本控制的功能,它依賴於第三方的版本控制工具對XML檔案進行管理。因此,你還是必須想辦法保證基於檔案的版本控制庫能夠正確地反映當前被測試的資料庫版本。為了確保版本控制庫能夠作為唯一的信任源,開發者必需將變更籤入,以便進行測試。這有可能導致尚未完成的變更也被部署到下一個環境中。
  • 確保被執行的部署指令碼在執行時能夠正確判斷環境狀態 ——Liquibase知道哪些變更日誌已經被部署,並且不會再次執行它們。但是,如果某個邏輯變更是增加一個日期型別的列,而該列已經存在,並且是varchar格式的,那麼部署肯定會失敗。此外,Liquibase也無法避免外部程式對資料庫進行的任何變更。
  • 確保部署指令碼能夠正確處理與合併衝突 ——在Liquibase之外對資料庫進行的任何變更都可能導致衝突,而這是Liquibase無法處理的。


無法處理外部程式產生的變更

  • 只為相關的變更生成部署指令碼 —— 在變更日誌這一級別可以忽略某些變更,但將一個變更日誌分解為多個日誌需要重寫編寫XML檔案,而這也需要更多的測試。
  • 確保部署指令碼瞭解資料庫的依賴項 —— 在變更日誌XML檔案的編寫過程中,需要手動維護變更的順序。
結論:使用能夠追蹤變更執行的系統並不能處理資料庫開發中的所有挑戰,最終也無法勝任部署的需求。

建立簡單的比較與同步機制

另外一種常見的方式是通過將源(開發)環境與目標(測試、UAT、生產等等)環境進行比較,由此自動生成資料庫變更指令碼。這種方式節省了開發者與DBA的大量時間,因此他們無需手動地對每次釋出的建立指令碼或變更指令碼進行手動維護了。只在需要的時候生成對應目標環境當前結構的指令碼。

讓我們再來看一看,這種方式是否能夠應對資料庫管理的挑戰:

  • 確保所有的資料庫程式碼都被涵蓋 —— 多數的比較與同步工具都瞭解如何處理不同的資料庫物件,但其中只有一部分工具能夠在比較與同步時處理引用資料。
  • 確保以版本控制庫作為唯一的信任源 —— 簡單的比較與同步工具在執行比較與生成合並指令碼的時候,並不會用到程式碼控制庫。
  • 確保被執行的部署指令碼在執行時能夠正確判斷環境狀態 —— 最佳實踐是在準備執行的時候生成指令碼,這樣就能保證它引用了正確的環境狀態了。

    [*]

  • 確保部署指令碼能夠正確處理與合併衝突 —— 簡單的比較與同步工具將A與B(源與目標)環境進行比較,基於右方的表,該工具能夠生成一份指令碼,將目標環境進行“升級”,以符合源環境的內容。如果不瞭解某個變更的內容,那麼有可能會生成錯誤的指令碼。舉例來說,在目標環境中有一個索引,是在某個不同的分支或是嚴重缺陷修復時建立的。如果該索引並不存在於源環境中,那麼工具又該怎麼做呢?刪除這個索引?如果在開發環境中存在某個索引,而在生產環境中不存在,是意味著開發環境中加入了這個索引,還是說生產環境中刪除了這個索引呢?使用這種工具作為解決方案,需要你對每個變更的內容有深入的瞭解,以保證能夠正確地進行處理。
  • 只為相關的變更生成部署指令碼 —— 比較與同步工具會對整個資料庫schema進行比較,並顯示出不同之處。但它們並不瞭解變更背後的原因,因為這些資訊是儲存在軟體生命週期管理工具、CMS,或是版本控制庫中的,而它們對於比較與同步工具來說屬於外部資訊。結果是你可能會被一大堆無關的背景雜音所干擾,導致你難以判斷應該做些什麼。
  • 確保部署指令碼瞭解資料庫的依賴項 —— 比較與同步工具能夠了解資料庫的依賴項,並且以正確的順序生成相關的DDL、DCL與DML語句。但不是所有比較與同步工具都支援在生成的指令碼中包含多個schema的內容。
結論:比較與同步工具能夠滿足這些必要需求中的一部分,但不是全部。指令碼依然需要手動審查,而且在自動化過程中無法完成依賴。

建立一個資料庫執行變更管理解決方案

資料庫執行變更管理結合了對資料庫物件強制使用版本控制的流程,並且基於版本控制庫及當前環境的結構,在需要時生成部署指令碼。

這種方式意味著“按需構建與部署”,意即部署指令碼是在需要時才進行構建(生成)的,而不是作為開發過程的一部分。這種方式保證了有效地處理衝突、合併,以及外部程式產生的變更。


按需構建與部署

那麼資料庫執行變更管理解決方案又是如何應對相同的挑戰的呢?

  • 確保所有的資料庫程式碼都被涵蓋 —— 結構、用資料庫語言編寫的業務邏輯、引用內容、資料庫許可權等內容都被正確地管理。
  • 確保以版本控制庫作為唯一的信任源 —— 強制的變更策略能夠阻止任何人在任何IDE(甚至是命令列)中對資料庫物件進行更改,而不經過事先的簽出與變更後的簽入。這就保證了版本控制庫在物件簽入時始終於物件的定義相一致。


單一的流程強制了版本控制的實施

  • 確保被執行的部署指令碼在執行時能夠正確判斷環境狀態 —— 按需(在準備執行前)構建(生成)部署指令碼的方式確保了它完全瞭解當前的環境狀態。
  • 確保部署指令碼能夠正確處理與合併衝突 —— 在分析過程中使用基線比較,就能夠了解變更的原因,並且能夠簡單地判斷是否要將變更進行部署、或是對目標環境進行保護(即忽略該變更)、或是對衝突進行合併。


瞭解基線的分析

  • 只為相關的變更生成部署指令碼 —— 與應用程式生命週期管理(ALM)工具及變更管理系統(CMS)的結合保證你能夠為每個變更分配一個原因,就如同你在基於檔案的版本控制系統或任務管理系統中所做的一樣。
  • 確保部署指令碼瞭解資料庫的依賴項 —— 嚴密的分析與指令碼生成演算法確保了DDL、DCL和DML等語句能夠根據資料庫的依賴,以正確的順序進行執行,包括了跨schema的依賴。
除了這些必需滿足的需求之外,還存在著一些別的需求,例如對並行開發的支援、合併分支、與資料庫IDE的整合,以及支援由資料建模工具生成的變更等等。無論你選擇了哪種方式,都必須驗證它是否能夠處理這些需求。

結論

對資料庫元件的管理有著特殊的需求,因此對自動化流程來說是個極大的挑戰。在距今較遠的年代裡,一年中通常只有幾次釋出,因此花費大量時間對資料庫部署指令碼進行手動審查以及維護是常見的、也是情有可原的做法。現如今,隨著對敏捷及更快的釋出速度的需求不斷增長,資料庫管理必須成為自動化流程的一部分。無論是編寫SQL或XML指令碼,或是使用簡單的比較與合併工具,一旦在自動化過程中使用,都存在著低效或是高風險的問題。最有效的方式是實現資料庫執行變更管理。

相關閱讀
評論(1)

相關文章