Postgres 9.3: проблема Sharelock с простой вставкой

Update: потенциальное решение ниже

у меня есть большой корпус файлов конфигурации, состоящий из пар ключ/значение, которые я пытаюсь вставить в базу данных. Многие ключи и значения повторяются в файлах конфигурации, поэтому я сохраняю данные с помощью 3 таблиц. Один для всех уникальных значений ключа, один для всех уникальных значений пары и один список всех пар ключ / значение для каждого файла.

: Я использую несколько параллельных процессов (и, следовательно, соединений) для добавления необработанных данных в базу данных. К сожалению, я получаю много обнаруженных тупиков при попытке добавить значения в таблицы ключей и значений. Я попробовал несколько разных методов вставки данных (показано ниже), но всегда заканчиваю с ошибкой "deadlock detected"

TransactionRollbackError: обнаружен тупик
деталь: процесс 26755 ожидает ShareLock по транзакции 689456; заблокирован процессом 26754. Процесс 26754 ожидает ShareLock по транзакции 689467; заблокирован процесс 26755.

мне было интересно, может ли кто-нибудь пролить свет на то, что может быть причиной этих тупиков, и, возможно, указать мне на какой-то способ решения проблемы. Глядя на операторы SQL, которые я использую (перечислены ниже), я действительно не понимаю, почему вообще существует какая-либо совместная зависимость.

Спасибо за чтение!

пример конфига файл:

example_key this_is_the_value
other_example other_value
third example yet_another_value

таблицы определения:

    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:

Первоначально я пытался использовать этот оператор, чтобы избежать каких-либо исключений:

    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;

но даже что-то такое простое, как это вызывает тупики:

    INSERT INTO keys (hash, key)
        VALUES ('hash_value', 'key_value')
        RETURNING id;
  • в обоих случаях, если я получаю исключение, потому что вставить хэш значение не уникально, я использую savepoints для отката изменения и другой заявление просто выберите идентификатор я после.
  • я использую хеши для уникального поля, как некоторые из ключей и значений слишком долго индексироваться

полный пример кода python (с использованием psycopg2) с savepoints:

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]

обновление: Поэтому я считаю, что намекаю на другой сайт stackexchange:

в частности:

ОБНОВИТЬ, УДАЛИТЬ, ВЫБРАТЬ ДЛЯ ОБНОВЛЕНИЯ, и выберите для команд SHARE вести себя так же, как SELECT с точки зрения поиска целевых строк: они будут найдены только целевые строки, которые были зафиксированы на момент запуска команды времени1. Однако такая целевая строка, возможно, уже обновлена (или удалено или заблокировано) другой параллельной транзакцией к моменту найдено. В этом случае потенциальный updater будет ждать первого обновление транзакции для фиксации или отката (если она все еще находится в прогресс.) Если первое обновление откатывается, тогда его последствия отрицается, и второй updater может продолжить обновление первоначально найдена строка. Если первый updater фиксирует, второй updater будет игнорировать строку, если первый updater удалил it2, в противном случае попытается применить свою операцию к обновленной версии строки.

хотя я все еще не совсем уверен, где находится совместная зависимость, кажется, что обработка большого количества пар ключ/значение без фиксации, вероятно, приведет к чему-то вроде этого. Конечно, если я фиксирую после добавления каждого отдельного файла конфигурации, взаимоблокировки не происходят.

1 ответов


похоже, вы находитесь в такой ситуации:

  1. таблица для вставки имеет первичный ключ(или уникальный индекс (ы) любого рода).
  2. несколько вставок в эту таблицу выполняются в рамках одной транзакции (в отличие от фиксации сразу после каждой)
  3. строки для вставки приходят в случайном порядке (с учетом первичного ключа)
  4. строки вставляются в параллельные транзакции.

эта ситуация создает следующая возможность для deadlock:

предполагая, что есть два сеанса, каждый из которых начал транзакцию.

  1. Сессия #1: Вставьте строку с PK 'A'
  2. Сессия #2: вставьте строку с PK 'B'
  3. Сессия #1: попробуйте вставить строку с PK 'B' => Сеанс #1 откладывается до тех пор, пока сеанс #2 не зафиксирует или откат
  4. Сессия #2: попробуйте вставить строку с PK 'A' => Сеанс #2 будет ждать сеанса #1.

вскоре после этого детектор тупика узнает, что оба сеанса теперь ждут друг друга, и завершает один из них фатальным обнаружена взаимоблокировка ошибка.

Если вы находитесь в этом случае, самым простым решением будет совершить после вставки новой записи, прежде чем пытаться вставить новую строку в таблицу.