Які основні причини тупиків і чи можна їх запобігти?


55

Нещодавно в одному з наших програм ASP.NET з'явилася помилка в тупиковій базі даних, і мені було запропоновано перевірити та виправити помилку. Мені вдалося знайти причину тупику - це збережена процедура, яка суворо оновлювала таблицю в межах курсору.

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

Пізніше я знайшов розробника, який написав базу даних для розгортання. Я додав первинний ключ, і проблема була вирішена.

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

Мабуть, саме круговий стан очікування спричинив тупик. Оновлення, мабуть, тривають довше без первинного ключа, ніж із первинним ключем.

Я знаю, що це не чітко визначений висновок, тому я публікую тут ...

  • Чи проблема з відсутнім первинним ключем?
  • Чи є інші умови, які спричиняють тупик, окрім (взаємне виключення, затримка та чекання, відсутність попередження та кругового очікування)?
  • Як запобігти та відстежити тупикові місця?

2
Більшість IME (усі?) Тупиків, які я бачив, трапляються через кругові очікування (головним чином через надмірне використання пускових механізмів).
Сатьядхіт Бхат

Циркулярність - одна з необхідних умов тупикової ситуації. Ви можете уникнути будь-якого глухого кута, якщо всі ваші сеанси набувають блокування в одному порядку.
Петро Григорович

Відповіді:


38

відстеження тупиків легше з двох:

За замовчуванням тупикові місця не записуються в журнал помилок. Ви можете змусити SQL писати тупики в журнал помилок із прапорами 1204 та 3605 слідів.

Запишіть інформацію про тупик у журнал помилок SQL Server: DBCC TRACEON (-1, 1204, 3605)

Вимкніть його: TRACEOFF DBCC (-1, 1204, 3605)

Див. "Виправлення неполадок" для обговорення прапора 1204 про слідування та вихід, який ви отримаєте при його включенні. https://msdn.microsoft.com/en-us/library/ms178104.aspx

Профілактика складніше, по суті, ви повинні стежити за наступним:

Блок 1 коду блокує ресурс A, потім ресурс B у такому порядку.

Блок 2 коду блокує ресурс B, потім ресурс A у такому порядку.

Це класична умова, коли може статися глухий кут, якщо блокування обох ресурсів не є атомним, блок 1 блоку коду може заблокувати A і попередньо зняти його, тоді блок коду 2 блокує B, перш ніж A поверне час обробки. Тепер у вас тупик.

Щоб запобігти цьому стану, ви можете зробити щось на кшталт наступного

Блок коду A (код psuedo)

Lock Shared Resource Z
    Lock Resource A
    Lock Resource B
Unlock Shared Resource Z
...

Блок коду B (псевдокод)

Lock Shared Resource Z
    Lock Resource B
    Lock Resource A
Unlock Shared Resource Z
...

не забуваючи розблокувати A і B, коли закінчите з ними

це запобігло б тупінг між кодовим блоком A і кодом B

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


Ви мали на увазі заблокувати ресурс A перед ресурсом B у блоці коду B? Як написано, це спричинить тупики .. як ви самі згадуєте в коментарях раніше. Наскільки це можливо, ви хочете заблокувати ресурси в одному порядку завжди, навіть якщо вам потрібні фіктивні запити на початку, щоб забезпечити цей порядок блокування.
Джерард ONeill

23

мої улюблені статті для читання та дізнання про тупики: Прості розмови - Відстеження тупиків та центральний сервер SQL Server - Використання Профілера для вирішення тупикових ситуацій . Вони дадуть вам зразки та поради щодо того, як впоратися з висмоктуванням ситуації.

Коротше кажучи, щоб вирішити поточну проблему, я би зробив скорочені транзакції, вийняв із них непотрібну частину, подбав про порядок використання об’єктів, подивився, який рівень ізоляції насправді потрібен, а не читати зайве дані ...

Але краще прочитайте статті, вони будуть набагато приємнішими в порадах.


16

Іноді тупик може бути вирішений шляхом додавання індексації, оскільки це дозволяє базі даних фіксувати окремі записи, а не всю таблицю, тому ви зменшуєте суперечки та можливість речей заважати.

Наприклад, у InnoDB :

Якщо у вас немає індексів, відповідних для вашої операції, і MySQL повинен сканувати всю таблицю для обробки оператора, кожен рядок таблиці стає заблокованим, що в свою чергу блокує всі вставки інших користувачів до таблиці. Важливо створити хороші індекси, щоб ваші запити не зайво сканували багато рядків.

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

