交易

附註:強烈建議建構新應用程式的開發人員使用 NDB 用戶端程式庫;相較於此用戶端程式庫,NDB 用戶端程式庫有幾項優點,例如能透過 Memcache API 自動快取實體。如果您目前使用的是舊版 DB 用戶端程式庫,請參閱 DB 至 NDB 遷移指南

Cloud Datastore 支援「交易」。交易是一項或一組不可分割的操作,這表示交易中的操作必須全部完成,或是全部不進行。應用程式可以在一次交易中執行多項操作與計算。

使用交易

「交易」是指在一或多個實體上的一組 Cloud Datastore 操作。每次交易皆保證具有單元性,這代表交易絕不會部分完成。不是交易中的操作全部完成,就是全部不予進行。交易的最長持續時間為 60 秒,而在 30 秒後則有 10 秒的閒置到期時間。

如有以下情形,操作可能會失敗:

  • 同一個實體群組上嘗試進行太多並行修改。
  • 交易超過資源限制。
  • Cloud Datastore 遭遇內部錯誤。

在這些情況下,Cloud Datastore API 都會引發例外狀況。

交易是 Cloud Datastore 的選用功能;不必透過交易來執行 Cloud Datastore 操作。

應用程式可以在單一交易中執行一組陳述式和資料儲存操作。這樣一來,假如任何陳述式或操作引發例外狀況,則不會套用組合中的任何 Cloud Datastore 操作。應用程式使用 Python 函式定義要在交易中執行的操作。應用程式會使用其中一種 run_in_transaction 方法啟動交易,這取決於交易是存取單一實體群組中的實體,或是該交易為跨群組交易。

針對僅在交易中使用函式的常用情況,請使用 @db.transactional 修飾符:

from google.appengine.ext import db

class Accumulator(db.Model):
    counter = db.IntegerProperty(default=0)

@db.transactional
def increment_counter(key, amount):
    obj = db.get(key)
    obj.counter += amount
    obj.put()

q = db.GqlQuery("SELECT * FROM Accumulator")
acc = q.get()

increment_counter(acc.key(), 5)

如果在不含交易的情況下呼叫函式,請呼叫包含函式的 db.run_in_transaction() 做為引數 (而不是進行修飾):

from google.appengine.ext import db

class Accumulator(db.Model):
    counter = db.IntegerProperty(default=0)

def increment_counter(key, amount):
    obj = db.get(key)
    obj.counter += amount
    obj.put()

q = db.GqlQuery("SELECT * FROM Accumulator")
acc = q.get()

db.run_in_transaction(increment_counter, acc.key(), 5)

db.run_in_transaction() 會擷取函式物件,以及位置和關鍵字引數來傳送至函式。如果函式傳回一個值,則 db.run_in_transaction() 會傳回該值。

假如函式傳回值,則會送出交易,並且套用 Cloud Datastore 操作的所有效果。如果函式引發例外狀況,則交易會「復原」,而且不會套用效果。請參閱上述有關例外狀況的說明。

假如交易函式從另一個交易收到呼叫,@db.transactionaldb.run_in_transaction() 會有不同的預設行為。@db.transactional 將允許這個操作,並且內部交易變為與外部交易相同的交易。呼叫 db.run_in_transaction() 會嘗試在現有交易上「以巢狀方式嵌入」另一個交易;不過目前尚未支援這個行為且會引發 db.BadRequestError。您可以指定其他行為;詳情請參閱交易選項

使用跨群組 (XG) 交易

跨多個實體群組操作的跨群組交易行為類似於單一群組交易,但如果程式碼嘗試從一個以上的實體群組更新實體,則不會失敗。如要叫用跨群組交易,請使用交易選項。

使用 @db.transactional

from google.appengine.ext import db

@db.transactional(xg=True)
def make_things():
  thing1 = Thing(a=3)
  thing1.put()
  thing2 = Thing(a=7)
  thing2.put()

make_things()

使用 db.run_in_transaction_options

from google.appengine.ext import db

xg_on = db.create_transaction_options(xg=True)

