溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Django在不停機(jī)的情況下創(chuàng)建索引的方法

發(fā)布時間:2020-08-03 09:01:47 來源:億速云 閱讀:286 作者:小豬 欄目:開發(fā)技術(shù)

這篇文章主要講解了Django在不停機(jī)的情況下創(chuàng)建索引的方法,內(nèi)容清晰明了,對此有興趣的小伙伴可以學(xué)習(xí)一下,相信大家閱讀完之后會有幫助。

該框架在管理數(shù)據(jù)庫更改方面非常強(qiáng)大和有用,但是該框架提供的靈活性受到了一定的限制。為了理解Django遷移的局限性,你將處理一個眾所周知的問題:在不停機(jī)的情況下,在Django中創(chuàng)建一個索引。

在本教程中,你將學(xué)習(xí):

Django如何以及何時生成新的遷移;

如何檢查Django生成的執(zhí)行遷移的命令;

如何安全地修改遷移以滿足你的需求。

本中級教程是為已經(jīng)熟悉Django遷移(Migration)的讀者設(shè)計的。

在Django遷移中創(chuàng)建索引的問題

當(dāng)應(yīng)用程序存儲的數(shù)據(jù)增長時,通常需要進(jìn)行的一個常見更改就是添加索引。索引可以用來加快查詢速度,并使你的應(yīng)用程序運(yùn)行和響應(yīng)更快。

在大多數(shù)數(shù)據(jù)庫中,添加索引時需要對表使用獨(dú)占鎖。在創(chuàng)建索引時,獨(dú)占鎖會防止數(shù)據(jù)修改(DML)操作,如UPDATE,INSERT,和DELETE。

數(shù)據(jù)庫在執(zhí)行某些操作時會隱式地獲取鎖。例如,當(dāng)用一個戶登錄到你的應(yīng)用程序時,Django將更新auth_user表中的last_login字段。要執(zhí)行更新,數(shù)據(jù)庫首先必須在這個行上獲得一個鎖。如果該行當(dāng)前被另一個連接鎖定,那么你會得到一個數(shù)據(jù)庫異常。

當(dāng)需要在遷移期間保持系統(tǒng)可用時,鎖定表可能會造成問題。表越大,創(chuàng)建索引所需的時間就越長。創(chuàng)建索引所需的時間越長,系統(tǒng)不可用或?qū)τ脩魺o響應(yīng)的時間就越長。

一些數(shù)據(jù)庫供應(yīng)商提供了一種創(chuàng)建索引而不鎖定表的方法。例如,要在PostgreSQL中創(chuàng)建索引而不鎖定表,你可以使用CONCURRENTLY關(guān)鍵字:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

在Oracle中,有一個ONLINE選項允許在創(chuàng)建索引時對表執(zhí)行DML操作:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

在生成遷移時,Django不會使用這些特殊的關(guān)鍵字。按原樣運(yùn)行遷移將使數(shù)據(jù)庫獲得表上的獨(dú)占鎖,并在創(chuàng)建索引時防止DML操作。

并發(fā)創(chuàng)建索引有一些注意事項。提前了解特定于數(shù)據(jù)庫后端的問題是很重要的。例如,PostgreSQL中的一個警告是并發(fā)創(chuàng)建索引需要更長的時間,因為它需要進(jìn)行額外的表掃描。

在本教程中,你將使用Django遷移在一個大型表上創(chuàng)建索引,而不會導(dǎo)致任何停機(jī)。

注意:要學(xué)習(xí)本教程,建議你使用PostgreSQL后端,Django2.x和python3。

也可以使用其他數(shù)據(jù)庫后端。在使用PostgreSQL特有的SQL特性的地方,更改SQL以匹配你的數(shù)據(jù)庫后端。

設(shè)置

你將在一個名為app的應(yīng)用中使用一個虛構(gòu)的Sale模型。在現(xiàn)實生活中,Sale等模型是數(shù)據(jù)庫中的主要表,它們通常會非常大,并存儲大量數(shù)據(jù):