...

Що стосується їх відстеження, це залежить від програмного забезпечення бази даних, яке ви використовуєте.


Загальна люб’язність надавати коментарі при зворотному зверненні ... Це вірний варіант відповіді, вибране твердження, оновлення до блокування таблиці та прийняття назавжди, безумовно, може стати причиною тупику.
BlackICE

1
MS SQLServer також може надати несподівану поведінку блокування, якщо індекси не кластеризовані. Він мовчки ігнорує ваш напрямок використання блокування рівня рядків і зробить блокування рівня сторінки. Потім ви можете отримати тупикові очікування на сторінці.
Джей

7

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

Найкраще пройти рядки в моді курсору, використовуючи цикл while

Під час циклу while буде здійснено виділення для кожного ряду циклу, а блокування відбуватиметься лише в одному рядку. Решта даних у таблиці є безкоштовною для запитів, тим самим знижуючи ймовірність виникнення тупикової ситуації.

Плюс це швидше. Змушує замислитися, чому все одно є курсори.

Ось приклад такої структури:

DECLARE @LastID INT = (SELECT MAX(ID) FROM Tbl)
DECLARE @ID     INT = (SELECT MIN(ID) FROM Tbl)
WHILE @ID <= @LastID
    BEGIN
    IF EXISTS (SELECT * FROM Tbl WHERE ID = @ID)
        BEGIN
        -- Do something to this row of the table
        END

    SET @ID += 1  -- Don't forget this part!
    END

Якщо поле вашого ідентифікатора є рідким, ви можете спробувати окремий список ідентифікаторів та повторити його:

DECLARE @IDs TABLE
    (
    Seq INT NOT NULL IDENTITY PRIMARY KEY,
    ID  INT NOT NULL
    )
INSERT INTO @IDs (ID)
    SELECT ID
    FROM Tbl
    WHERE 1=1  -- Criteria here

DECLARE @Rec     INT = 1
DECLARE @NumRecs INT = (SELECT MAX(Seq) FROM @IDs)
DECLARE @ID      INT
WHILE @Rec <= @NumRecs
    BEGIN
    SET @ID = (SELECT ID FROM @IDs WHERE Seq = @Seq)

    -- Do something to this row of the table

    SET @Seq += 1  -- Don't forget this part!
    END

6

Відсутність первинного ключа - це не проблема. Принаймні сама собою. По-перше, вам не потрібен основний, щоб мати індекси. По-друге, навіть якщо ви робите сканування таблиці (що має статися, якщо ваш конкретний запит не використовує індекс, блокування таблиці саме по собі не спричинить тупик. Процес запису зачекає на прочитане, а процес читання чекайте написання, і, звичайно, читання взагалі не доведеться чекати один одного.

Додаючи до інших відповідей, рівень ізоляції транзакцій має значення, оскільки повторне читання та серіалізація - це те, що спричиняє блокування "читання", яке потрібно тримати до кінця транзакції. Блокування ресурсу не спричиняє тупик. Тримаючи його заблокованим, це робить. Операції з запису завжди зберігають свій ресурс заблокованим до кінця транзакції.

Моя улюблена стратегія запобігання блокуванню - це використання функцій "знімка". Функція "Здійснення читання знімків" означає, що для читання не використовуються блокування! І якщо вам потрібен більший контроль, ніж «Читання здійснено», є функція «Рівень ізоляції швидкого пострілу». Цей дозволяє здійснювати серіалізовані (тут використовуються терміни MS) транзакції, не блокуючи інших гравців.

Нарешті, один клас тупиків можна запобігти за допомогою блокування оновлення. Якщо ви прочитаєте та утримуєте прочитане (HOLD або використовуєте Повторне читання), а інший процес робить те саме, то обидва намагаються оновити однакові записи, у вас з’явиться тупик. Але якщо обидва вимагають блокування оновлення, другий процес буде чекати першого, дозволяючи іншим процесам читати дані, використовуючи спільні блокування, поки дані фактично не будуть записані. Це, звичайно, не буде працювати, якщо один із процесів все ще вимагає блокування загального HOLD.


-2

Хоча курсори повільні в SQL Server, ви можете уникнути тупикового блокування в курсорі, перетягнувши вихідні дані для курсора в таблицю Temp і запустивши курсор на ньому. Це утримує курсор від блокування фактичної таблиці даних, і єдині отримані вами блоки - це оновлення або вставки, виконані всередині курсору, які зберігаються лише протягом тривалості вставки / оновлення, а не протягом тривалості курсору.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.