My favorites | 中文(繁體) | Sign in
英文版或許有比此中譯版新的內容

交易

「應用服務引擎」資料存放區支援「交易」功能。交易是一種 (或一組) 全部成功或全部失敗的操作。應用程式可以在單次交易中執行多項操作與計算。

使用交易

交易是一個資料存放區操作或一組資料存放區操作,這是全部成功或全部失敗的一種操作。如交易成功,資料存放區將套用所有的預期效果。如交易失敗,則不會套用任何效果。

每個資料存放區的寫入操作都是單一性的。因此操作不是嘗試建立、更新或刪除實體,就是完全不做嘗試。操作可能會因為爭用資源的頻率太高 (太多使用者同時嘗試修改實體) 而失敗。操作也可能會因為應用程式達到配額上限,或是資料存放區發生內容錯誤而失敗。在這些情況下,操作的效果將無法套用,且 Datastore API (資料存放區 API) 將引發例外狀況。

應用程式可以在單一交易中執行一組陳述式和資料存放區操作,因此若任何陳述式或操作引發例外狀況,則整組中的任何資料存放區操作都不會套用。應用程式使用 Python 定義交易中要執行的動作,然後呼叫 db.run_in_transaction() 以函式做為引數:

from google.appengine.ext import db

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

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() 會傳回該值。

若函式傳回,則表示交易已認可,而資料存放區操作的所有效果都會套用。若函式引發例外狀況,則會「復原」交易,而不會套用任何效果。

若函式引發 Rollback 例外狀況,db.run_in_transaction() 會傳回 None。 若有其他任何例外狀況,則 db.run_in_transaction() 會再次引發例外狀況。

交易中可以完成什麼

資料存放區針對單一交易中可完成的事項有一些限制。

交易中的所有資料存放區的操作對象必須是相同實體群組中的實體。這些操作包括使用金鑰擷取實體、更新實體以及刪除實體。請注意,每個根實體均屬於個別的實體群組,因此單一交易不能建立或操作超過一個的根實體。 如需實體群組的詳細說明,請參閱「金鑰和實體群組」。

應用程式在交易時無法執行查詢。不過,應用程式在交易時可以使用金鑰擷取資料存放區實體,且所擷取的實體與交易的其他部分絕對一致。您可以在交易前預先準備金鑰,或在交易中使用金鑰名稱或 ID 建立金鑰。

在單一交易中,應用程式只能建立或更新實體一次。

所有其他 Python 程式碼都可以在交易函式中使用。除了資料存放區操作之外,交易函式不應有其他副作用。若因為其他使用者同時在實體群組中更新實體,而導致資料存放區操作失敗,則交易函式可能被呼叫多次。發生此情況時,Datastore API (資料存放區 API) 會重試交易 (一定次數)。若全都失敗,則 db.run_in_transaction() 會引發 TransactionFailedError。您可以使用 db.run_in_transaction_custom_retries()調整交易重試的次數,而不使用 db.run_in_transaction()。

同樣地,交易函式不應有取決於交易的成功與否的副作用,除非呼叫交易函式的程式碼知道如何復原那些效果。例如,若交易儲存新的資料存放區實體,儲存建立的實體 ID 供後續使用,但是交易失敗,則已儲存的 ID 不會參考原本指向的實體 (因為實體的建立會被復原)。在此情況下,呼叫程式碼要小心,不要用到已經儲存的 ID。

交易的用途

這個範例將說明交易的用途之一:以新的屬性值 (與目前值相關的值) 更新實體。

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

在程式碼擷取物件到儲存修改的物件的這段時間內,其他使用者可能會更新這個值,因此必須進行交易。 如果沒有交易,則使用者的要求將採用其他使用者更新之前的 counter 值,而儲存的動作也會覆寫更新的值。但是藉由交易,應用程式將取得其他使用者的更新。 如果實體在交易期間更新,則系統會重試交易,直到所有步驟不受中斷地完成。

另一個常見的交易用途是透過具名金鑰更新實體,或在實體不存在的狀況下建立實體:

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

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

  obj.put()

如上所述,出現其他的使用者試圖以相同的字串 ID 建立或更新實體時,即必須進行交易。當兩位使用者嘗試建立目前不存在的實體時,如果沒有交易,則第二位使用者會在不知情的狀況下,覆寫掉第一位使用者所建立的實體。 反之,如果使用交易,則第二位使用者必須重試,並發現實體目前已經存在,轉而更新實體。

「建立或更新」的方式很有用,因此有一個內建的方法來完成這件事:Model.get_or_insert() 會將金鑰名稱、選用的父系以及引數,傳遞給模型建構函式 (若該名稱和路徑的實體不存在的話)。「取得」嘗試和「建立」發生在一個交易中,因此 (若交易成功) 方法會傳回代表實際實體的模型實例。

提示:交易發生時,應儘快完成,以減少交易所使用的實體發生變更的機會。一旦發生變更,就需要重試交易。因為資料存放區操作需要一致不變的操作環境,因此請儘可能在先交易之外準備資料,然後再執行交易以執行資料存放區操作。應用程式必須為交易中使用的物件準備金鑰,再擷取交易中的實體。