2015-02-06 11 views
6

Update: soluzione potenziale al di sottoPostgres 9.3: problema ShareLock con semplice INSERT

Ho un grande corpus di file di configurazione, comprensivi di coppie chiave/valore che sto cercando di spingere in un database. Molte chiavi e valori sono ripetuti attraverso i file di configurazione, quindi sto memorizzando i dati utilizzando 3 tabelle. Uno per tutti i valori chiave univoci, uno per tutti i valori di coppia univoci e un elenco di tutte le coppie chiave/valore per ogni file.

Problema: Sto utilizzando più processi simultanei (e quindi connessioni) per aggiungere i dati grezzi nel database. Sfortunatamente ottengo un sacco di deadlock rilevati quando provo ad aggiungere valori alla chiave e alle tabelle dei valori. Ho un provato alcuni metodi diversi di inserimento dati (vedi sotto), ma sempre finire con un "deadlock rilevato" Errore

TransactionRollbackError: deadlock detected
DETAIL: Process 26755 waits for ShareLock on transaction 689456; blocked by process 26754. Process 26754 waits for ShareLock on transaction 689467; blocked by process 26755.

mi chiedevo se qualcuno potrebbe far luce su esattamente ciò potrebbe causare questi deadlock e possibilmente mi indicano un qualche modo di risolvere il problema. Osservando le istruzioni SQL che sto usando (elencate di seguito), non vedo per quale motivo vi sia alcuna co-dipendenza.

Grazie per la lettura!

Esempio file di configurazione:

example_key this_is_the_value 
other_example other_value 
third example yet_another_value 

definizioni Tabella:

CREATE TABLE keys (
     id SERIAL PRIMARY KEY, 
     hash UUID UNIQUE NOT NULL, 
     key TEXT); 

    CREATE TABLE values (
     id SERIAL PRIMARY KEY, 
     hash UUID UNIQUE NOT NULL, 
     key TEXT); 

    CREATE TABLE keyvalue_pairs (
     id SERIAL PRIMARY KEY, 
     file_id INTEGER REFERENCES filenames, 
     key_id INTEGER REFERENCES keys, 
     value_id INTEGER REFERENCES values); 

istruzioni SQL:

Inizialmente stavo cercando di utilizzare questa istruzione per evitare eventuali eccezioni :

WITH s AS (
     SELECT id, hash, key FROM keys 
      WHERE hash = 'hash_value'; 
    ), i AS (
     INSERT INTO keys (hash, key) 
     SELECT 'hash_value', 'key_value' 
     WHERE NOT EXISTS (SELECT 1 FROM s) 
     returning id, hash, key 
    ) 
    SELECT id, hash, key FROM i 
    UNION ALL 
    SELECT id, hash, key FROM s; 

Ma anche qualcosa di semplice come questo fa sì che le situazioni di stallo:

INSERT INTO keys (hash, key) 
     VALUES ('hash_value', 'key_value') 
     RETURNING id; 
  • In entrambi i casi, se ricevo un eccezione generata poiché il valore hash inserito non è unico, io uso i punti di salvataggio per ripristinare la modifica e un'altra istruzione per selezionare solo l'ID che sto cercando.
  • sto usando gli hash per il campo unico, come alcune delle chiavi e valori sono troppo lunghi per essere indicizzati

completa esempio del codice Python (usando psycopg2) con i punti di salvataggio:

key_value = 'this_key' 
hash_val = generate_uuid(value) 
try: 
    cursor.execute(
     ''' 
     SAVEPOINT duplicate_hash_savepoint; 
     INSERT INTO keys (hash, key) 
      VALUES (%s, %s) 
      RETURNING id; 
     ''' 
     (hash_val, key_value) 
    ) 

    result = cursor.fetchone()[0] 
    cursor.execute('''RELEASE SAVEPOINT duplicate_hash_savepoint''') 
    return result 
except psycopg2.IntegrityError as e: 
    cursor.execute(
     ''' 
     ROLLBACK TO SAVEPOINT duplicate_hash_savepoint; 
     ''' 
    ) 

    #TODO: Should ensure that values match and this isn't just 
    #a hash collision 

    cursor.execute(
     ''' 
     SELECT id FROM keys WHERE hash=%s LIMIT 1; 
     ''' 
     (hash_val,) 
    ) 
    return cursor.fetchone()[0] 

Aggiornamento: quindi credo che un suggerimento su another stackexchange site:

In particolare:

UPDATE, DELETE, SELECT FOR UPDATE, and SELECT FOR SHARE commands behave the same as SELECT in terms of searching for target rows: they will only find target rows that were committed as of the command start time1. However, such a target row might have already been updated (or deleted or locked) by another concurrent transaction by the time it is found. In this case, the would-be updater will wait for the first updating transaction to commit or roll back (if it is still in progress). If the first updater rolls back, then its effects are negated and the second updater can proceed with updating the originally found row. If the first updater commits, the second updater will ignore the row if the first updater deleted it2, otherwise it will attempt to apply its operation to the updated version of the row.

Mentre io non sono ancora esattamente sicuro dove il co-dipendenza è, sembra che l'elaborazione di un gran numero di coppie chiave/valore, senza commettere sarebbe probabilmente porterà a qualcosa di simile. Abbastanza sicuro, se mi impegno dopo ogni singolo file di configurazione viene aggiunto, i deadlock non si verificano.

risposta

7

Sembra che tu sei in questa situazione:

  1. La tabella da inserire in ha una chiave primaria (o indice univoco (es) di qualsiasi tipo).
  2. Diversi inserisce tale tabella vengono eseguite all'interno di una transazione (al contrario di commettere immediatamente dopo ciascuno)
  3. Le righe da inserire venire in ordine casuale (con riferimento alla chiave primaria)
  4. Le righe vengono inserite in transazioni concorrenti.

Questa situazione crea le seguenti opportunità di stallo:

Supponendo che non ci sono due sessioni, che ogni iniziato una transazione.

  1. Session # 1: riga di inserimento con la PK 'A'
  2. Session # 2: riga di inserimento con la PK 'B'
  3. Session # 1: cercare di inserire la riga con PK 'B' => Sessione n. 1 viene messa in attesa fino a quando la sessione n. 2 esegue commit o rollback
  4. Sessione n. 2: provare a inserire riga con PK 'A' => La sessione n. 2 viene messa in attesa della sessione n.

Poco dopo, il rilevatore deadlock ottiene consapevole che entrambe le sessioni sono ora in attesa per l'altro, e termina uno di loro con un fatale deadlock rilevato errore.

Se ci si trova in questo scenario, la soluzione più semplice è COMMIT dopo aver inserito una nuova voce, prima di tentare di inserire una nuova riga nella tabella.

+0

Un'altra alternativa sarebbe inserire i valori sempre nello stesso ordine. –