Transazioni

Nota: Gli sviluppatori che creano nuove applicazioni sono vivamente incoraggiati a utilizzare il Libreria client NDB, che offre numerosi vantaggi rispetto a questa libreria client, come la memorizzazione automatica nella cache delle entità tramite API. Se al momento utilizzi la libreria client DB precedente, leggi il Guida alla migrazione da database a NDB

Datastore supporta le transazioni. Una transazione è un'operazione insieme di operazioni atomiche, ovvero tutte le operazioni della transazione o non avviene nessuno di questi. Un'applicazione può eseguire più operazioni i calcoli in un'unica transazione.

Utilizzo delle transazioni

Una transazione è un insieme di operazioni Datastore su una o più le entità. Ogni transazione è garantita come atomica, il che significa che le transazioni non vengono mai applicate parzialmente. Qualsiasi operazione nel transazioni applicate o nessuna di queste viene applicata. Le transazioni hanno un durata massima di 60 secondi con una scadenza di inattività di 10 secondi dopo 30 secondi secondi.

Un'operazione potrebbe non riuscire quando:

  • Sono state tentate troppe modifiche simultanee nello stesso gruppo di entità.
  • La transazione supera un limite di risorse.
  • Datastore rileva un errore interno.

In tutti questi casi, l'API Datastore genera un'eccezione.

Le transazioni sono una funzionalità facoltativa di Datastore; non sei necessarie per utilizzare le transazioni per eseguire operazioni di Datastore.

Un'applicazione può eseguire un insieme di istruzioni e operazioni di datastore singola transazione, in modo tale che se una qualsiasi istruzione o operazione genera un'eccezione, non viene applicata nessuna delle operazioni Datastore nel set. La un'applicazione definisce le azioni da eseguire nella transazione utilizzando personalizzata. L'applicazione avvia la transazione utilizzando uno dei run_in_transaction di metodi, a seconda che la transazione acceda o meno entità all'interno di un singolo gruppo di entità o se la transazione è una transazione tra gruppi.

Per il caso d'uso comune di una funzione utilizzata solo all'interno delle transazioni, utilizza il decoratore di @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)

Se a volte la funzione viene chiamata senza una transazione, invece di decorarlo, chiama db.run_in_transaction() con la funzione come argomento:

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() prende l'oggetto funzione, mentre posizionale e la parola chiave da passare alla funzione. Se la funzione restituisce un valore, db.run_in_transaction() restituisce quel valore.

Se la funzione restituisce, viene eseguito il commit della transazione e tutti gli effetti della Vengono applicate le operazioni Datastore. Se la funzione genera un'eccezione, la transazione viene "rollback", e gli effetti non vengono applicati. Consulta la nota riportata sopra sulle eccezioni.

Quando una funzione di transazione viene richiamata dall'interno di un'altra transazione, @db.transactional e db.run_in_transaction() hanno un comportamento predefinito diverso. @db.transactional consentirà questa operazione, e la transazione interna diventerà la stessa della transazione esterna. Chiamata a db.run_in_transaction() tenta di "nest" un'altra transazione all'interno della transazione esistente; ma questo comportamento non è ancora supportato e genera un aumento di db.BadRequestError. Puoi specificare un altro comportamento; consulta il riferimento alle funzioni nelle opzioni di transazione per maggiori dettagli.

Utilizzo delle transazioni tra gruppi (XG)

Le transazioni tra gruppi, che operano su più gruppi di entità, si comportano come le transazioni di singoli gruppi, ma non avranno esito negativo se il codice tenta di aggiornare di entità da più gruppi di entità. Per richiamare una transazione tra gruppi: utilizzare le opzioni di transazione.

Utilizzo di @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()

Utilizzo di 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)

Operazioni consentite in una transazione

Datastore impone delle restrizioni su ciò che può essere fatto all'interno di una singola transazione.

