Блокировки чтения SQL запросом with nolock

Продолжаем тему SQL. Однажды с одним программистом зашел разговор о блокировках, и я вскользь сказал, что есть такие блокировки на чтение данных. Он не поверил, а я доказать наличие блокировок не смог. Почему я вспомнил? Да просто в последнее время что-то у нас на сайте участились блокировки. За последние два дня пришлось решать 5 подобных проблем и в одном случае снова пришлось объяснять человеку, который проверяет качество кода, когда можно использовать антиблок, а когда нельзя.

Итак, любители Oracle просто закрывают эту заметку и высоко задрав нос спокойно пинают Microsoft обеими ногами. Их такая проблема не волнует. Почему? Даже по умолчанию Oracle работает так, что никакие блокировки не блокируют данные от чтения. Это значит, что вы можете держать открытыми транзакции столько, сколько душе угодно. Допустим, что вы запустили большую транзакцию, в которой обновили данные в таблице:

begin transaction
update tablename
set column1 = column1 * 10

Обратите внимание, что транзакция еще не завершена. Что будет, если какой-то другой пользователь выполнит запрос select * from tablename? Ничего страшного не произойдет. Запрос выполниться и пользователь должен получить в результате те данные, которые были до начала транзакции. Зашибись? Лебедев в этом моменте сказал бы более жестко, а я скажу так - офигенно. Это значит, что при использовании Oracle вы можете держать открытыми транзакции сколько душе угодно.

Вы можете блокировать данные помощью SELECT FOR UPDATE, только не забывайте указывать таймаут. Я так часто делал, когда нужно было узнать – заблокированы ли данные на изменение. Если заблокированы, то можно выполнять просто SELECT и никаких проблем. Если же запрос выполнился, то можно еще и изменять данные.

Теперь затачиваем копье, которое запустим адрес Microsoft SQL Server и большинства других баз дынных. Что будет, если транзакция заблокировала данные обновлением? Произойдет блокировка чтение. Запрос SELECT не будет выполнен, пока данные не освободятся, поэтому в этом сервере баз данных нельзя держать блокировки слишком долго, по крайней мере до SQL Server 2005. На счет 2008-й версии не знаю. Если на базу данных идет большое количество обновлений, то некоторые даже очень простые запросы могут выполняться долго.

Если у вас классическое приложение, то это не проблема, бухгалтера подождут, если это не день выдачи зарплаты. Но если это Web сайт, то тут уже проблема не только в скорости ответа, но и в безопасности. Если пользователю удастся выполнить UPDATE или найти такую страничку сайта, которая делает массовое обновление и вызвать ее непрерывно в цикле, то можно уложить сайт с помощью DoS без особого напряга. Запросы SELECT будут стоять в очереди на освобождение данных долго и печально.

Проблема решается двумя способами – глобально и локально. Глобальный способ, это изменение уровня изоляции данных. Можно разрешить грязное чтение, и тогда ничего блокироваться не будет, но эту фишку делать никогда нельзя. Нужно работать с каждым случаем отдельно, и особо клинические решать не доводя до морга.

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

begin transaction
update tablename
set column1 = column1 * 10

Если теперь выполнить:

Select * 
from tablename with (nolock)

В отличии от Oracle, данный запрос вернет не те данные, которые были до начала транзакции, а уже обновленные данные, но не закоммиченные. Это значит, что если транзакция откатится, то пользователь видит фуфло.

И что же тогда делать? Если вы заточили копье, то самое время сейчас запустить его в сторону Microsoft. И вот тут читаем интернет и думаем, что же делать? Один лагерь профессионалов говорит, что нужно использовать nolock и нефиг бояться, потому что это производительность и надежность, а другие говорят, что это зло, и нужно бороться с причиной, а не результатом. Что считаю я? Я как всегда посередине, потому что никогда не кидаюсь в крайности и ни к каким лагерям не принадлежу. Включаем мозг, и начинаем работать.

Допустим, что ваша транзакция большая и обновляет громадную базу данных, совершая сначала приход товара, а потом расход (или наоборот, мне пофиг, ведь все равно все идет в транзакции). Теперь допустим, что ваш запрос SELECT должен получать количество определенного товара на складе посетителю. Можно использовать nolock? Да без базару. Чтобы сайт не лег на время выполнения транзакции, можно использовать nolock и спать спокойно, ничего плохого в этом нет. Даже если посетитель увидит на время неверные данные о состоянии склада, ничего с ним не случиться. Интернет большой, всегда можно списать на динамичность жизни. Когда он через минуту увидит уже другие значения. Зато сайт свистит, ничего не блокируется, и все прекрасно работает.

А что, если ваш запрос возвращает количество товара, но теперь уже не просто для отображения на странице, а для того, чтобы определить, можно оформлять заказ или нет. Вот тут nolock использовать нельзя. Если запрос вышел по таймауту, то нужно сказать пользователю: «С новым годом! Пошел нафиг!», или лучше сообщите, что сервис отгрузки временно недоступен. Тут уже ошибку допускать не желательно, ведь если оформить заказ на то, чего нет, морду от торта потом не отмоешь, а кто-нибудь этот торт обязательно запустит.

