Отслеживание, отладка и исправление Row Lock Contentions

12

Поздно поздно я столкнулся с множеством споров о блокировке строк. Стол в споре, кажется, является конкретной таблицей.

Это вообще то, что происходит -

  • Разработчик 1 запускает транзакцию с экрана интерфейса Oracle Forms
  • Разработчик 2 запускает другую транзакцию из другого сеанса, используя тот же экран

~ 5 минут через, передний конец кажется не отвечает. Проверка сеансов показывает конфликт блокировки строк. «Решение», которое каждый выбрасывает, - убивать сессии: /

Как разработчик базы данных

  • Что можно сделать, чтобы устранить проблемы блокировки строк?
  • Можно ли выяснить, какая строка хранимой процедуры вызывает эти споры о блокировке строки
  • Каково будет общее руководство для уменьшения / предотвращения / устранения таких проблем, которые связаны с кодированием?

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


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


Версия базы данных - Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64-разрядная версия. Поток логики выполняется в одном и том же порядке в обеих сессиях, транзакция не остается открытой слишком долго (или, по крайней мере, мне так кажется ), и блокировки происходят во время активного выполнения транзакций.


Обновление: количество строк в таблице больше, чем я ожидал, около 3,1 миллиона строк. Кроме того, после отслеживания сеанса я обнаружил, что пара операторов обновления для этой таблицы не использует индекс. Почему это так - я не уверен. Столбец, указанный в предложении where, индексируется. В настоящее время я перестраиваю индекс.

Сатьяджит Бхат
источник
1
@ Сатья - можешь ли ты уточнить сложность хранимой процедуры? Подозреваемая таблица подвергается тщательному обновлению или вставке?
CoderHawk
Здесь играют роль внешние ключи? (Иногда для этого нужен индекс). Какая версия базы данных используется? Поток логики выполняется в одном и том же порядке в обеих сессиях? Сохраняется ли транзакция открытой в течение длительного времени? Блокировка происходит во время размышления пользователей или во время активного выполнения транзакции?
ik_zelf
@Sandy Я обновил вопрос
Сатьяджит Бхат
@ik_zelf Я обновил вопрос
Сатьяджит Бхат
1
Мне не ясно, почему это проблема - Oracle делает именно то, что должен, то есть сериализацию доступа к одной строке. Если у кого-то есть эта строка, вы можете прочитать ее предыдущую версию, но чтобы написать, вам нужно подождать, пока они снимут блокировку. Единственное «исправление» для этого заключается в том, чтобы либо: а) не дурачиться и / COMMITили ROLLBACKв разумные сроки, либо б) устроить так, чтобы одни и те же люди не всегда хотели один и тот же ряд в одно и то же время.
Гай

Ответы:

10

Можно ли выяснить, какая строка хранимой процедуры вызывает эти споры о блокировке строки?

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

SELECT sid, sql_text
FROM v$session s
LEFT JOIN v$sql q ON q.sql_id=s.sql_id
WHERE state = 'WAITING' AND wait_class != 'Idle'
AND event = 'enq: TX - row lock contention';

Каким будет общее руководство, чтобы уменьшить / избежать / устранить такие проблемы с кодированием?

