Подтвердить что ты не робот

Ошибка взаимоблокировки в инструкции INSERT

У нас есть веб-приложение. В приложении есть временные операции с базами данных (INSERT и UPDATE), которые занимают больше времени, поэтому этот конкретный поток был изменен на Java-поток, поэтому он не будет ждать (блок) для завершения полной операции с базой данных.

Моя проблема в том, что, если более 1 пользователь сталкивается с этим конкретным потоком, я столкнулся со следующей ошибкой, созданной PostgreSQL:

org.postgresql.util.PSQLException: ERROR: deadlock detected
  Detail: Process 13560 waits for ShareLock on transaction 3147316424; blocked by process 13566.
Process 13566 waits for ShareLock on transaction 3147316408; blocked by process 13560.

Вышеупомянутая ошибка последовательно вводится в инструкциях INSERT.

Дополнительная информация: 1) У меня есть ПЕРВИЧНЫЙ КЛЮЧ, определенный в этой таблице. 2) В этой таблице указаны ссылки FOREIGN KEY. 3) Отдельное соединение с базой данных передается каждому потоку Java.

Технологии Веб-сервер: Tomcat v6.0.10 Java v1.6.0 Servlet База данных: PostgreSQL v8.2.3 Управление подключением: pgpool II

4b9b3361

Ответ 1

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

Конечно, если вы в состоянии определить причину тупика, то обычно гораздо лучше устранить ее или она вернется, чтобы укусить вас. Для почти всех случаев, даже когда условие взаимоблокировки является редкими, небольшая пропускная способность и накладные расходы на кодирование, чтобы получить блокировки в детерминированном порядке или получить более крупнозернистые блокировки, стоит того, чтобы избежать случайного большого латентного удара и внезапного скачка производительности при масштабировании concurrency.

Когда вы последовательно получаете два оператора INSERT, которые блокируют его, скорее всего, проблема с уникальной вставкой индекса. Попробуйте, например, следующее в двух командных окнах psql:

Thread A           | Thread B
BEGIN;             | BEGIN;
                   | INSERT uniq=1;
INSERT uniq=2;     | 
                   | INSERT uniq=2; 
                   |   block waiting for thread A to commit or rollback, to
                   |   see if this is an unique key error.
INSERT uniq=1;     |
   blocks waiting  |
   for thread B,   |
     DEADLOCK      | 
                   V    

Обычно наилучшим способом решения этой проблемы является выяснение родительских объектов, которые защищают все такие транзакции. Большинство приложений имеют один или два из первичных объектов, таких как пользователи или учетные записи, которые являются хорошими кандидатами для этого. Тогда все, что вам нужно, это для каждой транзакции получить блокировки первичного объекта, к которому он прикасается через SELECT... FOR UPDATE. Или, если затрагивает несколько, получайте блокировки на всех из них, но в том же порядке каждый раз (порядок по первичному ключу является хорошим выбором).

Ответ 2

Что PostgreSQL здесь описано в документации по Явное блокирование. Пример в разделе "Тупики" показывает, что вы, вероятно, делаете. Часть, которую вы, возможно, и не ожидали, заключается в том, что когда вы ОБНОВЛЯЕТ что-то, которое получает блокировку в этой строке, которая продолжается до завершения транзакции. Если у вас есть несколько клиентов, каждый из которых выполняет обновление более чем одной вещи сразу, вы неизбежно окажетесь в тупиках, если не будете уходить с пути, чтобы предотвратить их.

Если у вас есть несколько вещей, которые вызывают неявные блокировки, такие как UPDATE, вы должны обернуть всю последовательность в блоках транзакций BEGIN/COMMIT и убедиться, что вы согласны с порядком, в котором они приобретают блокировки (даже неявные, например UPDATE захватывает) повсюду. Если вам нужно что-то обновить в таблице A, тогда таблица B, а одна часть приложения делает A тогда B, а другая - B, затем A, вы собираетесь зайти в тупик в один прекрасный день. Два UPDATE для одной и той же таблицы аналогичным образом обречены на провал, если вы не можете обеспечить выполнение некоторых заказов, которые повторяются среди клиентов. Сортировка по первичному ключу, когда у вас есть набор записей для обновления и всегда захватывающий "нижний" первый, является общей стратегией.

Менее вероятно, что ваши ВСТАВКИ будут виноваты здесь, тем сложнее попасть в тупик, если вы не нарушаете первичный ключ, как уже указывали муравьи.

То, что вы не хотите делать, это попытаться дублировать блокировку в вашем приложении, которая превратится в гигантскую масштабируемость и надежность (и, вероятно, все равно приведет к блокировкам базы данных). Если вы не можете обойти это в рамках стандартных методов блокировки базы данных, попробуйте использовать либо средство консультативной блокировки, либо явное LOCK TABLE для обеспечения соблюдения что вам нужно. Это спасет вас от болезненного кодирования, пытаясь вытолкнуть все блокировки на клиентскую сторону. Если у вас есть несколько обновлений против таблицы и не может обеспечить соблюдение порядка, в котором они происходят, у вас нет выбора, кроме как заблокировать всю таблицу во время ее выполнения; что единственный путь, который не создает возможности для тупика.

Ответ 3

Отключен тупик:
Вкратце, происходит то, что конкретный оператор SQL (INSERT или другой) ждет другого оператора, чтобы освободить блокировку для определенной части базы данных, прежде чем она сможет продолжить. Пока эта блокировка не будет выпущена, первый оператор SQL, назовите его "оператор А", не позволит себе получить доступ к этой части базы данных для выполнения своей работы (= регулярная ситуация блокировки). Но... оператор A также поместил блокировку в другую часть базы данных, чтобы гарантировать, что никакие другие пользователи доступа к базе данных (для чтения или модификации/удаления в зависимости от типа блокировки). Теперь... второй оператор SQL, сам по себе нуждается в доступе к разделу данных, отмеченному блокировкой Statement A. Это DEAD LOCK: оба Statement будут ждать, ad infinitum, друг на друге.

Средство...

Для этого потребуется знать конкретный оператор SQL, в котором работают эти различные потоки, и смотреть там, если есть способ:

a) removing some of the locks, or changing their types.
   For example, maybe the whole table is locked, whereby only a given row, or
   a page thereof would be necessary.
b) preventing multiple of these queries to be submitted at a given time.
   This would be done by way of semaphores/locks (aka MUTEX) at the level of the
   multi-threading logic.

Остерегайтесь того, что подход "b", если он не был правильно реализован, может просто переместить ситуацию взаимоблокировки изнутри SQL в логику программы/потоков. Ключ должен был бы только создать один мьютекс, который должен быть получен первым любым потоком, который собирается запустить один из этих запросов, связанных с блокировкой.

Ответ 4

Ваша проблема, вероятно, заключается в том, что команда insert пытается заблокировать один или оба индекса, а индексы заблокированы для другого протектора.

Одной из распространенных ошибок является блокировка ресурсов в разных порядках для каждого потока. Проверьте заказы и попытайтесь заблокировать ресурсы в том же порядке во всех потоках.