Django在不停機(jī)的情況下創(chuàng)建索引的方法

創(chuàng)建表,生成初始遷移并應(yīng)用它:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

一段時間之后,sales表變得非常大,用戶開始抱怨速度太慢。在監(jiān)視數(shù)據(jù)庫時,你注意到許多查詢使用sold_at列。為了加快速度,你決定在列上需要一個索引。

要在sold_at上添加索引,你需要對模型進(jìn)行以下更改:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

如果按原樣運(yùn)行這個遷移,那么Django將在表上創(chuàng)建索引,并且它將被鎖定,直到索引完成。在非常大的表上創(chuàng)建索引可能需要一段時間,你希望避免停機(jī)。

在具有小數(shù)據(jù)集和很少連接的本地開發(fā)環(huán)境中,這種遷移可能是瞬間完成的。然而,對于具有許多并發(fā)連接的大型數(shù)據(jù)集,獲取鎖并創(chuàng)建索引可能需要一段時間。

在接下來的步驟中,你將修改Django創(chuàng)建的遷移,以便在不引起任何停機(jī)的情況下創(chuàng)建索引。

偽造遷移

第一種方法是手動創(chuàng)建索引。你將生成遷移,但實際上并不會讓Django應(yīng)用它。相反,你將在數(shù)據(jù)庫中手動運(yùn)行SQL,然后讓Django認(rèn)為遷移已經(jīng)完成。

首先,生成遷移:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

使用sqlmigrate命令來查看Django將用于執(zhí)行此遷移的SQL:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

你希望在不鎖定表的情況下創(chuàng)建索引,因此你需要修改命令。添加CONCURRENTLY關(guān)鍵字并在數(shù)據(jù)庫中執(zhí)行:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

注意,你在執(zhí)行命令的過程中沒有BEGIN和COMMIT部分。省略這些關(guān)鍵字會在沒有數(shù)據(jù)庫事務(wù)的情況下執(zhí)行命令。我們將在本文后面討論數(shù)據(jù)庫事務(wù)。

執(zhí)行命令后,如果你嘗試應(yīng)用遷移,會出現(xiàn)以下錯誤:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

Django會提示你該索引已經(jīng)存在,因此無法繼續(xù)遷移。你剛剛在數(shù)據(jù)庫中直接創(chuàng)建了索引,所以現(xiàn)在需要讓Django認(rèn)為已經(jīng)應(yīng)用了遷移。

如何偽造一個遷移

Django提供了一種內(nèi)置的方法,可以將遷移標(biāo)記為已執(zhí)行,而不需要實際執(zhí)行它們。要使用這個選項,你需要在應(yīng)用遷移時設(shè)置—fake標(biāo)志:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

這一次Django沒有拋出錯誤。實際上,Django并沒有真正應(yīng)用任何遷移。它只是將其標(biāo)記為已執(zhí)行(或FAKED)。

以下是在偽造遷移時需要考慮的一些問題:

手動命令必須與Django生成的SQL等價: 你需要確保所執(zhí)行的命令與Django生成的SQL等價。使用sqlmigrate來生成SQL命令。如果命令不匹配,則可能導(dǎo)致數(shù)據(jù)庫和模型狀態(tài)之間的不一致。

其他未應(yīng)用的遷移也將被偽造:當(dāng)你有多個未應(yīng)用的遷移時,它們都將被偽造。在應(yīng)用遷移之前,重要的是確保只有你想要偽造的遷移沒有應(yīng)用。否則,你可能會得到不一致的結(jié)果。另一個選項是指定要偽造的確切遷移。

需要直接訪問數(shù)據(jù)庫:你需要在數(shù)據(jù)庫中運(yùn)行SQL命令,這有時也不是必需的。此外,在生產(chǎn)數(shù)據(jù)庫中直接執(zhí)行命令是危險的,應(yīng)該盡可能避免。

自動化部署流程可能需要調(diào)整:如果你自動化了部署流程(使用CI、CD或其他自動化工具),那么你可能需要將流程更改為偽遷移。這并不總是可取的。

