ВИБЕРІТЬ ДЛЯ ОНОВЛЕННЯ за допомогою SQL Server


79

Я використовую базу даних Microsoft SQL Server 2005 з рівнем ізоляції READ_COMMITTEDта READ_COMMITTED_SNAPSHOT=ON.

Тепер я хочу використовувати:

SELECT * FROM <tablename> FOR UPDATE

... так що інші підключення до бази даних блокуються при спробі отримати доступ до того самого рядка "ДЛЯ ОНОВЛЕННЯ".

Я намагався:

SELECT * FROM <tablename> WITH (updlock) WHERE id=1

... але це блокує всі інші з'єднання навіть для вибору ідентифікатора, відмінного від "1".

Який правильний підказка робити, SELECT FOR UPDATEяк відомо для Oracle, DB2, MySql?

EDIT 2009-10-03:

Це оператори для створення таблиці та індексу:

CREATE TABLE example ( Id BIGINT NOT NULL, TransactionId BIGINT, 
    Terminal BIGINT, Status SMALLINT );
ALTER TABLE example ADD CONSTRAINT index108 PRIMARY KEY ( Id )
CREATE INDEX I108_FkTerminal ON example ( Terminal )
CREATE INDEX I108_Key ON example ( TransactionId )

Багато паралельних процесів роблять це SELECT:

SELECT * FROM example o WITH (updlock) WHERE o.TransactionId = ?

РЕДАКТУВАТИ 05.10.2009:

Для кращого огляду я записав усі випробувані рішення в наступну таблицю:

механізм | SELECT на різних блоках рядків | SELECT на тих самих рядкових блоках
----------------------- + -------------------------- ------ + --------------------------
ROWLOCK | ні | ні
updlock, rowlock | так | так
xlock, rowlock | так | так
повторюванечитання | ні | ні
DBCC TRACEON (1211, -1) | так | так
рядок, xlock, затримка | так | так
оновлення, затримка | так | так
UPDLOCK, READPAST | ні | ні

Я шукаю | ні | так

Я шукаю не лише підказки щодо оптимізатора. Іншим можливим рішенням може бути зміна рівня ізоляції, атрибутів глобальної бази даних, ... Все (але використовувати іншу базу даних) можливо.
tangens

4
Що ви намагаєтесь зробити, що потребує такого блокування. Зазвичай краще вирішувати за допомогою правильних запитів, а не «функцій» сервера
TFD,

2
Чи можете ви надати запит, який ви використовуєте, та DDL таблиці (таблиць), включаючи будь-які Ключі та Індекси.
RBarryYoung

Ви впевнені, що ваш інший запит не є ізоляцією транзакції, прочитаною без зобов'язання?
Натан Фегер,

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

Відповіді:


34

Нещодавно у мене виникла проблема з тупиком, оскільки сервер Sql блокує більше, ніж потрібно (сторінка). Ви насправді нічого не можете зробити проти цього. Зараз ми ловимо винятки із тупикових ситуацій ... і я би хотів, щоб замість них я мав Oracle.

Редагувати: Тим часом ми використовуємо ізоляцію знімків, яка вирішує багато, але не всі проблеми. На жаль, щоб мати можливість використовувати ізоляцію знімків, це має бути дозволено сервером баз даних, що може спричинити непотрібні проблеми на сайті клієнтів. Зараз ми не лише ловимо винятки із тупикових ситуацій (які, звичайно, все ще можуть траплятися), але й проблеми одночасності знімків, щоб повторити транзакції з фонових процесів (які користувач не може повторити). Але це все одно працює набагато краще, ніж раніше.


Боюсь, це правда. Я не знайшов способу отримати діючий "ВИБІР ДЛЯ ОНОВЛЕННЯ". Зараз моє рішення полягає в тому, щоб відмовитись від «ВИБРАТИ ДЛЯ ОНОВЛЕННЯ» та виконати простий, не блокуючий «ВИБІР» та перевірити наявність одночасних «ОНОВЛЕНЬ» за допомогою лічильника оновлення («ОНОВИТИ ДЕ, де id =? Та updateCount =?»).
tangens