В разделе «Руководство по концепциям Oracle» о блокировках говорится: «Строка блокируется только при изменении ее автором». Другой сеанс, обновляющий эту же строку, будет ожидать первого сеанса до COMMITили ROLLBACKдо его продолжения. Чтобы устранить проблему, вы можете сериализовать пользователей, но вот некоторые вещи, которые могут уменьшить проблему, возможно, до уровня, когда она не будет проблемой.

  • COMMITпочаще. Каждый COMMITвыпуск блокируется, поэтому, если вы можете делать обновления в пакетах, вероятность того, что другой сеанс будет нуждаться в той же строке, уменьшается.
  • Убедитесь, что вы не обновляете строки без изменения их значений. Например, UPDATE t1 SET f1=DECODE(f2,’a’,f1+1,f1);должен быть переписан как более избирательный (читайте меньше блокировок) UPDATE t1 SET f1=f1+1 WHERE f2=’a’;. Конечно, если изменение оператора по-прежнему блокирует большинство строк в таблице, то изменение принесет пользу только для удобства чтения.
  • Убедитесь, что вы используете последовательности, а не блокируете таблицу, чтобы добавить ее к максимальному текущему значению.
  • Убедитесь, что вы не используете функцию, из-за которой индекс не используется. Если функция необходима, подумайте о том, чтобы сделать ее индексом на основе функций.
  • Думай в наборах. Подумайте, можно ли переписать цикл, выполняющий блок PL / SQL, выполняющий обновления, как один оператор обновления. Если нет, то, возможно, массовая обработка может быть использована с BULK COLLECT ... FORALL.
  • Сократить работу, которая делается между первым UPDATEи COMMIT. Например, если код отправляет сообщение электронной почты после каждого обновления, рассмотрите возможность размещения сообщений в очереди и отправки их после фиксации обновлений.
  • Разработайте приложение для обработки ожидания, выполнив SELECT ... FOR UPDATE NOWAITили WAIT 2. Затем вы можете поймать невозможность заблокировать строку и сообщить пользователю, что другой сеанс изменяет те же данные.
Ли Риффель
источник
7

Я предоставлю ответ с точки зрения разработчика.

По моему мнению, когда вы сталкиваетесь с конфликтом строк, таким как описанный вами, это происходит из-за ошибки в вашем приложении. В большинстве случаев этот тип разногласий является признаком уязвимости с потерянным обновлением. Эта ветка на AskTom объясняет концепцию потерянного обновления:

Потерянное обновление происходит, когда:

Сессия 1: зачитать запись сотрудника Тома

Сессия 2: зачитать запись сотрудника Тома

Сессия 1: обновить запись сотрудника Тома

Сессия 2: обновление записи сотрудника Тома

Сессия 2 ЗАПИСИТ изменения сессии 1, даже не увидев их, что приведет к потере обновления.

Вы испытали один неприятный побочный эффект потерянного обновления: сеанс 2 может быть заблокирован, так как сеанс 1 еще не зафиксирован. Однако главная проблема заключается в том, что сессия 2 слепо обновляет запись. Предположим, что обе сессии выдают утверждение:

UPDATE table SET col1=:col1, ..., coln=:coln WHERE id = :pk

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


Потерянное обновление (и побочный эффект конкуренции) никогда не должно произойти, их можно избежать на 100%. Вы должны использовать блокировку, чтобы предотвратить их двумя основными методами: оптимистическая и пессимистическая блокировка .

1) Пессимистичная блокировка

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

2) Оптимистическая блокировка

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

SELECT <...>
  FROM table
 WHERE id = :id
   AND marker = :marker
   FOR UPDATE NOWAIT

Если запрос возвращает строку, сделайте обновление. Если это не так, это означает, что кто-то изменил строку с момента последнего запроса. Вам придется перезапустить процесс с самого начала.

Примечание. Если вы полностью доверяете всем приложениям, обращающимся к вашей БД, вы можете рассчитывать на прямое обновление для оптимистической блокировки. Вы можете оформить напрямую:

UPDATE table
   SET <...>, 
       marker = marker + 1
 WHERE id = :id;

Если инструкция не обновляет строку, вы знаете, что кто-то изменил эту строку, и вам нужно начинать все сначала.

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

TL; DR

  • Обновление строки без предварительной блокировки подвергает приложение потенциальному «зависанию». Этого можно избежать, если все DML для БД реализуют оптимистическую или пессимистическую блокировку.
  • Убедитесь, что оператор SELECT возвращает значения, согласующиеся с любым предыдущим SELECT (чтобы избежать проблем с потерянным обновлением)
Винсент Малграт
источник
5

Этот ответ, вероятно, будет претендовать на участие в The Daily WTF.

Хорошо, после отслеживания сессий и поиска USER_SOURCE- я выследил причину

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