清理

在繼續(xù)下一節(jié)之前,你需要將數(shù)據(jù)庫恢復(fù)到它在初始遷移之后的狀態(tài)。要做到這一點(diǎn),請遷移回初始遷移:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

Django沒有應(yīng)用在第二次遷移中所做的更改,所以現(xiàn)在可以安全地刪除文件:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

為了確保你做的一切都是正確的,檢查一下遷移:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

應(yīng)用了初始遷移之后,就沒有未應(yīng)用的遷移了。

在遷移(Migration)中執(zhí)行原始SQL

在上一節(jié)中,你直接在數(shù)據(jù)庫中執(zhí)行SQL并偽造遷移。這樣就完成了任務(wù),但是還有一個更好的解決方案。

Django提供了一種使用RunSQL在遷移中執(zhí)行原始SQL的方法。我們來嘗試使用它代替直接在數(shù)據(jù)庫中執(zhí)行命令。

首先,生成一個新的空遷移:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

接下來,編輯遷移文件并添加RunSQL操作:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

當(dāng)你運(yùn)行遷移時,你將獲得以下輸出:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

這看起來不錯,但有一個問題。我們再次來嘗試生成遷移:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

Django再次生成了相同的遷移。為什么會這樣?

清理

在回答這個問題之前,你需要清理并撤消對數(shù)據(jù)庫所做的更改。首先刪除最后一次遷移。它沒有被應(yīng)用,所以可以安全刪除:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

接下來,列出app應(yīng)用程序的遷移:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

第三次遷移已經(jīng)結(jié)束,但是只應(yīng)用了第二次遷移。你希望回到初始遷移之后的狀態(tài)。試著像你在上一節(jié)所做的那樣遷移回初始遷移狀態(tài):

Django在不停機(jī)的情況下創(chuàng)建索引的方法

Django無法進(jìn)行逆向遷移。

逆向遷移操作

要進(jìn)行一次逆向遷移,Django會對每個操作執(zhí)行相反的操作。在本例中,添加索引的反面是刪除索引。正如你已經(jīng)看到的,當(dāng)一個遷移是可逆的時,你可以取消應(yīng)用它。就像你可以在Git中使用checkout一樣,如果你對較早的遷移執(zhí)行了migrate命令,你可以進(jìn)行逆向遷移。

許多內(nèi)置遷移操作已經(jīng)定義了反向操作。例如,添加字段的反向操作是刪除對應(yīng)的列,創(chuàng)建模型的反向操作是刪除相應(yīng)的表。

有些遷移操作是不可逆的。例如,刪除字段或刪除模型沒有反向操作,因為一旦應(yīng)用了遷移,數(shù)據(jù)就會消失。

在上一節(jié)中,你使用了RunSQL操作。當(dāng)你試圖進(jìn)行反向遷移時,遇到了一個錯誤。根據(jù)錯誤提示,遷移中的一個操作不能逆轉(zhuǎn)。Django默認(rèn)情況下無法反轉(zhuǎn)原始SQL。因為Django不知道該操作執(zhí)行了什么,所以它不能自動生成相反的操作。

如何使遷移可逆

要使一個遷移是可逆的,遷移中的所有操作都必須是可逆的。只逆轉(zhuǎn)遷移的一部分是不可能的,因此一個單一的不可逆操作將使整個遷移不可逆。

要使RunSQL操作可逆,你必須提供在操作反轉(zhuǎn)時執(zhí)行的SQL。反向SQL在reverse_sql參數(shù)中提供。

添加索引的相反操作是刪除索引。要使你的遷移可逆,請?zhí)峁﹔everse_sql參數(shù)來刪除索引:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

現(xiàn)在試著反轉(zhuǎn)遷移:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

我們對第二次遷移進(jìn)行了反轉(zhuǎn),Django刪除了索引?,F(xiàn)在可以安全地刪除遷移文件了:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

