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 ответов
похоже, вы находитесь в такой ситуации:
- таблица для вставки имеет первичный ключ(или уникальный индекс (ы) любого рода).
- несколько вставок в эту таблицу выполняются в рамках одной транзакции (в отличие от фиксации сразу после каждой)
- строки для вставки приходят в случайном порядке (с учетом первичного ключа)
- строки вставляются в параллельные транзакции.
эта ситуация создает следующая возможность для deadlock:
предполагая, что есть два сеанса, каждый из которых начал транзакцию.
- Сессия #1: Вставьте строку с PK 'A'
- Сессия #2: вставьте строку с PK 'B'
- Сессия #1: попробуйте вставить строку с PK 'B' => Сеанс #1 откладывается до тех пор, пока сеанс #2 не зафиксирует или откат
- Сессия #2: попробуйте вставить строку с PK 'A' => Сеанс #2 будет ждать сеанса #1.
вскоре после этого детектор тупика узнает, что оба сеанса теперь ждут друг друга, и завершает один из них фатальным обнаружена взаимоблокировка ошибка.
Если вы находитесь в этом случае, самым простым решением будет совершить после вставки новой записи, прежде чем пытаться вставить новую строку в таблицу.