Tutte le operazioni Datastore in una transazione devono operare nello stesso file gruppo di entità se si tratta di una transazione a un gruppo o su entità in un massimo di venticinque gruppi di entità se la transazione è tra gruppi. Questo include l'esecuzione di query sulle entità per predecessore, il recupero delle entità per chiave, l'aggiornamento entità e l'eliminazione di entità. Nota che ogni entità base appartiene a un un gruppo di entità separato, quindi una singola transazione non può creare o operare su più di un'entità base, a meno che non si tratti di una transazione tra gruppi.

Quando due o più transazioni tentano contemporaneamente di modificare entità in una o più gruppi di entità comuni, solo la prima transazione a cui viene eseguito il commit delle modifiche possono avere successo; tutti gli altri avranno esito negativo al momento del commit. Grazie a questa progettazione, l'uso I gruppi di entità limitano il numero di scritture simultanee che puoi eseguire su qualsiasi entità in gruppi. Quando inizia una transazione, Datastore utilizza controllo ottimistico della contemporaneità controllando l'ora dell'ultimo aggiornamento dei gruppi di entità utilizzati nella transazione. Dopo il commit di una transazione per i gruppi di entità, Datastore nuovamente controlla l'ora dell'ultimo aggiornamento dei gruppi di entità utilizzati nella transazione. Se è cambiato rispetto al controllo iniziale viene generata un'eccezione .

Un'app può eseguire una query durante una transazione, ma solo se include un filtro dei predecessori. Un'app può anche ottenere entità Datastore per chiave durante una transazione. Puoi preparare le chiavi prima della transazione oppure puoi creare chiavi all'interno della transazione con i relativi nomi o ID.

Tutto il resto del codice Python è consentito all'interno di una funzione di transazione. Puoi determina se l'ambito attuale è nidificato in una funzione di transazione utilizzando db.is_in_transaction(). La funzione di transazione non deve avere effetti collaterali diversi dal Operazioni di Datastore. La funzione di transazione può essere chiamata più volte se un'operazione Datastore non riesce a causa di un'altra un utente che aggiorna contemporaneamente entità nel gruppo di entità. In questi casi, l'API Datastore proverà nuovamente a eseguire la transazione per un numero fisso volte. Se non superano tutti, db.run_in_transaction() genera TransactionFailedError. Puoi modificare il numero di nuovi tentativi della transazione utilizzando db.run_in_transaction_custom_retries() invece di db.run_in_transaction().

Analogamente, la funzione di transazione non deve avere effetti collaterali che dipendono la riuscita della transazione, a meno che il codice che chiama la transazione sa di annullare questi effetti. Ad esempio, se la transazione memorizza un nuova entità Datastore, salva l'ID dell'entità creata per un secondo momento non va a buon fine, la transazione non va a buon fine, l'ID salvato non si riferisce all'oggetto perché è stato eseguito il rollback della creazione dell'entità. Il codice chiamante fare attenzione a non usare l'ID salvato, in questo caso.

Isolamento e coerenza

Al di fuori delle transazioni, il livello di isolamento di Datastore è quello più vicino per leggere il commit. All'interno delle transazioni viene applicato l'isolamento serializzabile. Ciò significa che un'altra transazione non può contemporaneamente modificare i dati lette o modificate da questa transazione.

In una transazione, tutte le letture riflettono lo stato attuale e coerente di Datastore al momento dell'inizio della transazione. query e ottiene all'interno di una transazione hanno la garanzia di vedere un unico snapshot coerente Datastore all'inizio della transazione. Entità e le righe di indice nel gruppo di entità della transazione vengono aggiornate in modo che le query restituiscono l'insieme completo e corretto di entità di risultati, senza i falsi positivi o falsi negativi che possono verificarsi nelle query al di fuori delle transazioni.

Questa visualizzazione istantanea coerente si estende anche alle letture dopo le scritture all'interno transazioni. A differenza della maggior parte dei database, esegue query e ottiene all'interno di un La transazione Datastore non vede il i risultati delle precedenti scritture all'interno della transazione. Nello specifico, se un'entità viene modificato o eliminato all'interno di una transazione, di una query o di un comando GET versione originale dell'entità all'inizio della transazione o niente se l'entità non esisteva in quel caso.