提供reverse_sql總是一個好主意。在反轉(zhuǎn)原始SQL操作而不需要其他任何操作的情況下,你可以使用特殊的哨兵語句migrations.

RunSQL.noop將該操作標(biāo)記為可逆操作。

Django在不停機(jī)的情況下創(chuàng)建索引的方法

理解模型狀態(tài)和數(shù)據(jù)庫狀態(tài)

在你之前嘗試使用RunSQL手動創(chuàng)建索引時,Django一次又一次地生成了相同的遷移,盡管索引是在數(shù)據(jù)庫中創(chuàng)建的。要理解Django為什么這樣做,你首先需要理解Django如何決定何時生成新的遷移。

當(dāng)Django生成一個新的遷移時

在生成和應(yīng)用遷移的過程中,Django同步數(shù)據(jù)庫狀態(tài)和模型狀態(tài)。例如,當(dāng)你向模型添加字段時,Django會向表添加一列。當(dāng)你從模型中刪除字段時,Django將從表中刪除列。

為了在模型和數(shù)據(jù)庫之間同步,Django擁有一個表示模型的狀態(tài)。為了使數(shù)據(jù)庫與模型同步,Django會生成遷移操作。遷移操作轉(zhuǎn)換為可以在數(shù)據(jù)庫中執(zhí)行的特定供應(yīng)商的SQL。當(dāng)所有遷移操作都執(zhí)行后,數(shù)據(jù)庫和模型應(yīng)該是一致的。

為了獲得數(shù)據(jù)庫的狀態(tài),Django聚合了所有過去遷移的操作。當(dāng)遷移的聚合狀態(tài)與模型的狀態(tài)不一致時,Django會生成一個新的遷移。

在前面的例子中,你使用原始SQL創(chuàng)建了索引。Django不知道你創(chuàng)建了索引,因為你沒有使用熟悉的遷移操作。

當(dāng)Django聚合所有遷移并將它們與模型的狀態(tài)進(jìn)行比較時,它發(fā)現(xiàn)缺少一個索引。這就是為什么即使你手動創(chuàng)建了索引,Django仍然認(rèn)為它是缺失的,并為它生成了一個新的遷移。

如何在遷移中分離數(shù)據(jù)庫和狀態(tài)

由于Django無法按照你希望的方式創(chuàng)建索引,所以你希望提供自己的SQL,但仍然要讓Django知道你已經(jīng)創(chuàng)建了索引。

換句話說,你需要在數(shù)據(jù)庫中執(zhí)行一些操作,并為Django提供遷移操作來同步其內(nèi)部狀態(tài)。為此,Django為我們提供了一個名為 SeparateDatabaseAndState的特殊遷移操作。這項操作并不為人所知,應(yīng)該留到像這種特殊情況下使用。

編輯遷移要比從頭開始寫容易的多,因此,首先以通常的方式生成一個遷移:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

這是Django生成的遷移內(nèi)容,和之前一樣:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

Django在字段sold_at上生成了一個AlterField操作。該操作會創(chuàng)建一個索引并更新狀態(tài)。我們希望保留這個操作,但是在數(shù)據(jù)庫中提供一個不同的命令來執(zhí)行。

同樣,要獲得該命令,請使用Django生成的SQL:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

在合適的地方添加CONCURRENTKY關(guān)鍵字:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

接著,編輯該遷移文件,并使用SeparateDatabaseAndState來提供你修改過的SQL命令并執(zhí)行:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

遷移操作separate atabaseandstate接受2個操作列表:

1.state_operations是應(yīng)用于內(nèi)部模型狀態(tài)的操作。它們不會影響數(shù)據(jù)庫。

2.database_operations是應(yīng)用于數(shù)據(jù)庫的操作。

你在state_operations中保留了Django生成的原始操作。當(dāng)使用SeparateDatabaseAndState時,這是你通常想要做的,注意向字段提供db_index=True參數(shù)。這個遷移操作將讓Django知道字段上有一個索引。