1
SQL 2008 забезпечує два нові оптимістичні рівні ізоляції, подібні до того, що пропонує Oracle
Кріс Беднарський,

@ChrisBednarski я використовував WITH (ROWLOCK) у SQL Server 2008 R2, і він все одно заблокував більше одного рядка !!! Ви можете пояснити
bjan

2
@bjan: шукати ізоляцію знімків. Деяка інформація тут msdn.microsoft.com/en-us/library/tcbchxcb(v=vs.80).aspx
Кріс Беднарський,

1
@ChrisBednarski дякую за посилання, ці дві команди змінили день і ніч: ALTER DATABASE MyDatabase SET ALLOW_SNAPSHOT_ISOLATION ON; ALTER DATABASE MyDatabase SET READ_COMMITTED_SNAPSHOT ON; див. також відповідь MichaelBuen на це питання.
Ласло ван ден Хук

22

У мене схожа проблема, я хочу заблокувати лише 1 рядок. Наскільки мені відомо, сUPDLOCK опції SQLSERVER блокує всі рядки, які йому потрібно прочитати, щоб отримати рядок. Отже, якщо ви не визначите індекс для прямого доступу до рядка, усі попередні рядки будуть заблоковані. У вашому прикладі:

Припустимо, у вас є таблиця з назвою TBL із idполем. Ви хочете зафіксувати рядок за допомогою id=10. Вам потрібно визначити індекс для ідентифікатора поля (або будь-якого іншого поля, яке бере участь у вибраному вами):

CREATE INDEX TBLINDEX ON TBL ( id )

А потім, ваш запит на блокування ТІЛЬКИ тих рядків, які ви прочитали:

SELECT * FROM TBL WITH (UPDLOCK, INDEX(TBLINDEX)) WHERE id=10.

Якщо ви не використовуєте опцію INDEX (TBLINDEX), SQLSERVER повинен прочитати всі рядки з початку таблиці, щоб знайти ваш рядок id=10, тому ці рядки будуть заблоковані.


+1 за надання першопричини, чому SQL Server "ігнорує" директиву блокування рядків. Я використав цей підхід у своєму додатку і справді досяг того, про що вимагає тангенс
Кот

-1 У мене був кластерний індекс, і включення того, що всередині підказок, не мало значення. Updlock + rowlock + holdlock чудово спрацювали, як тільки я зрозумів, що помилково торкався інших записів минулого блокування оператора SELECT у моєму sp (бракувало умови на ідентифікаторах, яка була в оригіналі select).
MoonStom

8

Ви не можете одночасно ізолювати знімання та блокувати читання. Метою ізоляції знімків є запобігання блокуванню зчитування.


5

Спробуйте (updlock, rowlock)


Так, я спробував блокування рядків (без поєднання з іншими підказками), але тоді одночасний ВИБІР ДЛЯ ОНОВЛЕННЯ того ж рядка не блокується.
tangens

Добре, я спробував (updlock, rowlock), але другий ВИБІР ДЛЯ ОНОВЛЕННЯ блокує навіть при зверненні до іншого рядка.
tangens

(xlock, rowlock) блокує й інші рядки.
tangens

Чому ви вважаєте, що це має блокувати?
RBarryYoung

1
Це песимістичний паралелізм. Якщо ви цього хочете, то чому ви вказуєте оптимістичну паралельність?
RBarryYoung,

5

Повна відповідь може заглибитися у внутрішні частини СУБД. Це залежить від того, як працює механізм запитів (який виконує план запитів, сформований оптимізатором SQL).