def my_txn():
    x = MyModel(a=3)
    x.put()
    y = MyModel(a=7)
    y.put()

db.run_in_transaction_options(xg_on, my_txn)

在交易中可以完成什麼

Cloud Datastore 針對單一交易可執行的操作施加了限制。

假如交易是單一群組交易,則交易中的所有 Cloud Datastore 操作必須對同一個實體群組中的實體進行。假設交易為跨群組交易,則必須對最多 25 個實體群組中的實體進行操作。這些操作包括使用上階查詢實體、使用鍵擷取實體、更新實體以及刪除實體。請注意,每個根實體都屬於獨立的實體群組,因此除了跨群組交易,您無法在一個以上的根實體上建立或操作單一交易。

當兩個或多個交易同時嘗試修改一或多個共用實體群組中的實體時,只有提出修改的第一個交易才能成功;其他則會在提出時失敗。由於這項設計的緣故,使用實體群組會限制您可以對群組中任何實體執行的並行寫入次數。當交易開始時,Cloud Datastore 會透過檢查交易中使用實體群組的上次更新時間來使用最佳化並行控制。在為實體群組提出交易時,Cloud Datastore 會再次檢查交易中使用實體群組的上次更新時間。假如該值自初始檢查後已更改,則會擲回例外情況。如需實體群組的說明,請參閱 Cloud Datastore 總覽頁面。

應用程式只有在包含祖系篩選器的情況下可執行查詢。應用程式也可以在交易時,藉由鍵來取得資料儲存庫實體。您可以在交易之前先準備好「鍵」,或是可以在交易中藉由鍵名或 ID 來建立「鍵」。

交易函式內部允許其他 Python 程式碼。您可使用 db.is_in_transaction() 判定目前範圍是否嵌入交易函式中。除了 Cloud Datastore 操作,交易函式不得包含其他副作用。假如由於另一個使用者同時更新實體群組中的實體而導致 Cloud Datastore 操作失敗,則可以多次呼叫交易函式。發生這種情況時,Cloud Datastore API 會以固定次數重試該交易。如果全部失敗,db.run_in_transaction() 會引發 TransactionFailedError。您可以使用 db.run_in_transaction_custom_retries(),而不是「db.run_in_transaction()」,調整交易的嘗試次數。

同樣地,除非呼叫交易函式的程式碼知道撤銷這些影響,否則交易函式不得包含會依賴交易成功的副作用。舉例來說,假設交易會儲存新的 Cloud Datastore 實體、保留已建立的實體 ID 供之後使用。當交易失敗時,由於實體的建立作業已復原,保留的 ID 不會引用預定的實體。在這種情況下,呼叫程式碼必須小心不要使用保留的 ID。

隔離與一致性

在交易之外,Cloud Datastore 的隔離等級最接近於已提出的讀取。交易內部則會強制執行可序列化隔離。這代表另一個交易無法並行修改由這個交易讀取或修改的資料。如要進一步瞭解隔離等級,請參閱維基百科的可序列化隔離條目和交易隔離一文。

在交易中,所有讀取都會反映交易開始時 Cloud Datastore 的當前一致狀態。在交易開始時,交易內部的查詢和獲取一定可看到 Cloud Datastore 的單一且一致的快照。交易實體群組中的實體和索引列已完全更新,以便查詢傳回完整、正確的結果實體組合,而不會發生在交易隔離 (可能在交易之外的查詢中發生) 所述的誤判和漏判情形。

此一致性的快照檢視也擴充至交易內部的寫入後的讀取。與大部分資料庫不同,Cloud Datastore 交易內部的查詢和獲取「不會」看到先前在該交易中寫入的結果。尤其如果實體在交易內經過修改或刪除,查詢或取得 (get) 將傳回交易開始時的「原始」版實體,如果當時該實體尚不存在,則不傳回任何實體。

交易用途

這個範例將說明交易的一種用途:將實體更新為新的屬性值,而不是其目前的值。

def increment_counter(key, amount):
    obj = db.get(key)
    obj.counter += amount
    obj.put()