Это только пример логики, когда можно использовать грязное чтение, а когда нет. Опция nolock позволяет спасти сайт от нежного и ласкового отдыха, когда на сервере выполняется долгоиграющие ириски, т.е. транзакции, но ее нельзя использовать тупо. Как я всегда говорю, не нужно ничего использовать тупо, потому что это тупо. В каждом отдельном случае нужно разбираться и выносить конкретное решение. В случае с MS SQL сервером, желательно делать транзакции как можно короче и отпускать данные как можно скорее. Остальные сервера как-то не проверял, потому что работал с ними редко, так что эксперимент вам в руки и проверяйте, на сколько вы счастливы.


Понравилось? Кликни Лайк, чтобы я знал, какой контент более интересен читателям. Заметку пока еще никто не лайкал и ты можешь быть первым


Комментарии

Юрий

15 Мая 2010

В Oracle, InterBase&FB многоверсионная архитектура(чтение подтверж. транзакций), а в MS SQL этого нет, with (nolock) - грязное чтение


Михаил Фленов

15 Мая 2010

Как раз в SQL Server и есть возможность чтения только подтвержденных транзакций (READ COMMITED - если по стандарту). Если транзакция не подтверждена, то чтение останавливается и блокируется. Если мне не изменяет память, то существует 4 стандарта различных вида чтения. SQL Server поддерживает 2 - чтение подтвержденных и чтение грязных данных.

Oracle и SQL Server поддерживает еще и уровень REPEATABLE READ, когда внутри транзакции ты можешь читать данные сколько угодно (именно читать) и ты увидишь одно и то же, потому что никто не может изменить данные. На счет четвертого - я его не помню даже название, потому что Oracle его точно не поддерживает, а на счет SQL Server не уверен.

В любом случае, уровень изоляции - это всего лишь способ борьбы с корявыми блокировками. SQL сервер блокирует данные в транзакции эксклюзивно и не позволяет после этого даже чтнеие. Oracle блокирует только изменение, но не чтение. При этом для чтения возвращает результат тот, который был до начала транзакции и это отличное решение.


Алексей Егоров

18 Мая 2010

Надо быть справедливым к MSSQL. В версии 2005 появился режим неблокируемого чтения. Чтобы перевести базу в этот режим делаем так

ALTER DATABASE MyDatabase SET ALLOW_SNAPSHOT_ISOLATION ON

ALTER DATABASE MyDatabase SET READ_COMMITTED_SNAPSHOT ON

Включится версионирование записей и MSSQL будет работать как Oracle/Firebird. Но надо понимать, что версионирование записей, используемое в  Oracle/Firebird по-умолчанию, не совсем бесплатное. На это используются дополнительные ресурсы. А длинные транзакции - всегда нежелательны.


Михаил Фленов

18 Мая 2010

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


Алексей Егоров

18 Мая 2010

Кстати, да. SNAPSHOT ISOLATION на MSSQL не очень дорогой, по разным замерам где-то 10-20% на проседание производительности. А если грамотно оптимизировать хранилище (tempdb на отдельном диске, таком же быстром, как и для базы), то сводится почти к нулю.


Михаил Фленов

18 Мая 2010

У меня очень большая база данных, и я решил провести эксперимент с верисонностью на ней. Сижу в ожидании конца ALTER DATABASE уже пару часов.


Михаил Фленов

18 Мая 2010

Странно, а на другой базе сработало мгновенно


Vs

19 Мая 2010

> в ожидании конца ALTER DATABASE уже пару часов

Это вы включаете SNAPSHOT ISOLATION? Надо чтобы подключений к БД не было, тогда включается сразу, иначе ждет пока подключения пропадут.


Михаил Фленов

19 Мая 2010

Я догадался, просто делал это на рабочей базе, на которой другие пользователи сидят. Выводить базу в офлайн не мог. Меня удивило, какого черта кто-то заблокировал даныне на такое большое время, ведь сессии Web пользователей короткие. Думаю, что кто-то еще из программеров был подключен помимо меня, хотя никто не должен быть

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


Фарит

03 Ноября 2010

Добрый день!Я понимаю что вопрос не по теме!ну может кто даст ответ!я неплохо разбераюсь в компьютерах!мне хотелось бы еще изучить и программирование что бы посоветовали для начала (новичка)???Заранее спасибо


Устинов Сергей

04 Марта 2012

Михаил, спасибо за статью !
Вопрос "грязной выборки" данных всегда был каким-то недосказанным для меня, теперь все точки над i расставлены, желаю по-больше информативных статеек  =)


Rasputin

15 Января 2014

Благодарю. Статья очень помогла с SQL Server-ом.
Только ВНИМАНИЕ ПОЛЬЗОВАТЕЛЕЙ Oralce нельзя держать транзакцию "сколько душе угодно", мы же не хотим получить ошибку ORA-00060 : deadlock detected while waiting for resource.



Михаил Фленов

15 Января 2014

Добиться deadlock достаточно проблематично, даже если ты держишь долго транзакции открытыми. Ты скорее увидишь ошибку, что-то типа Already locked. Если нормально обрабатывать эту ситуацию, то никаких проблем


Добавить Комментарий

Еще что-нибудь

Хотите найти еще что-то интересное почитать? Можно попробовать отфильтровать заметки на блоге по категориям.

О блоге

Программист, автор нескольких книг серии глазами хакера и просто блогер. Интересуюсь безопасностью, хотя хакером себя не считаю

Обратная связь

Без проблем вступаю в неразборчивые разговоры по e-mail. Стараюсь отвечать на письма всех читателей вне зависимости от страны проживания, вероисповедания, на русском или английском языке.

Пишите мне