Однак одне з можливих пояснень (застосовних принаймні до деяких версій деяких СУБД - не обов'язково до MS SQL Server) полягає в тому, що в стовпці ідентифікатора немає індексу, тому будь-який процес, який намагається виконати запит із символом ` WHERE id = ?` в ньому ' ', закінчується послідовне сканування таблиці, і це послідовне сканування потрапляє в замок, який застосував ваш процес. Ви також можете зіткнутися з проблемами, якщо СУБД застосовує блокування на рівні сторінки за замовчуванням; блокування одного рядка блокує всю сторінку та всі рядки на цій сторінці.

Є кілька способів, як ви можете розвінчати це як джерело проблем. Подивіться на план запитів; вивчити показники; спробуйте свій SELECT з ідентифікатором 1000000 замість 1 і перевірте, чи інші процеси все ще заблоковані.


Тож це залежить від MS SQL Server; можливо, це не пропустить блокування в індексі. Я пропоную спробувати тест 'ID = <large-number>' на достатньо великому наборі даних, що у вас використовується кілька сторінок. Ви можете помітити різницю; ви не могли б. Ви можете спробувати запустити "інші процеси" на рівні ізоляції "брудного читання"; це повинно пройти повз замків для читання - але це не дуже гарне рішення загалом.
Джонатан Леффлер

Усі процеси виконують одне і те ж читання: вони вибирають "свій" запис і оновлюють статус в кінці операції.
tangens

1
Отже, значення для ID однакове для кожного процесу? Або кожен з них використовує різні посвідчення особи? Я припускаю останнє. І питання полягає в тому, чи дозволить СУБД достатньо різні значення ідентифікатора, щоб обійти будь-яке блокування. Я припускаю, що якщо значення ідентифікатора досить різні, ви отримаєте минулі замки. Але не в тому випадку, якщо індекс на ідентифікаторі не використовується, наприклад. Ви вже розглядали плани запитів?
Джонатан Леффлер

На жаль, я ще не зміг зрозуміти, як це зробити. І так, кожен процес використовує інший ідентифікатор.
tangens

Так, я переглянув план запитів, але нічого особливого не побачив.
tangens

5

можливо, створення постійного mvcc може вирішити це (на відміну лише від конкретної партії: ВСТАНОВИТИ РІВЕНЬ РІВНЯ ІЗОЛЯЦІЇ ОПЕРАЦІЇ):

ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON;

[РЕДАКТУВАТИ: 14 жовтня]

Прочитавши це: Краща паралельність в Oracle, ніж SQL Server? і це: http://msdn.microsoft.com/en-us/library/ms175095.aspx

Коли опцію бази даних READ_COMMITTED_SNAPSHOT увімкнено, механізми, що використовуються для підтримки опції, активуються негайно. При встановленні параметра READ_COMMITTED_SNAPSHOT у базі даних дозволяється лише підключення, що виконує команду ALTER DATABASE. У базі даних не повинно бути іншого відкритого підключення, поки ALTER DATABASE не буде завершено. База даних не повинна знаходитися в однокористувацькому режимі.

я дійшов висновку, що вам потрібно встановити два прапори, щоб постійно активувати MVCC mssql у даній базі даних:

ALTER DATABASE yourDbNameHere SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON;

Прочитавши коментар @Chris Bednarski's під прийнятою відповіддю, я дійшов того ж висновку, що і ти, Майкл. Здається, це позбавляє багатьох питань, що зайшли у глухий кут. Якщо ви використовуєте діалогове вікно властивостей бази даних із SQL Server Management Studio, вам буде запропоновано відключити всі клієнти, і перезапуск не потрібен.
Ласло ван ден Хук

3

Добре, один вибраний wil за замовчуванням використовує ізоляцію транзакції "Read Committed", яка блокує і, отже, зупиняє запис у цей набір. Ви можете змінити рівень ізоляції транзакції за допомогою

Set Transaction Isolation Level { Read Uncommitted | Read Committed | Repeatable Read | Serializable }
Begin Tran
  Select ...
Commit Tran

Вони детально пояснюються в SQL Server BOL

Ваша наступна проблема полягає в тому, що за замовчуванням SQL Server 2K5 посилить блокування, якщо у вас більше ~ 2500 блокувань або ви використовуєте більше 40% "звичайної" пам'яті в транзакції блокування. Ескалація переходить до сторінки, а потім блокування таблиці

Ви можете вимкнути цю ескалацію, встановивши "прапор трасування" 1211t, див. BOL для отримання додаткової інформації


Щиро дякую, це звучить так, ніби це може бути рішенням. Спробую в понеділок.
tangens

Я спробував, але це не спрацювало. Другий SELECT все ще блокується при доступі до іншого рядка.
tangens

3

Створіть підроблене оновлення, щоб забезпечити блокування рядків.

UPDATE <tablename> (ROWLOCK) SET <somecolumn> = <somecolumn> WHERE id=1

Якщо це не блокує ваш рядок, Бог знає, що буде.

Після цього " UPDATE" ви можете робити своє SELECT (ROWLOCK)та наступні оновлення.


Цікаво, чи працює це гірше, ніж альтернатива, тобто просто зробити вибране з підказкою xlock. Rowlock не є надійним, оскільки SQL Server фактично не блокує рядки, він блокує 6-байтові хеші рядків, тому існує ймовірність зіткнення з великою кількістю записів у таблиці. Крім того, оскільки блокування рядків є "підказкою", це не гарантує ескалації блокування, але, можливо, ви зможете мінімізувати цей ризик, вимкнувши ескалацію блокування на столі.
Трійко

2

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

Загортання SELECT у транзакцію під час використання підказки блокування WITH (XLOCK, READPAST) отримає бажані результати. Просто переконайтеся, що інші паралельні читання НЕ використовують WITH (NOLOCK). READPAST дозволяє іншим сеансам виконувати те саме SELECT, але в інших рядках.

BEGIN TRAN
  SELECT *
  FROM <tablename> WITH (XLOCK,READPAST) 
  WHERE RowId = @SomeId

  -- Do SOMETHING

  UPDATE <tablename>
  SET <column>=@somevalue
  WHERE RowId=@SomeId
COMMIT

2

Питання - чи доведено, що цей випадок є результатом ескалації блокування (тобто, якщо ви прослідкуєте за допомогою профілі для подій ескалації блокування, чи точно це саме спричиняє блокування)? Якщо так, є повне пояснення та (досить екстремальне) обхідне рішення, увімкнувши прапор трасування на рівні екземпляра, щоб запобігти нарощуванню блокування. Див. Http://support.microsoft.com/kb/323630 прапор трасування 1211

Але це, ймовірно, матиме небажані побічні ефекти.

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

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


Погоджено, хоча я волію піти на оптимістичний підхід блокування створення версій рядків, як це робиться в рівнях / стійкості ORM, таких як Hibernate, див .: codippa.com/ ... Це передбачає контрольоване середовище, де оновлення проходять через певний додаток, або що кілька додатків застосовують однаковий підхід. Ця стаття висвітлює більшість сценаріїв, включаючи тип даних "рядок версій" SQL Server: simple-talk.com/sql/t-sql-programming/…
Камал,

2

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


Блокування додатків, на жаль, не завжди допомагає, коли йдеться про проблеми з глухим кутом. Незалежно від того, що ви заблокували за допомогою sp_getapplock, коли ви фактично йдете і починаєте модифікувати записи бази даних, вам доведеться боротися з тим, що механізм починає блокувати рядки, і ненавмисно блокує не пов'язані між собою рядки в результаті ескалації блокування сторінок або таблиці, які можуть зіткнутися з одночасними транзакціями. Найкраще вимкнути ескалацію блокування та вказати ексклюзивні підказки щодо блокування рядків, а також блокувати рядки у визначеному порядку блокування, щоб максимально уникнути блокування.
Трійко

Я б скоріше вибрав оптимістичний підхід блокування створення версій рядків, як це робиться в ORM / рівнях стійкості, таких як Hibernate, див .: codippa.com/ ... Це передбачає контрольоване середовище, де оновлення проходять через конкретну програму, або що кілька програми застосовують той самий підхід. Ця стаття висвітлює більшість сценаріїв, включаючи конкретний тип даних "рядок" SQL Server та власну пропозицію sp_getapplock
Камал

1

Спробуйте використати:

SELECT * FROM <tablename> WITH ROWLOCK XLOCK HOLDLOCK

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


(rowlock, xlock, holdlock) блокує й інші рядки.
tangens

Чи є спосіб запобігти блокуванню інших рядків? Зараз у мене немає екземпляра SQL Server, щоб перевірити його. Можливо UPDLOCK XLOCK HOLDLOCK?
RMorrisey

(updlock, xlock) - несумісні підказки.
tangens

Спробуйте просто UPDLOCK HOLDLOCK?
RMorrisey

(updlock, holdlock) блокує й інші рядки.
tangens

1

Відповідно до цієї статті рішення полягає у використанні підказки WITH (REPEATABLEREAD).


Гарна стаття, дякую. Тепер я все розумію трохи краще. Але, на жаль, підказка (repeatableread) не блокує другий вибір того самого рядка.
tangens

tangens: Чому ти думаєш, що так слід?
RBarryYoung

Бо саме таку поведінку я шукаю.
tangens

Так, але чому ви вважаєте, що налаштування, якими ви користуєтесь, повинні мати таку особливу поведінку?
RBarryYoung

6
Я так не думаю. Я шукаю рішення, яке дає мені бажану поведінку.
tangens

1

Знову перегляньте всі свої запити, можливо, у вас є якийсь запит, який вибирається без підказки ROWLOCK / FOR UPDATE з тієї самої таблиці, яку ви маєте SELECT FOR UPDATE.


MSSQL часто ескалює ці блокування рядків до блокування на рівні сторінки (навіть блокування на рівні таблиці, якщо у вас немає індексу в полі, яке ви запитуєте), див. Це пояснення . Оскільки ви запитуєте ЗА ОНОВЛЕННЯ, я міг би припустити, що вам потрібна надійність на рівні транзакції (наприклад, фінансова, інвентаризаційна тощо). Тож поради на цьому сайті не стосуються вашої проблеми. Це лише розуміння того, чому MSSQL нарощує замки .


Якщо ви вже використовуєте MSSQL 2005 (і новіших версій), вони засновані на MVCC, я думаю, у вас не повинно виникнути проблем із блокуванням на рівні рядка за допомогою підказки ROWLOCK / UPDLOCK. Але якщо ви вже використовуєте MSSQL 2005 і новіших версій, спробуйте перевірити деякі з ваших запитів, які надсилають запити до тієї самої таблиці, яку ви хочете ЗАЛУЖИТИ, якщо вони посилюють блокування, перевіряючи поля в їх реченні WHERE, якщо вони мають індекс.


PS
Я використовую PostgreSQL, він також використовує MVCC є FOR UPDATE, я не стикаюся з тією ж проблемою. Ескалація блокування - це те, що вирішує MVCC, тому я був би здивований, якби MSSQL 2005 все ще нарощував блокування в таблиці з реченнями WHERE, які не мають індексу в своїх полях. Якщо це (ескалація блокування) все ще має місце для MSSQL 2005, спробуйте перевірити поля в реченнях WHERE, якщо вони мають індекс.

Застереження: моє останнє використання MSSQL - лише версія 2000.


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

1

Вам доведеться мати справу з винятком під час фіксації та повторити транзакцію.


1

Я вирішив проблему з блокуванням рядків зовсім по-іншому. Я зрозумів, що сервер sql не зміг керувати таким блокуванням задовольняючим чином. Я вирішив вирішити це з програмної точки зору, використовуючи мьютекс ... waitForLock ... releaseLock ...


4
Це працює до тих пір, поки лише одному серверу потрібно оновити базу даних.
Andrew Swan

0

Ви пробували READPAST?

Я використовував UPDLOCK і READPAST разом, коли обробляв таблицю як чергу.


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

0

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

UPDATE dbo.Customer SET FieldForLock = FieldForLock WHERE CustomerID = @CustomerID
/* do whatever you want */

Змінити : звичайно, вам слід обернути це транзакцією

Редагування 2 : іншим рішенням є використання рівня СЕРІАЛІЗОВАНОЇ ізоляції


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