你使用了Django生成的SQL并添加了CONCURRENTLY關(guān)鍵字。你使用特殊的操作RunSQL來在遷移中執(zhí)行原始SQL。

如果你試圖運(yùn)行此遷移,你將獲得以下輸出:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

非原子遷移

在SQL中,CREATE、DROP、ALTER和TRUNCATE操作稱為數(shù)據(jù)定義語言(Data Definition Language, DDL)。在支持事務(wù)性DDL的數(shù)據(jù)庫中,比如PostgreSQL,Django默認(rèn)會在數(shù)據(jù)庫事務(wù)中執(zhí)行遷移。然而,根據(jù)上面的錯誤,PostgreSQL不能在事務(wù)塊中并發(fā)地創(chuàng)建索引。

為了能夠在遷移中并發(fā)地創(chuàng)建索引,你需要告訴Django不要在數(shù)據(jù)庫事務(wù)中執(zhí)行遷移。為此,通過將atomic設(shè)置為False,將遷移標(biāo)記為非原子(non-atomic):

Django在不停機(jī)的情況下創(chuàng)建索引的方法

將遷移標(biāo)記為非原子之后,你可以運(yùn)行遷移了:

Django在不停機(jī)的情況下創(chuàng)建索引的方法

你只是執(zhí)行了遷移,并沒有引起任何停機(jī)。

下面是使用SeparateDatabaseAndState時需要考慮的一些問題

數(shù)據(jù)庫操作必須與狀態(tài)操作等價:數(shù)據(jù)庫和模型狀態(tài)之間的不一致可能會造成很多麻煩。一個好的開始是保留Django在 state_operations中生成的操作和編輯sqlmigrate的輸出并在database_operations中使用。

非原子遷移在發(fā)生錯誤時不能回滾:如果在遷移過程中出現(xiàn)錯誤,則無法回滾。你必須要么回滾遷移,要么手動完成它。將在非原子遷移中執(zhí)行的操作保持在最少是一個好主意。如果你在遷移中有其他操作,請將它們移到新的遷移中。

遷移可能是特定于供應(yīng)商的:Django生成的SQL特定于項目中使用的數(shù)據(jù)庫后端。它可能會在其他數(shù)據(jù)庫后端運(yùn)行,但這并不能保證。如果你需要支持多個數(shù)據(jù)庫后端,則需要對這種方法進(jìn)行一些調(diào)整。

結(jié)論:

你從一個大型的數(shù)據(jù)表和一個問題開始了本教程。你想讓你的應(yīng)用程序?qū)τ脩魜碚f更快,你想在不引起應(yīng)用程序任何停機(jī)的情況下做到這一點(diǎn)。

在本教程的最后,你嘗試生成并安全地修改了一個Django遷移來實現(xiàn)這一目標(biāo)。在此過程中,你遇到了不同的問題,并使用migration 框架提供的內(nèi)置工具設(shè)法解決了這些問題。

在本教程中,你學(xué)習(xí)了以下內(nèi)容:

  • Django遷移在內(nèi)部如何使用模型和數(shù)據(jù)庫狀態(tài)進(jìn)行工作,以及何時生成新的遷移;
  • 如何使用RunSQL操作在遷移中執(zhí)行自定義的SQL;
  • 什么是可逆遷移,以及如何使RunSQL操作可逆;
  • 什么是原子遷移,以及如何根據(jù)需要更改默認(rèn)行為;
  • 如何安全地在Django中執(zhí)行復(fù)雜的遷移。

模型與數(shù)據(jù)庫狀態(tài)的分離是一個重要的概念。一旦你理解了它,以及知道如何使用它,你就可以突破內(nèi)置遷移操作的許多限制。我想到的一些用例包括添加已經(jīng)在數(shù)據(jù)庫中創(chuàng)建的索引,以及為DDL命令提供特定的服務(wù)商參數(shù)。

看完上述內(nèi)容,是不是對Django在不停機(jī)的情況下創(chuàng)建索引的方法有進(jìn)一步的了解,如果還想學(xué)習(xí)更多內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI