不停機狀態下使用Django建立索引
該框架在管理資料庫更改方面非常強大和有用,但是該框架提供的靈活性受到了一定的限制。為了理解Django遷移的侷限性,你將處理一個眾所周知的問題:在不停機的情況下,在Django中建立一個索引。
在本教程中,你將學習:
Django如何以及何時生成新的遷移;
如何檢查Django生成的執行遷移的命令;
如何安全地修改遷移以滿足你的需求。
本中級教程是為已經熟悉Django遷移(Migration)的讀者設計的。
在Django遷移中建立索引的問題
當應用程式儲存的資料增長時,通常需要進行的一個常見更改就是新增索引。索引可以用來加快查詢速度,並使你的應用程式執行和響應更快。
在大多數資料庫中,新增索引時需要對錶使用獨佔鎖。在建立索引時,獨佔鎖會防止資料修改(DML)操作,如UPDATE,INSERT,和DELETE。
資料庫在執行某些操作時會隱式地獲取鎖。例如,當用一個戶登入到你的應用程式時,Django將更新auth_user表中的last_login欄位。要執行更新,資料庫首先必須在這個行上獲得一個鎖。如果該行當前被另一個連線鎖定,那麼你會得到一個資料庫異常。
當需要在遷移期間保持系統可用時,鎖定表可能會造成問題。表越大,建立索引所需的時間就越長。建立索引所需的時間越長,系統不可用或對使用者無響應的時間就越長。
一些資料庫供應商提供了一種建立索引而不鎖定表的方法。例如,要在PostgreSQL中建立索引而不鎖定表,你可以使用CONCURRENTLY關鍵字:
在Oracle中,有一個ONLINE選項允許在建立索引時對錶執行DML操作:
在生成遷移時,Django不會使用這些特殊的關鍵字。按原樣執行遷移將使資料庫獲得表上的獨佔鎖,並在建立索引時防止DML操作。
併發建立索引有一些注意事項。提前瞭解特定於資料庫後端的問題是很重要的。例如,PostgreSQL中的一個警告是併發建立索引需要更長的時間,因為它需要進行額外的表掃描。
在本教程中,你將使用Django遷移在一個大型表上建立索引,而不會導致任何停機。
注意:要學習本教程,建議你使用PostgreSQL後端,Django2.x和python3。
也可以使用其他資料庫後端。在使用PostgreSQL特有的SQL特性的地方,更改SQL以匹配你的資料庫後端。
設定
你將在一個名為app的應用中使用一個虛構的Sale模型。在現實生活中,Sale等模型是資料庫中的主要表,它們通常會非常大,並儲存大量資料:
建立表,生成初始遷移並應用它:
一段時間之後,sales表變得非常大,使用者開始抱怨速度太慢。在監視資料庫時,你注意到許多查詢使用sold_at列。為了加快速度,你決定在列上需要一個索引。
要在sold_at上新增索引,你需要對模型進行以下更改:
如果按原樣執行這個遷移,那麼Django將在表上建立索引,並且它將被鎖定,直到索引完成。在非常大的表上建立索引可能需要一段時間,你希望避免停機。
在具有小資料集和很少連線的本地開發環境中,這種遷移可能是瞬間完成的。然而,對於具有許多併發連線的大型資料集,獲取鎖並建立索引可能需要一段時間。
在接下來的步驟中,你將修改Django建立的遷移,以便在不引起任何停機的情況下建立索引。
偽造遷移
第一種方法是手動建立索引。你將生成遷移,但實際上並不會讓Django應用它。相反,你將在資料庫中手動執行SQL,然後讓Django認為遷移已經完成。
首先,生成遷移:
使用sqlmigrate命令來檢視Django將用於執行此遷移的SQL:
你希望在不鎖定表的情況下建立索引,因此你需要修改命令。新增CONCURRENTLY關鍵字並在資料庫中執行:
注意,你在執行命令的過程中沒有BEGIN和COMMIT部分。省略這些關鍵字會在沒有資料庫事務的情況下執行命令。我們將在本文後面討論資料庫事務。
執行命令後,如果你嘗試應用遷移,會出現以下錯誤:
Django會提示你該索引已經存在,因此無法繼續遷移。你剛剛在資料庫中直接建立了索引,所以現在需要讓Django認為已經應用了遷移。
如何偽造一個遷移
Django提供了一種內建的方法,可以將遷移標記為已執行,而不需要實際執行它們。要使用這個選項,你需要在應用遷移時設定—fake標誌:
這一次Django沒有丟擲錯誤。實際上,Django並沒有真正應用任何遷移。它只是將其標記為已執行(或FAKED)。
以下是在偽造遷移時需要考慮的一些問題:
手動命令必須與Django生成的SQL等價: 你需要確保所執行的命令與Django生成的SQL等價。使用sqlmigrate來生成SQL命令。如果命令不匹配,則可能導致資料庫和模型狀態之間的不一致。
其他未應用的遷移也將被偽造:當你有多個未應用的遷移時,它們都將被偽造。在應用遷移之前,重要的是確保只有你想要偽造的遷移沒有應用。否則,你可能會得到不一致的結果。另一個選項是指定要偽造的確切遷移。
需要直接訪問資料庫:你需要在資料庫中執行SQL命令,這有時也不是必需的。此外,在生產資料庫中直接執行命令是危險的,應該儘可能避免。
自動化部署流程可能需要調整:如果你自動化了部署流程(使用CI、CD或其他自動化工具),那麼你可能需要將流程更改為偽遷移。這並不總是可取的。
清理
在繼續下一節之前,你需要將資料庫恢復到它在初始遷移之後的狀態。要做到這一點,請遷移回初始遷移:
Django沒有應用在第二次遷移中所做的更改,所以現在可以安全地刪除檔案:
為了確保你做的一切都是正確的,檢查一下遷移:
應用了初始遷移之後,就沒有未應用的遷移了。
在遷移(Migration)中執行原始SQL
在上一節中,你直接在資料庫中執行SQL並偽造遷移。這樣就完成了任務,但是還有一個更好的解決方案。
Django提供了一種使用RunSQL在遷移中執行原始SQL的方法。我們來嘗試使用它代替直接在資料庫中執行命令。
首先,生成一個新的空遷移:
接下來,編輯遷移檔案並新增RunSQL操作:
當你執行遷移時,你將獲得以下輸出:
這看起來不錯,但有一個問題。我們再次來嘗試生成遷移:
Django再次生成了相同的遷移。為什麼會這樣?
清理
在回答這個問題之前,你需要清理並撤消對資料庫所做的更改。首先刪除最後一次遷移。它沒有被應用,所以可以安全刪除:
接下來,列出app應用程式的遷移:
第三次遷移已經結束,但是隻應用了第二次遷移。你希望回到初始遷移之後的狀態。試著像你在上一節所做的那樣遷移回初始遷移狀態:
Django無法進行逆向遷移。
逆向遷移操作
要進行一次逆向遷移,Django會對每個操作執行相反的操作。在本例中,新增索引的反面是刪除索引。正如你已經看到的,當一個遷移是可逆的時,你可以取消應用它。就像你可以在Git中使用checkout一樣,如果你對較早的遷移執行了migrate命令,你可以進行逆向遷移。
許多內建遷移操作已經定義了反向操作。例如,新增欄位的反向操作是刪除對應的列,建立模型的反向操作是刪除相應的表。
有些遷移操作是不可逆的。例如,刪除欄位或刪除模型沒有反向操作,因為一旦應用了遷移,資料就會消失。
在上一節中,你使用了RunSQL操作。當你試圖進行反向遷移時,遇到了一個錯誤。根據錯誤提示,遷移中的一個操作不能逆轉。Django預設情況下無法反轉原始SQL。因為Django不知道該操作執行了什麼,所以它不能自動生成相反的操作。
如何使遷移可逆
要使一個遷移是可逆的,遷移中的所有操作都必須是可逆的。只逆轉遷移的一部分是不可能的,因此一個單一的不可逆操作將使整個遷移不可逆。
要使RunSQL操作可逆,你必須提供在操作反轉時執行的SQL。反向SQL在reverse_sql引數中提供。
新增索引的相反操作是刪除索引。要使你的遷移可逆,請提供reverse_sql引數來刪除索引:
現在試著反轉遷移:
我們對第二次遷移進行了反轉,Django刪除了索引。現在可以安全地刪除遷移檔案了:
提供reverse_sql總是一個好主意。在反轉原始SQL操作而不需要其他任何操作的情況下,你可以使用特殊的哨兵語句migrations.
RunSQL.noop將該操作標記為可逆操作。
理解模型狀態和資料庫狀態
在你之前嘗試使用RunSQL手動建立索引時,Django一次又一次地生成了相同的遷移,儘管索引是在資料庫中建立的。要理解Django為什麼這樣做,你首先需要理解Django如何決定何時生成新的遷移。
當Django生成一個新的遷移時
在生成和應用遷移的過程中,Django同步資料庫狀態和模型狀態。例如,當你向模型新增欄位時,Django會向表新增一列。當你從模型中刪除欄位時,Django將從表中刪除列。
為了在模型和資料庫之間同步,Django擁有一個表示模型的狀態。為了使資料庫與模型同步,Django會生成遷移操作。遷移操作轉換為可以在資料庫中執行的特定供應商的SQL。當所有遷移操作都執行後,資料庫和模型應該是一致的。
為了獲得資料庫的狀態,Django聚合了所有過去遷移的操作。當遷移的聚合狀態與模型的狀態不一致時,Django會生成一個新的遷移。
在前面的例子中,你使用原始SQL建立了索引。Django不知道你建立了索引,因為你沒有使用熟悉的遷移操作。
當Django聚合所有遷移並將它們與模型的狀態進行比較時,它發現缺少一個索引。這就是為什麼即使你手動建立了索引,Django仍然認為它是缺失的,併為它生成了一個新的遷移。
如何在遷移中分離資料庫和狀態
由於Django無法按照你希望的方式建立索引,所以你希望提供自己的SQL,但仍然要讓Django知道你已經建立了索引。
換句話說,你需要在資料庫中執行一些操作,併為Django提供遷移操作來同步其內部狀態。為此,Django為我們提供了一個名為 SeparateDatabaseAndState的特殊遷移操作。這項操作並不為人所知,應該留到像這種特殊情況下使用。
編輯遷移要比從頭開始寫容易的多,因此,首先以通常的方式生成一個遷移:
這是Django生成的遷移內容,和之前一樣:
Django在欄位sold_at上生成了一個AlterField操作。該操作會建立一個索引並更新狀態。我們希望保留這個操作,但是在資料庫中提供一個不同的命令來執行。
同樣,要獲得該命令,請使用Django生成的SQL:
在合適的地方新增CONCURRENTKY關鍵字:
接著,編輯該遷移檔案,並使用SeparateDatabaseAndState來提供你修改過的SQL命令並執行:
遷移操作separate atabaseandstate接受2個操作列表:
1.state_operations是應用於內部模型狀態的操作。它們不會影響資料庫。
2.database_operations是應用於資料庫的操作。
你在state_operations中保留了Django生成的原始操作。當使用SeparateDatabaseAndState時,這是你通常想要做的,注意向欄位提供db_index=True引數。這個遷移操作將讓Django知道欄位上有一個索引。
你使用了Django生成的SQL並新增了CONCURRENTLY關鍵字。你使用特殊的操作RunSQL來在遷移中執行原始SQL。
如果你試圖執行此遷移,你將獲得以下輸出:
非原子遷移
在SQL中,CREATE、DROP、ALTER和TRUNCATE操作稱為資料定義語言(Data Definition Language, DDL)。在支援事務性DDL的資料庫中,比如PostgreSQL,Django預設會在資料庫事務中執行遷移。然而,根據上面的錯誤,PostgreSQL不能在事務塊中併發地建立索引。
為了能夠在遷移中併發地建立索引,你需要告訴Django不要在資料庫事務中執行遷移。為此,透過將atomic設定為False,將遷移標記為非原子(non-atomic):
將遷移標記為非原子之後,你可以執行遷移了:
你只是執行了遷移,並沒有引起任何停機。
下面是使用SeparateDatabaseAndState時需要考慮的一些問題
資料庫操作必須與狀態操作等價:資料庫和模型狀態之間的不一致可能會造成很多麻煩。一個好的開始是保留Django在 state_operations中生成的操作和編輯sqlmigrate的輸出並在database_operations中使用。
非原子遷移在發生錯誤時不能回滾:如果在遷移過程中出現錯誤,則無法回滾。你必須要麼回滾遷移,要麼手動完成它。將在非原子遷移中執行的操作保持在最少是一個好主意。如果你在遷移中有其他操作,請將它們移到新的遷移中。
遷移可能是特定於供應商的:Django生成的SQL特定於專案中使用的資料庫後端。它可能會在其他資料庫後端執行,但這並不能保證。如果你需要支援多個資料庫後端,則需要對這種方法進行一些調整。
結論:
你從一個大型的資料表和一個問題開始了本教程。你想讓你的應用程式對使用者來說更快,你想在不引起應用程式任何停機的情況下做到這一點。
在本教程的最後,你嘗試生成並安全地修改了一個Django遷移來實現這一目標。在此過程中,你遇到了不同的問題,並使用migration 框架提供的內建工具設法解決了這些問題。
在本教程中,你學習了以下內容:
Django遷移在內部如何使用模型和資料庫狀態進行工作,以及何時生成新的遷移;
如何使用RunSQL操作在遷移中執行自定義的SQL;
什麼是可逆遷移,以及如何使RunSQL操作可逆;
什麼是原子遷移,以及如何根據需要更改預設行為;
如何安全地在Django中執行復雜的遷移。
模型與資料庫狀態的分離是一個重要的概念。一旦你理解了它,以及知道如何使用它,你就可以突破內建遷移操作的許多限制。我想到的一些用例包括新增已經在資料庫中建立的索引,以及為DDL命令提供特定的服務商引數。
python學習網,大量的免費,歡迎線上學習!
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4548/viewspace-2835651/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 不停機條件下部署 Django 應用Django
- 有限狀態機(FSM)的使用
- 使用NAS動態儲存卷建立有狀態應用
- 狀態機
- PHP 有限狀態機使用說明PHP
- 【iCore4 雙核心板_FPGA】例程七:狀態機實驗——狀態機使用FPGA
- 【iCore3 雙核心板_FPGA】例程九:狀態機實驗——狀態機使用FPGA
- 前端狀態管理與有限狀態機前端
- Django REST framework API 指南(25):狀態碼DjangoRESTFrameworkAPI
- 【iCore1S 雙核心板_FPGA】例程六:狀態機實驗——狀態機使用FPGA
- 使用 Provider 管理 Flutter 應用狀態 (下)IDEFlutter
- 從工作流狀態機實踐中總結狀態模式使用心得模式
- react 狀態機管理React
- 狀態機設計
- 程式的建立和程式的狀態
- oracle 巢狀表 索引表 使用Oracle巢狀索引
- 基於函式的索引狀態變化函式索引
- 使用列舉實現狀態機來優雅你的狀態變更邏輯
- 狀態機解決複雜邏輯及使用
- 使用有限狀態機原理實現英文分詞分詞
- C#狀態機StatelessC#
- JavaScript與有限狀態機JavaScript
- 狀態機工作流
- RMAN不停機搭建DG
- Django使用終端建立superuser報錯。Django
- 5種狀況下的HTTP狀態碼HTTP
- 使用Elasticsearch的動態索引和索引優化Elasticsearch索引優化
- django下載excel,使用django-excel外掛DjangoExcel
- Linux下用netstat檢視網路狀態、埠狀態Linux
- django怎麼建立超級使用者Django
- Angular狀態管理的使用Angular
- concent 騷操作之元件建立&狀態更新元件
- 理解alivc_framework狀態機Framework
- 玩轉Spring狀態機Spring
- Unity——有限狀態機FSM修改Unity
- Unity/C# 有限狀態機UnityC#
- 抽象的藝術-狀態機抽象
- 不停機 data guard 以及 switchover