Відстеження, налагодження та виправлення вмісту блокування рядків


12

Пізно пізно, я зіткнувся з великою кількістю зміст блокування рядків. Таблиця, заперечувана, здається, є певною таблицею.

Це, як правило, те, що відбувається -

  • Розробник 1 починає транзакцію з переднього екрану Oracle Forms
  • Розробник 2 починає іншу транзакцію з іншого сеансу, використовуючи той самий екран

~ Через 5 хвилин передній кінець здається невідповідним. Перевірка сеансів показує суперечність блокування рядків. "Рішення", яке всі кидають, - це вбити сеанси: /

Як розробник бази даних

  • Що можна зробити, щоб виключити зміст блокування рядків?
  • Чи можна було б дізнатися, який рядок збереженої процедури викликає ці зміст блокування рядків
  • Що було б загальним керівництвом щодо зменшення / уникнення / усунення таких проблем, які кодують?

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


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


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


Оновлення: кількість рядків таблиці більша, ніж я очікував, приблизно на 3,1 мільйона рядків. Крім того, після простеження сеансу я виявив, що пара операторів оновлення до цієї таблиці не використовує індекс. Чому це так - я не впевнений. Стовпець, на який посилається пункт де індексується. Зараз я відновлюю індекс.


1
@Sathya - чи можете ви скласти складність збереженої процедури? чи підозрювана таблиця суворо оновлена ​​чи вставлена?
CoderHawk

Тут грають роль зовнішні ключі? (Іноді для цього потрібен індекс) Яка версія бази даних існує? Чи виконується потік логіки в одному порядку в обох сеансах? Чи тривалий час транзакція зберігається "відкритою"? Чи відбувається блокування під час, коли користувачі думають час або під час активного виконання транзакції?
ik_zelf

@Sandy Я оновив питання
Sathyajith Bhat

@ik_zelf Я оновив питання
Sathyajith Bhat

1
Мені не зрозуміло, чому це проблема - Oracle робить саме те, що повинен робити, а це серіалізує доступ до одного ряду. Якщо у когось є цей рядок, ви можете прочитати попередню версію його, але, щоб написати, вам доведеться почекати, коли він вимкне замок. Єдине "виправлення" для цього - або а) не обдурити і, COMMITабо ROLLBACKв розумний час, або b) влаштувати так, що одні і ті ж люди не завжди хочуть одного і того ж ряду одночасно.
Гай

Відповіді:


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

Після обох заяв модифікації session1 були перезаписані, без сеансу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- я виявив першопричину

  • Причиною, не дивно, була помилка логіки
  • Нещодавно до ІП було додано заяву про оновлення. Заява оновлення в основному оновлює всю таблицю. Мабуть, розробник, про який йдеться, забув про додавання права, де передбачено оновлення необхідних операторів.
  • Оновлена ​​таблиця була, як було сказано вище, однією з найбільш перетворених таблиць і мала велику кількість записів. Оновлення займе довгий, мучний час.
  • Результатом було те, що інші сеанси не змогли дістати замок на стіл і сидітимуть у змісті блокування рядків.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.