Utilizzi delle transazioni

Questo esempio mostra un utilizzo delle transazioni: l'aggiornamento di un'entità con un nuovo rispetto al suo valore attuale.

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

Questa operazione richiede una transazione perché il valore potrebbe essere aggiornato da un altro utente dopo il recupero dell'oggetto da parte di questo codice, ma prima di salvare l'oggetto modificato. Senza una transazione, la richiesta dell'utente utilizza il valore count precedente al valore l'aggiornamento di un altro utente e il salvataggio sovrascrive il nuovo valore. Con un transazione, all'applicazione viene comunicato l'aggiornamento dell'altro utente. Se l'entità viene aggiornata durante transazione, la transazione verrà ritentata fino al completamento di tutti i passaggi senza interruzioni.

Un altro uso comune delle transazioni è recuperare un'entità con una chiave denominata, oppure se non esiste ancora:

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

Come in precedenza, è necessaria una transazione per gestire il caso in cui un altro utente durante il tentativo di creare o aggiornare un'entità con lo stesso ID stringa. Senza un transazione, se l'entità non esiste e due utenti tentano di crearla, il secondo sostituisce la prima senza sapere che è successo. Con una transazione, il secondo tentativo riprova, e viene notificato che l'entità ora esiste e la aggiorna.

Quando una transazione non va a buon fine, puoi chiedere all'app di riprovare la transazione finché non riesce oppure puoi consentire agli utenti di gestire l'errore propagandolo il livello dell'interfaccia utente dell'app. Non è necessario creare un ciclo di nuovi tentativi ogni transazione.

Il comando get-or-create è talmente utile che esiste un metodo integrato: Model.get_or_insert() richiede un nome della chiave, un elemento padre facoltativo e argomenti da passare al modello costruttore se non esiste un'entità con quel nome e quel percorso. Il tentativo di recupero e la creazione avviene in una transazione, quindi (se la transazione va a buon fine) il metodo restituisce sempre un'istanza del modello che rappresenta un'entità effettiva.

Infine, puoi utilizzare una transazione per leggere uno snapshot coerente Datastore. Questo può essere utile quando sono necessarie più letture per visualizzare una pagina o esportare dati che devono essere coerenti. Questi tipi di sono chiamate spesso transazioni di sola lettura perché non eseguono scrive. Le transazioni di sola lettura su un singolo gruppo non vanno mai a buon fine a causa di in modo da non dover implementare nuovi tentativi in caso di errore. Tuttavia, le transazioni tra gruppi possono non riuscire a causa di modifiche contemporanee, pertanto dovrebbero avere dei nuovi tentativi. Il commit e il rollback di una transazione di sola lettura entrambi autonomi.

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

Accodamento delle attività transazionali

Puoi accodare un'attività come parte di una transazione Datastore, che l'attività è in coda solo se il commit della transazione è riuscito. Se il commit della transazione non viene eseguito, l'attività non viene accodata. Se la transazione viene confermata, l'attività viene accodata. Una volta accodata, l'attività non verrà eseguita immediatamente, quindi l'attività non è atomica sulla transazione. Tuttavia, una volta accodata, l'attività riproverà fino a quando non riesce. Ciò vale per qualsiasi attività accodata durante una funzione run_in_transaction().

Le attività transazionali sono utili perché ti consentono di combinare di azioni non Datastore a una transazione che dipende dal transazione completata (ad esempio, invio di un'email per confermare un acquisto). Puoi associa anche le azioni di Datastore alla transazione, ad esempio esegui il commit delle modifiche ai gruppi di entità al di fuori della transazione solo se transazione completata correttamente.

Un'applicazione non può inserire più di cinque attività transazionali nelle code di attività nel corso di una singola transazione. Le attività transazionali non devono essere specificate dall'utente names.

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

db.run_in_transaction(do_something_in_transaction, ....)