這需要一項交易,因為在此程式碼擷取物件之後 (但在儲存修改後的物件之前),其他使用者可能會更新該值。如果沒有交易,使用者的要求將在其他使用者更新之前運用 count 值,且儲存的內容會複寫新值。但是藉由交易,應用程式將得知其他使用者的更新。 假如實體在交易期間更新,則系統將重試交易,直到所有步驟不受中斷地完成。

交易的另一個常見用途是擷取具有指定鍵的實體,或是建立這個鍵 (假如鍵不存在):

class SalesAccount(db.Model):
    address = db.PostalAddressProperty()
    phone_number = db.PhoneNumberProperty()

def get_or_create(parent_key, account_id, address, phone_number):
    obj = db.get(db.Key.from_path("SalesAccount", account_id, parent=parent_key))
    if not obj:
        obj = SalesAccount(key_name=account_id,
                           parent=parent_key,
                           address=address,
                           phone_number=phone_number)
        obj.put()
    else:
        obj.address = address
        obj.phone_number = phone_number

和過去一樣,必須有一個交易用來處理另一個使用者嘗試建立或更新具有相同字串 ID 實體的情況。在沒有交易的情況下,假如實體不存在且兩個使用者嘗試建立該實體,則第二個實體會不知情地覆寫第一個實體。從另個角度看,假如交易存在,則第二個嘗試必須重試,並發現實體目前已經存在,轉而對實體進行更新。

當交易失敗時,您可以讓應用程式重試交易,直到成功為止,也可以將交易傳播至應用程式的使用者介面層級,讓使用者處理錯誤。您不必為每一個交易建立重試迴圈。

取得或建立操作非常實用,因此已有內建的方法:Model.get_or_insert() 會取得一個金鑰名稱、選用父項以及引數,並在該名稱和路徑的實體不存在時將這些內容傳遞給模型建構函式。取得嘗試和建立都發生在一個交易中,因此 (假設交易成功) 方法將傳回代表真正實體的模型實例。

最後,您可以使用交易來讀取 Cloud Datastore 的一致快照。當需要多個讀取取得以轉譯網頁,或是匯出必須是一致的資料時,這項功能相當有用。由於這種交易不執行寫入,所以通常稱為「唯讀」交易。單一群組唯讀交易一律不會因並行修改而失敗,因此您不必在失敗時重試。不過,跨群組交易可能由於並行修改而失敗,因此必須進行重試。認可和回復唯讀交易都是免人工管理的。

class Customer(db.Model):
    user = db.StringProperty()

class Account(db.Model):
    """An Account has a Customer as its parent."""
    address = db.PostalAddressProperty()
    balance = db.FloatProperty()

def get_all_accounts():
    """Returns a consistent view of the current user's accounts."""
    accounts = []
    for customer in Customer.all().filter('user =', users.get_current_user().user_id()):
        accounts.extend(Account.all().ancestor(customer))
    return accounts

交易作業佇列

您可以將工作排入佇列做為 Cloud Datastore 交易的一部分,以便只有在成功提出交易時才會將工作排入佇列。如果未提出交易,則工作不會排入佇列。假如已確實提出交易,則工作就會排入佇列。加入佇列後,工作將不會立即執行,因此工作並非交易的單元性內容。不過,新增工作至佇列後,該工作將不斷重試直到成功為止。這適用於 run_in_transaction() 函式執行期間,任何排入佇列的工作。

交易工作允許您將非 Cloud Datastore 操作結合至依賴於交易成功 (例如發送電子郵件以確認購買) 的交易,因此非常實用。您還可以將 Cloud Datastore 操作與交易綁定。例如,只有在交易成功時,於交易之外提出對實體群組的更改。

在單一交易期間,應用程式無法將五個以上的交易工作插入工作佇列中。交易工作不能有使用者指定的名稱。

def do_something_in_transaction(...)
    taskqueue.add(url='/path/to/my/worker', transactional=True)
  ...

db.run_in_transaction(do_something_in_transaction, ....)
本頁內容對您是否有任何幫助?請提供意見:

傳送您對下列選項的寶貴意見...

這個網頁
Python 2 適用的 App Engine 標準環境