Ошибка тупика в инструкции INSERT - PullRequest
15 голосов
/ 05 октября 2009

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

Моя проблема в том, что если более одного пользователя сталкиваются с этим конкретным потоком, я сталкиваюсь со следующей ошибкой, выдаваемой 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) В этой таблице есть ссылки на ИНОСТРАННЫЙ КЛЮЧ. 3) Отдельное соединение с базой данных передается каждому потоку Java.

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

Ответы [ 4 ]

24 голосов
/ 05 октября 2009

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

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

Когда вы последовательно получаете взаимоблокировку двух операторов 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. Или, если прикоснуться к нескольким, получить блокировки на всех из них, но в том же порядке каждый раз (выбор по первичному ключу - хороший выбор).

12 голосов
/ 06 октября 2009

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

Если у вас есть несколько вещей, которые снимают неявные блокировки, такие как UPDATE, вы должны обернуть всю последовательность в блоки транзакций BEGIN / COMMIT и убедиться, что вы согласны с порядком получения блокировок (даже неявных, таких как UPDATE). хватает) везде. Если вам нужно что-то обновить в таблице A, затем в таблице B, и одна часть приложения выполняет A, а затем B, а другая - B, а затем A, вы однажды зашли в тупик. Два ОБНОВЛЕНИЯ для одной и той же таблицы также обречены на неудачу, если только вы не можете принудительно установить порядок их повторения среди клиентов. Сортировка по первичному ключу, когда у вас есть набор записей для обновления, и всегда сначала выбирается «нижний» ключ - это общая стратегия.

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

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

5 голосов
/ 05 октября 2009

Объяснение тупика :
В двух словах, происходит то, что определенный оператор SQL (INSERT или другой) ожидает другого оператора, чтобы снять блокировку с определенной части базы данных, прежде чем он сможет продолжить. До тех пор, пока эта блокировка не будет снята, первый оператор SQL, называемый его «оператор A», не позволит себе получить доступ к этой части базы данных для выполнения своей работы (= ситуация обычной блокировки). Но ... оператор A также установил блокировку другой части базы данных, чтобы другие пользователи базы данных не могли получить к ней доступ (для чтения или изменения / удаления в зависимости от типа блокировки). Теперь ... второй оператор SQL сам нуждается в доступе к разделу данных, помеченному блокировкой оператора A. Это DEAD LOCK: оба оператора будут ждать, бесконечно, друг на друга.

Средство правовой защиты ...

Для этого потребуется знать конкретный оператор 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 в логику программы / потоков. Ключ должен был бы создать только один мьютекс, который будет сначала получен любым потоком, который собирается выполнить один из этих склонных к тупику запросов.

0 голосов
/ 05 октября 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...