Questa pagina spiega le transazioni in Spanner e include codice campione per l'esecuzione di transazioni.
Introduzione
Una transazione in Spanner è un insieme di letture e scritture che vengono eseguite in modo atomico in un singolo punto logico nel tempo su colonne, righe e tabelle di un database.
Spanner supporta queste modalità di transazione:
Blocco di lettura/scrittura. Queste transazioni si basano su blocchi pessimistici e, se necessario in due fasi. Il blocco delle transazioni di lettura/scrittura potrebbe essere interrotto, richiedono all'applicazione di riprovare.
Di sola lettura. Questo tipo di transazione offre coerenza garantita tra diverse letture, ma non consente scritture. Per impostazione predefinita, le transazioni di sola lettura vengono eseguite in base a un timestamp scelto dal sistema che garantisce la coerenza esterna, ma possono anche essere configurate per la lettura in base a un timestamp passato. Sola lettura le transazioni non devono essere sottoposte a commit e non prevedono blocchi. Inoltre, le transazioni di sola lettura potrebbero attendere il completamento delle scritture in corso prima di essere eseguite.
DML partizionato. Questo tipo di transazione esegue una manipolazione di dati Istruzione del linguaggio (DML) come DML partizionato. Il DML partizionato è progettato per aggiornamenti ed eliminazioni collettivi, in particolare per la pulizia e il backfill periodici. Se devi eseguire il commit di un numero elevato di scrittura cieche, ma non hai bisogno di una transazione atomica, puoi modificare collettivamente le tue tabelle Spanner utilizzando la scrittura batch. Per ulteriori informazioni, consulta la sezione Modificare i dati utilizzando le scritture collettive.
Questa pagina descrive le proprietà generali e la semantica delle transazioni in Spanner e introduce il DML partizionato, di sola lettura e lettura di transazioni in Spanner.
Transazioni di lettura/scrittura
Di seguito sono riportati gli scenari in cui è consigliabile utilizzare una transazione di lettura/scrittura con blocco:
- Se esegui una scrittura che dipende dal risultato di una
altre letture, dovresti eseguire la scrittura e le operazioni di lettura
di lettura/scrittura.
- Esempio: raddoppia il saldo del conto bancario A. La lettura del saldo di A deve essere nella stessa transazione della scrittura per sostituire il saldo con il valore raddoppiato.
- Se esegui una o più scritture che devono essere committate in modo atomico, devi eseguire queste scritture nella stessa transazione di lettura/scrittura.
- Esempio: trasferisci 200 € dall'account A all'account B. Entrambe le operazioni di scrittura (una per diminuire A di 200 $e una per aumentare B di 200 $) e le letture del saldo iniziale del conto deve essere incluso nella stessa transazione.
- Se potresti eseguire una o più scritture, a seconda dei risultati di una o più letture, devi eseguire queste scritture e letture nella stessa transazione di lettura/scrittura, anche se le scritture non vengono eseguite.
- Esempio: trasferisci 200 $dal conto bancario A al conto B se è A il saldo attuale è superiore a 500 $. La transazione deve contenere un letto del saldo di A e una dichiarazione condizionale che contiene scrive.
Ecco uno scenario in cui non dovresti utilizzare un sistema di lettura e scrittura di blocco transazione:
- Se esegui solo letture e puoi esprimerle utilizzando un metodo di lettura singola, devi utilizzare questo metodo o una transazione di sola lettura. Le letture singole non vengono bloccate, diversamente dalle transazioni di lettura/scrittura.
Proprietà
Una transazione di lettura-scrittura in Spanner esegue un insieme di letture e scritture in modo atomico in un singolo punto logico nel tempo. Inoltre, il timestamp al quale vengono eseguite le transazioni di lettura/scrittura corrisponde all'ora del sistema operativo e l'ordine di serializzazione corrisponde all'ordine dei timestamp.
Perché utilizzare una transazione di lettura/scrittura? Le transazioni di lettura/scrittura forniscono le proprietà ACID dei database relazionali (infatti, le transazioni di lettura/scrittura di Spanner offrono garanzie ancora più solide rispetto all'ACID tradizionale; consulta la sezione Semantica di seguito).
Isolamento
Di seguito sono riportate le proprietà di isolamento per le transazioni di lettura/scrittura e di sola lettura.
Transazioni di lettura e scrittura
Di seguito sono riportate le proprietà di isolamento che ottieni dopo aver eseguito il commit di una transazione contenente una serie di letture (o query) e scritture:
- Tutte le letture all'interno della transazione hanno restituito valori che riflettono uno snapshot coerente acquisito al timestamp di commit della transazione.
- Le righe o gli intervalli vuoti sono rimasti così al momento del commit.
- Tutte le scritture all'interno della transazione sono state committate al timestamp del commit della transazione.
- Le scritture non erano visibili in nessuna transazione fino al termine della transazione impegnato.
Alcuni driver client Spanner contengono una logica di ripetizione delle transazioni per mascherare gli errori temporanei, eseguendo nuovamente la transazione e convalidando i dati osservati dal client.
Il risultato è che tutte le operazioni di lettura e scrittura sembrano essere avvenute in un singolo momento specifico, sia dal punto di vista della transazione stessa sia il punto di vista di altri lettori e scrittori al database Spanner. Nella altre parole, le letture e le scritture finiscono per verificarsi allo stesso timestamp (per un'illustrazione, consulta la sezione Serializzabilità e coerenza di seguito).
Transazioni di sola lettura
Le garanzie per una transazione di lettura/scrittura che esegue solo letture sono simili: tutte le letture all'interno della transazione restituiscono i dati dello stesso timestamp, anche per le righe non esistenti. Una differenza è che se leggi i dati e in seguito esegui il commit di lettura/scrittura senza scritture, non c'è alcuna garanzia che i dati non sono cambiate nel database dopo la lettura e prima del commit. Se vuoi per sapere se i dati sono cambiati dall'ultima volta che li hai letti, l'approccio migliore è rileggere il messaggio (in una transazione di lettura/scrittura o utilizzando una lettura efficace). Inoltre, per maggiore efficienza, se sai in anticipo che leggerai solo contenuti e non devi utilizzare una transazione di sola lettura anziché una transazione di lettura/scrittura.
Atomicità, coerenza, durabilità
Oltre alla proprietà Isolation, Spanner fornisce l'Atomicità (se una qualsiasi delle scritture nell'impegno di transazione, si impegnano tutte), la coerenza (il rimane in uno stato coerente dopo la transazione) e la durabilità. (il commit dei dati impegnati rimane invariato.)
Vantaggi di queste strutture
Grazie a queste proprietà, gli sviluppatori di applicazioni possono concentrarsi sulle la correttezza di ogni transazione, senza doversi preoccupare di proteggerne l'esecuzione da altre transazioni che potrebbero essere eseguite contemporaneamente nel tempo.
Interfaccia
Le librerie client Spanner forniscono un'interfaccia per l'esecuzione di un corpo di lavoro nel contesto di una transazione di lettura-scrittura, con tentativi di nuovo invio per gli aborti della transazione. Ecco un po' di contesto per spiegare questo punto: potrebbe essere necessario provare una transazione Spanner più volte prima di eseguire il commit. Ad esempio, se due transazioni tentano di lavorare contemporaneamente sui dati in un modo che potrebbe causare un blocco, Spanner ne interrompe una in modo che l'altra transazione possa avanzare. (Più raramente, gli eventi temporanei all'interno di Spanner possono comporta l'interruzione di alcune transazioni.) Poiché le transazioni sono atomiche, una transazione abortita non ha alcun effetto visibile sul database. Pertanto, le transazioni devono essere eseguite tentando di nuovo finché non vanno a buon fine.
Quando utilizzi una transazione in una libreria client Spanner, definisci corpo di una transazione (ovvero le letture e le scritture da eseguire su uno o più tabelle in un database) sotto forma di oggetto funzione. Dietro le quinte, la libreria client di Spanner esegue la funzione ripetutamente fino al commit della transazione o all'incontro di un errore non ripetibile.
Esempio
Supponiamo che tu abbia aggiunto una colonna MarketingBudget
alla tabella
Albums
mostrata nella pagina Schema e modello di dati:
CREATE TABLE Albums ( SingerId INT64 NOT NULL, AlbumId INT64 NOT NULL, AlbumTitle STRING(MAX), MarketingBudget INT64 ) PRIMARY KEY (SingerId, AlbumId);
Il reparto marketing decide di fare una spinta di marketing per l'album associato a Albums (1, 1)
e ti chiede di trasferire 200.000 $ dal budget di Albums
(2, 2)
, ma solo se i fondi sono disponibili nel budget dell'album. Per questa operazione, devi utilizzare una transazione di lettura/scrittura con blocco, perché la transazione potrebbe eseguire scritture a seconda del risultato di una lettura.
Di seguito viene mostrato come eseguire una transazione di lettura/scrittura:
C++
C#
Vai
Java
Node.js
PHP
Python
Ruby
Semantica
Serializzabilità e coerenza esterna
Spanner offre la "serializzabilità", il che significa che tutte le transazioni appaiono come se fossero eseguite in un ordine seriale, anche se alcune delle letture, scritture, mentre le altre operazioni di transazioni distinte si sono verificate in parallelo. Spanner assegna timestamp dei commit che riflettono l'ordine delle transazioni committate per implementare questa proprietà. In effetti, Spanner offre una garanzia più solida della serializzabilità chiamata coerenza esterna: le transazioni vengono committate in un ordine che si riflette nei relativi timestamp di commit, che a loro volta riflettono il tempo reale, in modo da poterli confrontare con il tuo orologio. Legge in transazione vedrà tutti gli elementi di cui è stato eseguito il commit prima della transazione i commit e le scritture sono visibili da tutto ciò che inizia dopo che la transazione impegnato.
Ad esempio, considera l'esecuzione di due transazioni come illustrato nelle diagramma seguente:
La transazione Txn1
in blu legge alcuni dati A
, memorizza nel buffer una scrittura in A
, quindi
di eseguire il commit. La transazione Txn2
in verde inizia dopo il giorno Txn1
e indica
alcuni dati B
, quindi legge i dati A
. Poiché Txn2
legge il valore di A
dopo che Txn1
ha eseguito il commit della sua scrittura in A
, Txn2
vede l'effetto di Txn1
scrivi in A
, anche se Txn2
è stato avviato prima del completamento Txn1
.
Anche se c'è una sovrapposizione nel tempo in cui Txn1
e Txn2
sono entrambi
in esecuzione, i relativi timestamp di commit c1
e c2
rispettano una transazione lineare
dell'ordine, il che significa che tutti gli effetti delle letture e delle scritture di Txn1
che si siano verificate in un singolo momento (c1
) e tutti gli effetti
le letture e le scritture di Txn2
sembrano essere avvenute in un singolo momento
(c2
). Inoltre, c1 < c2
(che è garantito perché Txn1
e
Txn2
scritture di cui è stato eseguito il commit; questo vale anche se le scritture sono avvenute su
macchine), rispettando l'ordine di Txn1
che si verifica prima di Txn2
.
(Tuttavia, se Txn2
ha letto solo nella transazione, allora c1 <= c2
).
Le letture osservano un prefisso della cronologia di commit. se una lettura vede l'effetto
Txn2
, vede anche l'effetto di Txn1
. Tutte le transazioni che si impegnano
avere correttamente questa proprietà.
Garanzie di lettura e scrittura
Se una chiamata per eseguire una transazione non va a buon fine, le garanzie di lettura e scrittura dipende dall'errore con cui non è riuscita la chiamata di commit sottostante.
Ad esempio, un errore come "Riga non trovata" o "Riga già esistente" indica che la scrittura delle mutazioni in buffer ha riscontrato un errore, ad esempio una riga che il cliente sta tentando di aggiornare non esiste. In questo caso, le letture sono garantite come coerenti, le scritture non vengono applicate e la non esistenza della riga è garantita anche come coerente con le letture.
Annullamento delle operazioni di transazione
Le operazioni di lettura asincrona possono essere annullate in qualsiasi momento dall'utente (ad es. quando viene annullata un'operazione di livello superiore oppure si decide di interrompere una sui risultati iniziali ricevuti dalla lettura) senza influire su altre le operazioni esistenti all'interno della transazione.
Tuttavia, anche se hai tentato di annullare la lettura, Spanner non garantisce che la lettura sia effettivamente annullata. Dopo aver richiesto l'annullamento di una lettura, la lettura può comunque essere completata o non riuscire per un altro motivo (ad es. Abort). Inoltre, la lettura annullata potrebbe effettivamente restituirti alcuni risultati, che verranno convalidati nell'ambito del commit della transazione.
Tieni presente che, a differenza delle letture, l'annullamento di un'operazione di commit della transazione comporterà l'interruzione della transazione (a meno che non sia già stato eseguito il commit della transazione o non sia stata eseguita con un altro motivo).
Prestazioni
Chiusura della serratura
Spanner consente a più client di interagire contemporaneamente con lo stesso database. Per garantire la coerenza di più istanze transazioni, Spanner usa una combinazione di blocchi condivisi e blocchi per controllare l'accesso ai dati. Quando esegui una lettura nell'ambito di una transazione, Spanner acquisisce blocchi di lettura condivisi, che consentono ad altre letture di accedere comunque ai dati fino a quando la transazione non è pronta per l'commit. Quando viene eseguito il commit della transazione e vengono applicate le scritture, la transazione tenta di eseguire l'upgrade a un blocco esclusivo. Blocca i nuovi blocchi di lettura condivisi sui dati, attende che i blocchi di lettura condivisi esistenti vengano cancellati, quindi inserisce un blocco esclusivo per l'accesso esclusivo ai dati.
Note sulle serrature:
- I blocchi vengono applicati al livello di granularità di righe e colonne. Se la transazione T1 ha colonna bloccata "A" della riga "foo" e la transazione T2 vuole scrivere la colonna "B" della riga "foo" non ci sono conflitti.
- Le scritture in un elemento dati che non leggono anche i dati in fase di scrittura (ovvero le "scritture blind") non sono in conflitto con altre scritture blind dello stesso elemento (il timestamp del commit di ogni scrittura determina l'ordine in cui viene applicata al database). Di conseguenza, Spanner deve eseguire l'upgrade a un blocco esclusivo solo se hai letto i dati che stai scrivendo. In caso contrario, Spanner utilizza un blocco condiviso chiamato blocco condiviso per gli autori.
- Quando esegui ricerche di righe all'interno di una transazione di lettura/scrittura, utilizza indici secondari per limitare le righe sottoposti a scansione su un intervallo più piccolo. Questo fa sì che Spanner blocchi un numero inferiore di righe nella tabella, consentendo la modifica simultanea di righe al di fuori di intervallo.
Non utilizzare blocchi per garantire l'accesso esclusivo a una risorsa all'esterno di Spanner. Le transazioni possono essere interrotte per diversi motivi: Spanner come, ad esempio, per consentire lo spostamento dei dati all'interno e le risorse di calcolo dell'istanza. Se viene eseguito un nuovo tentativo di una transazione, esplicitamente tramite il codice dell'applicazione o implicitamente tramite il codice client come il driver JDBC Spanner, viene garantito solo che le chiavi sono state mantenute durante il tentativo che ha effettivamente eseguito il commit.
Puoi utilizzare le statistiche di blocco strumento di introspezione per esaminare i conflitti di blocco nel database.
Rilevamento di deadlock
Spanner rileva quando più transazioni potrebbero essere bloccate
forza l'interruzione di tutte le transazioni tranne una. Ad esempio, considera il seguente scenario: la transazione Txn1
detiene un blocco sul record A
ed è in attesa di un blocco sul record B
, mentre Txn2
detiene un blocco sul record B
ed è in attesa di un blocco sul record A
. L'unico modo per avanzare in questa situazione è interrompere una delle transazioni in modo da rilasciare il blocco e consentire l'avanzamento dell'altra transazione.
Spanner utilizza l'algoritmo standard "wound-wait" per gestire il rilevamento dei deadlock. Spanner tiene traccia dell'età di ogni che richiede blocchi in conflitto. Inoltre, consente anche le transazioni meno recenti interrompere le transazioni meno recenti (dove "meno recente" significa che la prima lettura, la query o il commit si sono verificati prima).
Dando la priorità alle transazioni meno recenti, Spanner garantisce che ogni transazione abbia la possibilità di acquisire i blocchi alla fine, quando diventa abbastanza vecchia da avere una priorità più alta rispetto alle altre transazioni. Ad esempio, un transazione che acquisisce un blocco condiviso del lettore può essere interrotta da un transazione che richiede un blocco condiviso dello scrittore.
Esecuzione distribuita
Spanner può eseguire transazioni su dati su più server. Questa potenza comporta un costo in termini di prestazioni rispetto alle transazioni su un singolo server.
Quali tipi di transazioni potrebbero essere distribuiti? Dietro le quinte, Spanner può suddividere la responsabilità per le righe del database su più server. Riga A e le righe corrispondenti nelle tabelle con interleaving sono solitamente pubblicate dallo stesso un server web, così come due righe nella stessa tabella con chiavi vicine. Spanner può Eseguire transazioni tra righe su server diversi; tuttavia, come regola Diminuzione: le transazioni che interessano molte righe con posizioni condivise sono più veloci e economiche transazioni che interessano molte righe sparse nel database oppure in una tabella di grandi dimensioni.
Le transazioni più efficienti in Spanner includono solo le letture e le scritture che devono essere applicate in modo atomico. Le transazioni sono più veloci legge e scrive i dati di accesso nella stessa parte dello spazio delle chiavi.
Transazioni di sola lettura
Oltre a bloccare le transazioni di lettura-scrittura, Spanner offre anche transazioni di sola lettura.
Utilizza una transazione di sola lettura quando devi eseguire più di una lettura al sullo stesso timestamp. Se puoi esprimere la lettura utilizzando uno dei metodi di lettura singola di Spanner, devi utilizzare questo metodo di lettura singola. Il rendimento dell'utilizzo di una singola chiamata di lettura dovrebbe essere paragonabile al rendimento di una singola lettura eseguita in una transazione di sola lettura.
Se stai leggendo una grande quantità di dati, valuta la possibilità di utilizzare le partizioni per leggere i dati in parallelo.
Poiché le transazioni di sola lettura non scrivono, non trattengono i blocchi e non bloccano altre transazioni. Le transazioni di sola lettura osservano un prefisso coerente della cronologia dei commit delle transazioni, in modo che la tua applicazione riceva sempre dati coerenti.
Proprietà
Una transazione di sola lettura di Spanner esegue un insieme di letture in un singolo momento logico, sia dal punto di vista della transazione di sola lettura stessa sia dal punto di vista di altri lettori e scrittori del database Spanner. Ciò significa che le transazioni di sola lettura osservare sempre uno stato coerente del database in un punto selezionato della cronologia delle transazioni.
Interfaccia
Spanner fornisce un'interfaccia per l'esecuzione di un insieme di lavori contesto di una transazione di sola lettura, con nuovi tentativi in caso di interruzione della transazione.
Esempio
Di seguito viene mostrato come utilizzare una transazione di sola lettura per ottenere dati coerenti per due letture nello stesso timestamp:
C++
C#
Vai
Java
Node.js
PHP
Python
Ruby
Transazioni DML partizionate
Utilizzando il linguaggio di manipolazione dei dati partizionato (DML partizionato), puoi eseguire istruzioni UPDATE
e DELETE
su larga scala senza incorrere in limiti di transazioni o bloccare un'intera tabella.
Spanner esegue il partizionamento dello spazio delle chiavi ed esegue le istruzioni DML su ogni
in una transazione di lettura/scrittura separata.
Esegui istruzioni DML in transazioni di lettura/scrittura che crei esplicitamente nel codice. Per ulteriori informazioni, vedi Utilizzo di DML.
Proprietà
Puoi eseguire una sola istruzione DML partizionata alla volta, indipendentemente dall'utilizzo di un metodo della libreria client o di Google Cloud CLI.
Le transazioni partizionate non supportano il commit o il rollback. Spanner esegue e applica immediatamente l'istruzione DML. Se annulli l'operazione, o l'operazione non va a buon fine, Spanner annulla tutte le esecuzioni e non avvia nessuna delle partizioni rimanenti. Spanner non esegue il rollback di nessuna partizione già eseguita.
Interfaccia
Spanner fornisce un'interfaccia per l'esecuzione di un singolo DML partizionato l'Informativa.
Esempi
Il seguente esempio di codice aggiorna la colonna MarketingBudget
della tabella Albums
.
C++
Utilizza la funzione ExecutePartitionedDml()
per eseguire un'istruzione DML partizionata.
C#
Utilizzerai il metodo ExecutePartitionedUpdateAsync()
per eseguire un'istruzione DML partizionata.
Vai
Utilizzerai il metodo PartitionedUpdate()
per eseguire un'istruzione DML partizionata.
Java
Utilizza il metodo executePartitionedUpdate()
per eseguire un'istruzione DML partizionata.
Node.js
Utilizza il metodo runPartitionedUpdate()
per eseguire un'istruzione DML partizionata.
PHP
Utilizza il metodo executePartitionedUpdate()
per eseguire un'istruzione DML partizionata.
Python
Utilizza il metodo execute_partitioned_dml()
per eseguire un'istruzione DML partizionata.
Ruby
Utilizzerai il metodo execute_partitioned_update()
per eseguire un'istruzione DML partizionata.
Il seguente esempio di codice elimina le righe dalla tabella Singers
in base al
SingerId
.