2015-02-06 18 views
6

Update: Mögliche Lösung unterPostgres 9.3: SHARELOCK Problem mit einfachen INSERT

Ich habe eine große Korpus von Konfigurationsdateien von Schlüssel/Wert-Paare aus, die ich in eine Datenbank zu schieben bin versucht. Viele der Schlüssel und Werte werden über Konfigurationsdateien hinweg wiederholt, sodass ich die Daten mit 3 Tabellen speichere. Eine für alle eindeutigen Schlüsselwerte, eine für alle eindeutigen Paarwerte und eine, die alle Schlüssel/Wert-Paare für jede Datei auflistet.

Problem: Ich verwende mehrere gleichzeitige Prozesse (und daher Verbindungen), um die Rohdaten in die Datenbank hinzuzufügen. Leider bekomme ich viele Deadlocks beim Versuch, Werte zu den Schlüssel- und Wertetabellen hinzuzufügen. Ich habe eine versucht, ein paar verschiedene Methoden, um die Daten einzufügen (siehe unten), aber immer mit einem „Deadlock erkannt“ am Ende Fehler

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.

ich mich gefragt, ob jemand auf genau etwas Licht konnte, was könnte verursachen diese Deadlocks, und möglicherweise weisen mich auf eine Möglichkeit, das Problem zu beheben. Wenn ich mir die SQL-Anweisungen ansehe, die ich verwende (siehe unten), sehe ich nicht wirklich, warum überhaupt eine Co-Abhängigkeit besteht.

Danke fürs Lesen!

Beispiel Konfigurationsdatei:

example_key this_is_the_value 
other_example other_value 
third example yet_another_value 

Tabellendefinitionen:

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); 

SQL-Anweisungen:

Am Anfang war ich versucht, diese Aussage zu verwenden, um alle Ausnahmen zu vermeiden :

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; 

Aber auch etwas so einfach wie dies bewirkt, dass die Deadlocks:

INSERT INTO keys (hash, key) 
     VALUES ('hash_value', 'key_value') 
     RETURNING id; 
  • In beiden Fällen, wenn ich eine Ausnahme geworfen, weil der eingesetzten Hash Wert nicht eindeutig zuzuordnen ist, verwende ich Savepoints um die Änderung rückgängig zu machen und eine andere Aussage, um nur die ID, nach der ich bin, zu wählen.
  • Ich Hashes für das einzigartige Feld verwenden, da einige der Schlüssel und Werte sind zu lang, um

Voll Beispiel des Python-Code (mit psycopg2) indiziert wird mit Sicherungspunkten:

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] 

Update: So glaube ich, dass ich einen Hinweis auf another stackexchange site:

Insbesondere:

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.

Während ich noch nicht ganz sicher bin, wo die Co-Abhängigkeit ist, scheint es, dass eine große Anzahl von Schlüssel/Wert-Paare Verarbeitung ohne commiting in so etwas wie dies wahrscheinlich zur Folge hätte. Sicher genug, wenn ich festlege, nachdem jede einzelne Konfigurationsdatei hinzugefügt wurde, treten die Deadlocks nicht auf.

Antwort

7

Es sieht aus wie Sie in dieser Situation sind:

  1. Die Tabelle in INSERT hat einen Primärschlüssel (oder einen eindeutigen Index (es) jeglicher Art).
  2. mehr Einfügungen in diese Tabelle sind innerhalb einer Transaktion durchgeführt
  3. die Reihen (wie nach jedem zu begehen unmittelbar entgegengesetzt), um in zufälliger Reihenfolge (in Bezug auf die Primärschlüssel) einzufügen kommt
  4. Die Zeilen eingefügt werden, gleichzeitige Transaktionen.

Diese Situation schafft die folgende Möglichkeit für Deadlock:

Angenommen, es gibt zwei Sitzungen, die jeweils eine Transaktion gestartet.

  1. Session # 1: Einfügezeile mit PK 'A'
  2. Session # 2: Einfügezeile mit PK 'B'
  3. Session # 1: Versuche Reihe mit PK 'B' => einzufügen Sitzung Nr. 1 wird warten, bis Sitzung Nr. 2 Commits oder Rollbacks durchführt
  4. Sitzung 2: versuchen, Zeile mit PK 'A' einzufügen => Sitzung # 2 wird auf Sitzung # 1 warten.

Kurz danach wird der Deadlock-Detektor wird bewusst, dass beide Sitzungen jetzt aufeinander warten, und beendet eine von ihnen mit tödlichem Deadlock Fehler erkannt.

Wenn Sie in diesem Szenario sind, ist die einfachste Lösung COMMIT, nachdem ein neuer Eintrag eingefügt wird, bevor Sie versuchen, eine neue Zeile in die Tabelle einzufügen.

+0

Eine andere Alternative wäre, die Werte immer in der gleichen Reihenfolge einzufügen. –

Verwandte Themen