Как посмотреть блокировки в ms sql

Как посмотреть, какие данные заблокированы в СУБД MS SQL Server

Иногда требуется посмотреть, какие объекты и данные заблокированы и какие блокировки на этих объектах стоят (речь идет только о транзакционных блокировках). В SQL Server для этих целей существует динамическое представление sys.dm_tran_locks. Оно возвращает сведения об активных в данный момент в SQL Server ресурсах диспетчера блокировок.

Нас интересуют следующие столбцы:

  • resource_type — тип ресурса. Значение может быть одним из следующих: DATABASE, FILE, OBJECT, PAGE, KEY, EXTENT, RID, APPLICATION, METADATA, HOBT или ALLOCATION_UNIT. Основные из них это — DATABASE — сама база данных; OBJECT — таблица или любой другой объект; PAGE — страница файла БД; KEY — указатель на ключ индекса; EXTENT — экстент файла БД; RID — физическая строка в куче.
  • resource_database_id — идентификатор базы данных.
  • resource_description — описание ресурса. Для ресурса типа PAGE в этом поле хранится адрес страницы в формате <file_id>:<page_in_file>; для KEY будет указан хэш ключевых столбцов; для RID адрес строки вида <file_id>:<page_in_file>:<row_on_page>.
  • resource_associated_entity_id — идентификатор сущности, с которой связан ресурс. Для OBJECT — это ИД объекта; для KEY, PAGE и RID — идентификатор HoBt.
  • request_mode — тип блокировки. Как раз в этой колонке указано, какая блокировка наложена на ресурс — S, U, X и пр.

Смотрим какие объекты заблокированы

Представление sys.dm_tran_locks выдает результат в неудобном для чтения виде. Одни идентификаторы и ничего более. Чтобы получить более наглядное отображение, необходимо воспользоваться еще несколькими инструментами SQL Server.

Название базы данных из ее идентификатора можно получить через функцию DB_NAME(database_id). Для обратного преобразования — DB_ID(‘database_name’). Для объектов существуют аналогичные функции OBJECT_NAME(object_id) и OBJECT_ID( ‘database_name.object_name’).

Чтобы посмотреть, какие данные хранятся на странице, достаточно ИД файла и ИД страницы. При чтении будет указан и объект, к которому эта страница относится, и сами данные. Но для индексов необходимо узнать еще имя самого индекса, чтобы можно было выполнить запрос.

Узнать, к какому объекту относится элемент блокировки, у которого указан только HoBt, можно по представлению sys.partitions. Оно содержит нужные нам колонки:

  • object_id — идентификатор объекта, к которому относится сущность.
  • index_id — идентификатор индекса объекта. Нужен для получения названия индекса для типа ресурса KEY. 0 — означает кучу, 1 — кластерный индекс, все остальные значения относятся к некластерным индексам.
  • hobt_id — идентификатор сущности.

По index_id и object_id уже можно узнать имя индекса, по ключу которого установлена блокировка. Для этого воспользуемся еще одним представлением — sys.indexes. От него нам нужны следующие колонки:

  • object_id — идентификатор объекта, к которому относится индекс.
  • index_id — идентификатор индекса. Его мы знаем из таблицы sys.partitions.
  • name — имя индекса. Если тип индекса 0 (куча), то имя будет Null.

Итоговый запрос, который покажет заблокированные объекты, может выглядеть следующим образом:

Смотрим, какие данные заблокированы. Тип ресурса KEY, RID

Для типа ресурса KEY в поле resource_description в представлении sys.dm_tran_locks хранится хэш ключевых столбцов индекса. Для каждой записи индекса SQL Server вычисляет хэш. Он не повторяется, даже если все значения в колонках одинаковы, т.к. для каждой строки добавляется уникальный идентификатор, который участвует в формировании хэша. Посмотреть хэши строк таблицы (если есть кластерный индекс) или записей индекса можно с помощью виртуальной колонки %%lockres%%. Если кластерного индекса нет, то вместо хэша выведется адрес строки вида <file_id>:<page_in_file>:<row_on_page> (который нам нужен для типа ресурса RID). Выводится в запросе как и обычная колонка:

Результат вывода для таблицы без кластерного индекса:

Для таблицы с кластерным индексом:

Чтобы посмотреть хэши по записям индекса, необходимо выполнить запрос:

Теперь, чтобы получить ключ индекса, достаточно выполнить запрос с условием по виртуальной колонке.

Например, для типа ресурса KEY:

Смотрим, какие данные заблокированы. Тип ресурса PAGE

Чтобы посмотреть, что хранится в странице файла базы данных, воспользуемся функцией DBCC PAGE (). Принимает параметры: DBCC PAGE(db_name|db_id, file_id, page_id, print_option). Последним параметром указывается формат вывода из следующих доступных значений:

  • 0 — только заголовок;
  • 1 — заголовок, дампы и индекс слотов;
  • 2 — заголовок и полный дамп;
  • 3 — заголовок и максимальная детализация для каждого слота.

Нам интересен формат вывода 3. Перед использованием функции необходимо включить флаг трассировки: DBCC TRACEON(3604), иначе вместо результата увидите только надпись, что команда выполнилась, и ничего более.

Если выполнить команду:

то мы скорее всего увидим результат в виде текста:

Это не очень удобно, поэтому команду DBCC PAGE удобнее всего выполнять с опцией WITH TABLE RESULT:

Теперь та же информация представлена в виде таблицы:

Каждая страница (в общем виде) состоит из заголовка, раздела с данными и таблицы смещений. В заголовке стоит обратить внимание на поле m_type — тип страницы. 1 — означает страница с данными или кластерный индекс; 2 — страница с записями некластерного индекса; 10 — карта распределения индекса. После заголовка идут данные, распределенные по "слотам" (результат для страницы m_type = 1):

И вот здесь мы уже видим наши данные, которые хранятся в самой таблице. В моем случае это регистр сведений, у которого два измерения с типом Строка (поля _Fld11, _Fld12) и ресурс с типом Число (_Fld13).

Если на странице расположены записи индекса (m_type = 2), то в таблице ключей записи мы не увидим. Они выведутся в другой таблице уже без дополнительной информации (заголовков и пр.):

Блокировки в среде MS SQL Server

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

MS SQL Server поддерживает различные уровни блокирования объектов (или детализацию блокировок), начиная с отдельной строки таблицы и заканчивая базой данных в целом. Менеджер блокировок автоматически оценивает, какое количество данных необходимо блокировать, и устанавливает соответствующий тип блокировки. Это позволяет поддерживать равновесие между производительностью работы системы блокирования и возможностью пользователей получать доступ к данным.

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

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

Действия, выполняемые пользователями при работе с данными, сводятся к операциям двух типов: их чтению и изменению. В операции по изменению включаются действия по добавлению, удалению и собственно изменению данных.

В зависимости от выполняемых действий сервер накладывает определенный тип блокировки из следующего перечня:

· Коллективные блокировки. Они накладываются при выполнении операций чтения данных (например, SELECT). Если сервер установил на ресурс коллективную блокировку, то пользователь может быть уверен, что уже никто не сможет изменить эти данные.

· Блокировка обновления. Если на ресурс установлена коллективная блокировка и для этого ресурса устанавливается блокировка обновления, то никакая транзакция не сможет наложить коллективную блокировку или блокировку обновления.

· Монопольная блокировка. Этот тип блокировок используется, если транзакция изменяет данные. Когда сервер устанавливает монопольную блокировку на ресурс, то никакая другая транзакция не может прочитать или изменить заблокированные данные. Монопольная блокировка не совместима ни с какими другими блокировками, и ни одна блокировка, включая монопольную, не может быть наложена на ресурс.

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

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

Ранее рассмотренные блокировки относятся к данным. Помимо перечисленных в среде SQL Server существует два других типа блокировок: блокировка диапазона ключей и блокировка схемы (метаданных, описывающих структуру объекта).

Блокировка диапазона ключей решает проблему возникновения фантомов и обеспечивает требования сериализуемоститранзакции. Блокировки этого типа устанавливаются на диапазон строк, соответствующих определенному логическому условию, с помощью которого осуществляется выборка данных из таблицы.

Блокировка схемы используется при выполнении команд модификации структуры таблиц для обеспечения целостности данных.

"Мертвые" блокировки

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

Без применения специальных механизмов обнаружения и снятия "мертвых" блокировок нормальная работа транзакций будет нарушена. Если в системе установлен бесконечный период ожидания завершения транзакции (а это задано по умолчанию), то при возникновении "мертвой" блокировки для двух транзакций вполне возможно, что, ожидая освобождения заблокированных ресурсов, в тупике окажутся и новые транзакции. Чтобы избежать подобных проблем, в среде MS SQL Server реализован специальный механизм разрешения конфликтов тупикового блокирования.

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

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

Для минимизации возможности образования "мертвых" блокировок при разработке кода транзакции следует придерживаться следующих правил:

· выполнять действия по обработке данных в постоянном порядке, чтобы не создавать условия для захвата одних и тех же данных;

· избегать взаимодействия с пользователем в теле транзакции;

· минимизировать длительность транзакции и выполнять ее по возможности в одном пакете;

· применять как можно более низкий уровень изоляции.

Уровни изоляции SQL Server

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

SQLServer поддерживает все четыре уровня изоляции, определенные стандартом ANSI.

· READ UNCOMMITED – незавершенное чтение, или допустимо черновое чтение. Низший уровень изоляции, соответствующий уровню 0. Он гарантирует только физическую целостность данных: если несколько пользователей одновременно изменяют одну и ту же строку, то в окончательном варианте строка будет иметь значение, определенное пользователем, последним изменившим запись. По сути, для транзакции не устанавливается никакой блокировки, которая гарантировала бы целостность данных. Для установки этого уровня используется команда:

SETTRANSACTIONISOLATIONLEVELREADUNCOMMITTED

· READ COMMITTED –завершенное чтение, при котором отсутствует черновое, "грязное" чтение. Тем не менее в процессе работы одной транзакции другая может быть успешно завершена и сделанные ею изменения зафиксированы. В итоге первая транзакция будет работать с другим набором данных. Это проблема неповторяемого чтения. Данный уровень изоляции установлен в SQL Server по умолчанию и устанавливается посредством команды:

SET TRANSACTION ISOLATIONLEVEL READ COMMITTED

· REPEATABLE READ – повторяющееся чтение. Повторное чтение строки возвратит первоначально считанные данные, несмотря на любые обновления, произведенные другими пользователями до завершения транзакции. Тем не менее на этом уровне изоляции возможно возникновение фантомов . Его установка реализуется командой:

SET TRANSACTION ISOLATIONLEVEL REPEATABLE READ

· SERIALIZABLE – сериализуемость. Чтение запрещено до завершения транзакции. Это максимальный уровень изоляции, который обеспечивает полную изоляцию транзакций друг от друга. Он устанавливается командой:

SET TRANSACTION ISOLATIONLEVEL SERIALIZABLE

В каждый момент времени возможен только один уровень изоляции.

Таблица 1. Уровень изоляции конкурирующей транзакции принят по умолчанию (READ COMMITTED). В примере шаги 4, 6 и 8 демонстрируют черновое чтение. Шаги 9 и 10 блокируются, потому что данные захвачены конкурирующей транзакцией.
Пользователь user1 Конкурирующая транзакция Пользователь user2 Текущая транзакция
USE basa_user2 BEGIN TRANSACTION TRA USE basa_user2 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED BEGIN TRANSACTION TRB
1. SELECT * FROM Товар 2. SELECT * FROM Товар
3.UPDATE Товар SET остаток=остаток+10 WHERE КодТовара=4 4. SELECT * FROM Товар (читает измененные неподтвержденные данные)
5.DELETEFROMТоварWHEREКодТовара=4 6. SELECT * FROM Товар (читает измененные неподтвержденные данные)
7. INSERT Товар (Название, остаток) VALUES (‘SS’,999) 8. SELECT * FROM Товар (читает измененные неподтвержденные данные)
12. ROLLBACK TRANSACTION TRA 9. UPDATE Товар SET остаток=остаток+10 WHERE КодТовара=4 (блокируется до окончания конкурирующей транзакции )
10.DELETE FROM Товар WHERE КодТовара=4(блокируется до окончания конкурирующей транзакции ) 11.INSERT Товар(Название, остаток) VALUES (‘SS’,999) (выполняется)
13. ROLLBACK TRANSACTION TRB SELECT @@TRANCOUNT
Таблица 16.2. Уровень изоляции конкурирующей транзакции принят по умолчанию (READ COMMITTED). В примере шаги 4, 6 и 8 демонстрируют блокировку данных, захваченных другой транзакцией, в то время как работа с другими данными разрешается (шаг 10).
Пользователь user1 Конкурирующая транзакция Пользователь user2 Текущая транзакция
USE basa_user2 BEGIN TRANSACTION TRA USE basa_user2 SET TRANSACTION ISOLATION LEVELREAD COMMITTED BEGIN TRANSACTION TRB
1. SELECT * FROM Товар 2. SELECT * FROM Товар
3. UPDATE Товар SET остаток=остаток+10 (захватывает данные) 4. SELECT * FROM Товар WHERE КодТовара=4 (блокируется до окончания конкурирующей транзакции )
5. DELETE FROM Товар WHERE КодТовара=4 6. UPDATE Товар SET остаток=остаток+10 WHERE КодТовара=4 (блокируется до окончания конкурирующей транзакции )
7. UPDATE Товар SET остаток=остаток+10 WHERE КодТовара=4 (выполняется той транзакцией, которая первой захватила данные на изменение или удаление) 8. DELETE FROM Товар WHERE КодТовара=4 (блокируется до окончания конкурирующей транзакции )
9. INSERT Товар (Название, остаток) VALUES (‘SS’,999) 10. INSERT Товар(Название, остаток) VALUES (‘SS’,999) (выполняется)
11. ROLLBACK TRANSACTION TRA SELECT @@TRANCOUNT 12. ROLLBACK TRANSACTION TRB SELECT @@TRANCOUNT
Таблица 16.3. Уровень изоляции конкурирующей транзакции принят по умолчанию (READ COMMITTED). На шаге 2 транзакция захватила данные чтением и блокирует работу с ними со стороны конкурирующей транзакции (шаги 3, 5), которая может лишь добавлять записи (шаг 7).
Пользователь user1 Конкурирующая транзакция Пользователь user2 Текущая транзакция
USE basa_user2 BEGIN TRANSACTION TRA USE basa_user2 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRANSACTION TRB
1. SELECT * FROM Товар 2. SELECT * FROM Товар (захватывает данные)
3. UPDATE Товар SET остаток=остаток+10 WHERE КодТовара=4 (блокируется) 4. SELECT * FROM Товар (блокируется до окончания конкурирующей транзакции )
5. DELETE FROM Товар WHERE КодТовара=4 (блокируется) 6. UPDATE Товар SET остаток=остаток+10 WHERE КодТовара=4 (выполняется, т.к. данные захвачены текущей транзакцией )
7. INSERT Товар (Название, остаток) VALUES (‘SS’,999) (выполняется) 8. DELETE FROM Товар WHERE КодТовара=4 (выполняется, т.к. данные захвачены текущей транзакцией )
10. ROLLBACK TRANSACTION TRA SELECT @@TRANCOUNT 9. INSERT Товар(Название, остаток) VALUES (‘SS’,999) (выполняется)
11. ROLLBACK TRANSACTION TRB SELECT @@TRANCOUNT
Таблица 16.4. Уровень изоляции конкурирующей транзакции принят по умолчанию (READ COMMITTED). Пример демонстрирует, что текущая транзакция захватила данные чтением (шаг 2) и блокирует любые действия с ними со стороны конкурирующей транзакции вплоть до вставки данных (шаг 7).
Пользователь user1 Конкурирующая транзакция Пользователь user2 Текущая транзакция
USE basa_user2 BEGIN TRANSACTION TRA USE basa_user2 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE BEGIN TRANSACTION TRB
1. SELECT * FROM Товар 2. SELECT * FROM Товар (захватывает данные)
3. UPDATE Товар SET остаток=остаток+10 WHERE КодТовара=4 (блокируется) 4. SELECT * FROM Товар (выполняется)
5. DELETE FROM Товар WHERE КодТовара=4 (блокируется) 6. UPDATE Товар SET остаток=остаток+10 WHERE КодТовара=4 (выполняется, т.к. данные захвачены текущей транзакцией )
7. INSERT Товар (наименование, остаток) VALUES (‘SS’,999) (блокируется) 8. DELETE FROM Товар WHERE КодТовара=4 (выполняется, т.к. данные захвачены текущей транзакцией )
10. ROLLBACK TRANSACTION TRA SELECT @@TRANCOUNT 9. INSERT Товар(Название, остаток) VALUES (‘SS’,999) (выполняется)
11. ROLLBACK TRANSACTION TRB SELECT @@TRANCOUNT

Использование sp_lock

Прежде чем показать запрос, автоматизирующий обнаружение проблемных запросов, хотелось бы поговорить о другой важной характеристике запросов с низкой производительностью, а именно, о безудержной трате ресурсов.

Sp_who2 показывает хорошую картину процессов, которые могут блокировать другие процессы, и некоторое начальное представление об использовании ресурсов типа CPU и I/O, но не говорит ничего о различных блокировках, наложенных для исполнения процесса.


Рисунок 1. После KILL в Important_Data нет записей.

Блокировка – это «нормальное» действие SQL Server, то есть это механизм, посредством которого SQL Server управляет параллельным доступом к данному ресурсу нескольких конкурирующих процессов. Однако как DBA вы должны распознавать поведение блокировок, явно указывающее, что что-то не так.

Распространенные типы блокировок:

§ RID – блокировка одной строки

§ KEY – диапазон ключей в индексе

§ PAG – блокировка страницы данных или индекса

§ EXT – блокировка экстента

§ TAB – блокировка таблицы

§ DB – блокировка БД

В дополнение к типам блокировок, относящимся к ресурсам или объектам, у SQL Server есть общие режимы блокировок:

§ S – Совмещаемая (или разделяемая, Shared) блокировка

§ U – блокировка обновления (Update)

§ X – монопольная (Exclusive) блокировка

§ Intent-блокировки (IS, IU, IX) – используются для создания иерархии блокировок.

§ BU – используется при массовой заливке данных в таблицу

§ Sch-S и Sch-M – блокировки схемы.

Из режимов и типов блокировок, приведенных выше, можно создавать комбинации. Так, например, можно создать блокировку таблицы (TAB) в режиме «Х», то есть эксклюзивном. Это значит, что процесс запрашивает или получает эксклюзивную блокировку таблицы. Естественно, удержание такой блокировки на существенное время может привести к проблемам с блокировками.

В SQL Server есть хранимая процедура sp_lock, предоставляющая массу полезной для DBA информации о количестве и типах блокировок, запрошенных процессом.

Примечание: В SQL Server 2005 и выше эквивалентом sp_lock является компонент SQL ServerDatabaseEngine- динамическое административное представление sys.dm_tran_locks.

На рисунке 9 показан результат выполнения sp_lock для SPID 51, Плохого Запроса.


Рисунок 9. Блокировки Плохого Запроса.

Здесь вы можете видеть, что наложено много блокировок, в основном эксклюзивных блокировок уровня строки, как показывают режим "X" и тип "RID". Когда один SPID, накладывающий такое количество блокировок- что-то точно идет не так, как должно.

Часто простого подсчета блокировок и, что важнее, типов блокировок от отдельного SPID, достаточно, чтобы помочь обнаружить плохо выполняющийся запрос, даже если нет явного блокирования работы. Запрос блокировок, так же как и запрос подключений, расходуют память, и даже разделяемые блокировки, которые могут и не блокировать доступ к данным, иногда могут иметь большое влияние на производительность из-за нагрузки на память и другие ресурсы.

Листинг 1. Блокировки в базе данных при уровне изоляции Зафиксированное чтение (CommittedRead)

Выходные данные процедуры sp_lock:
/Заголовки столбцов таблицы/
Spid — идентификатор процесса
Dbid — идентификатор базы данных
Objid — идентификатор объекта
Indid — индивидуальный идентификатор
Тип — тип ресурса (степень детализации)
Ресурс — идентификатор ресурса
Режим — режим блокирования
Статус — статус блокировки

USE PUBS SET TRANSACTION ISOLATION LEVEL READ COMMITTED BEGIN TRAN SELECT * FROM author WHERE au_lname = `Ringer` EXEC sp_lock @@spid COMMIT TRAN

Листинг 2. Блокировки ключей и разделяемые блокировки при уровне изоляции Повторяемое чтение (RepeatableRead)

USE PUBS SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRAN SELECT * FROM authors WHERE au_lname = `Ringer` EXEC sp_lock @@spid COMMIT TRAN

Листинг 3. Блокировки диапазона ключа при уровне изоляции Упорядочиваемый (Serializable)

USE PUBS SET TRANSACTION ISOLATION LEVEL SERIALIZABLE BEGIN TRAN SELECT * FROM authors WHERE au_lname = `Ringer` EXEC sp_lock @@spid COMMIT TRAN

Листинг 4. Исключающие блокировки при уровне изоляции Зафиксированное чтение (ReadCommitted)

USE PUBS SET TRANSACTION ISOLATION LEVEL READ COMMITTED BEGIN TRAN UPDATE authors SET contract = 0 WHERE au_lname = `Ringer` EXEC sp_lock @@spid COMMIT TRAN

Листинг 5. Блокировки строк при уровне изоляции Зафиксированное чтение (ReadCommitted)

SQL Batch: USE PUBS SET TRANSACTION ISOLATION LEVEL READ COMMITTED BEGIN TRAN updatenewTitles SET price = 3.99 WHERE type = `business` EXEC sp_lock @@spid ROLLBACK TRAN

Управление параллельностью

Определение. Управление параллельностью – это процесс организации одновременного выполнения в базе данных различных операций, гарантирующий исключение их взаимного влияния друг на друга.

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

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

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

Восстановление базы данных

Определение. Восстановление базы данных– это процесс возвращения базы данных в корректное состояние, утраченное в результате сбоя или отказа.

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

Транзакции представляют собой основную единицу восстановления в системах с базами данных. Именно менеджер восстановления СУБД обеспечивает поддержку двух из четырех основных свойств транзакций – атомарность и продолжительность, – даже при наличии сбоев в системе. Менеджер восстановления должен гарантировать, что при восстановлении после сбоя для каждой отдельной транзакции в базе данных будут постоянно фиксироваться либо все внесенные ею изменения, либо ни одного из них. Ситуация осложняется тем фактом, что запись в базу данных не представляет собой атомарного действия (выполняемого за один шаг), и поэтому существует вероятность, что, когда выполнение транзакции будет завершено, внесенные ею изменения не будут реально отражены в базе данных по той простой причине, что еще не достигли файлов базы данных.

При выполнении операций чтения СУБД осуществляет следующие действия:

– определяет дисковый адрес блока данных, содержащего запись с заданным значением первичного ключа;

– считывает блок данных с диска и помещает его в буфер СУБД в оперативной памяти;

– копирует сведения заданного поля из буфера СУБД в элемент данных.

При выполнении операции записи СУБД осуществляет следующие действия:

– определяет дисковый адрес блока данных, содержащего запись с заданным значением первичного ключа;

– считывает блок данных с диска и помещает его в буфер СУБД в оперативной памяти;

– копирует сведения из элемента данных в буфер СУБД;

– выводит блок данных из буфера СУБД в оперативной памяти на диск.

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

Если отказ системы произойдет между записью данных в буфер и выгрузкой буфера во вторичную память, менеджер восстановления должен уточнить состояние транзакции, выполнявшей запись в момент аварии. Если транзакция уже выдала команду завершения, то для обеспечения продолжительности ее результатов менеджер восстановления должен повторно (redo) ее выполнить (иногда говорят, прогнать), чтобы восстановить все внесенные ею изменения.

С другой стороны, если на момент отказа системы транзакция еще не была завершена, менеджер восстановления должен отменить (undo) любые ее результаты (откатить транзакцию в базе данных), что будет гарантировать соблюдение ее атомарности. Если требуется откатить только одну транзакцию, то это – частичный откат. Частичный откат может инициироваться графиком, когда транзакция откатывается и перезапускается по требованию протокола управления параллельностью. Кроме того, транзакция может отменяться по внутренним причинам, например, по требованию пользователя или в результате возникновения исключительной ситуации в прикладной программе. Если требуется выполнить откат всех активных транзакций, то это – глобальный откат.

Типичная СУБД должна предоставлять такие функции восстановления, как:

механизм резервного копирования, предназначенный для периодического создания копий базы данных;

– средства ведения журнала, в котором фиксируются текущее состояние транзакций и вносимые в базу данных изменения;

– функция создания контрольных точек, обеспечивающая перенос выполняемых в базе данных изменений во вторичную память с целью сделать их постоянными;

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

Как посмотреть блокировки в ms sql


Emfi   ( 2002-12-09 19:10 ) [0]

Работаю через ADO с MS SQL сервером. Пользуюсь TBetterADO компонентами. Возикает следующая ситуация: как только стартую транзакцию и вношу какое-то изменение таблица в которой поменялось хотя бы одно поле в единственной записи блокируется. Ее не удается даже прочитать.
ADODataSet открыт в режиме ReadWrite. Уровень изоляции транзакций любой (эффект не изменяется). LockType — optimistic.
Курсор клиентский и Statiс.
Как бороться, если это возможно? Режим ReadWrite снимать нельзя.


sniknik   ( 2002-12-09 19:42 ) [1]

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
перед чтением, блокироватся не булет. (но риск на непринятых транзакциях выводы делать)


Emfi   ( 2002-12-10 09:01 ) [2]

К сожалению, этот вариант тоже не проходит. Не понятно почему блокируется ВСЯ ТАБЛИЦА. MS SQL разве не умеет блокировать отдельную запись ?


Anatoly Podgoretsky   ( 2002-12-10 09:21 ) [3]

MS SQL разные бывают


Fiend   ( 2002-12-10 10:00 ) [4]

есть разные типы блокировок. но они внутрисерверные. Т.е. ваш АДО компонент и не знает о их существовании. Если бы вы пользовались всеми версиями MS SQL , а это 6.5-7.0-2000(8.0), то вы бы заметили по характеристикам продукта, что в версии 2000, уже есть и строчные блокировки, а до этого были и табличные и страничные, и вы конечно не можете ими управлять, т.е. сервер сам решает как нужно блокировать данные. Ну в общем смысл такой, если надо прочесть данные из таблицы, которая заблокирована не важно каким способом, то есть такая фишка:
select * from ATable with(nolock).прочтёт не смотря ни на что, т.е. транзакции явные и неявные вас не остановят.
УДАЧИ!


Emfi   ( 2002-12-10 10:32 ) [5]

Уточняю — MS SQL 2000 и тем не менее по записям не блокирует — только целиком всю таблицу. А насчет блокировок записей похоже, что все-таки провайдер тут прикладыват руку а не сервер. За фишку "with(nolock)" огромное спасибо, действительно работает. Получился действительно optimistic. Если MS SQL научился блокировать записи только в последней своей версии, то это совсем не здорово.
В документации нашлись эти опции, а сначала я на них и внимания не обратил, точнее не там искал.


Азазелло   ( 2002-12-10 12:36 ) [6]

А если меня интересует не только чтение? как дела обстоят с этим?

с nolock после удаления записей в кросс таблице, если транзакция сразу не коммитится (а я не могу её сразу коммитить)на других клиентах при попытке добавления в эту кросстаблицу betterADO датасет анализирует и ждет снятия блокировки, соответственно слетает по таймауту.. как с этим жить?

С уважением, Игорь.


Fiend   ( 2002-12-10 15:55 ) [8]

То Азазелло
Забудьте о том что провайдер делает блокировки. Это чушь полная!

Блокирует всегда сервер и только сервер, через какую бы вы там ху. ню не обратились к нему. Кто же станет переносить сложнейшую логику сервера (в частности блокировки) в провайдера?! Это абсурд.
А насчёт записи: Ни один порядочный сервер, в жизни вам не даст выполнить одновременный UPDATE для одних и тех же данных. Хотя возможен вариант, что в процессе обновления, сервер заблокирует только например 5 страницу таблицы, а какой то другой запрос вызовет блокировку 10 страницы данных. Эти запросы выполнятся и не заблокируют друг друга


Азазелло   ( 2002-12-10 18:06 ) [9]

To Fiend ©

Что значит не даст? Если переходить на термины "порядочные сервера" — в Interbase нет блокировок, поскольку он версионник и lock идет только в момент попытки коммита транзакции, насколько я знаю и Oracle сейчас декларирует это.. а вот MS получается полный блокировочник : (

И в данном случае речь идет не о странице или таблице. Почему, когда работа идет с одной конкретной записью происходит блокировка всех записей таблицы? блокировка записи, с которой идет работа — это одно.. а всех.

С уважением, Игорь.


Fiend   ( 2002-12-10 18:29 ) [10]

То Азазелло
тип блокировки устанавливается человеком который пишет запрос на Update, тама есть опции. Но если сервер всё же решит шо человек неверно написал или не указал ваще ничего, то лочится вся таблица.

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

С Уважением, Fiend


Fiend   ( 2002-12-10 18:31 ) [11]

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

Уважением, Fiend


Emfi   ( 2002-12-10 18:34 ) [12]

По поводу провайдера уточню, чтобы не было непонимания. Он передает серверу флаги и параметры, которыми должен руководствоваться сервер при работе с коннектом (или сессией), т.е. дает своего рода рекоммендации, а механизм реализуется разумеется сервером (с этим никто не спорит, т.к. если это будет делать провайдер, то на кой вообще и сервер . ). Если провайдер в параметрах не будет "настаивать", что ему неприемлима блокировка таблиц целиком, то сервер такую блокировку сделает, а если будет, то по крайней мере должен попробовать избежать подоюной ситуации (уж если не получилось, то блокировать). Вот что имелось ввиду под участием провайдера.
В моем случае правильным было бы блокировать записи (потому как у пишущих наборов данных установлен "оптимистичный" режим блокировки), которые были изменены в процессе выполнения транзакции, а не всю таблицу (причем на запись, а чтение предоставлять в соответствии с уровнем изоляции транзакций).
Oracle так себя и ведет. MS SQL 2000 так же должен уметь блокировать не только блоками и таблицами (что и файл-сервера умеют), но и записями, чего не происходит. Значит либо в действительности он этого не умеет, либо я задаю не правильные настройки, что наиболее вероятно.
Использование with(nolock) частично решило проблему и я могу считывать данные (кстати появление там незакоммиченных записей не очень желательно, но что терпимо), но я очень расчитывал на возможность записи: запись не откоммичена, уровень изоляции readcommited — ее даже видеть никто не должен был.
Почему же все-таки идет блокировка всей таблицы из-за одной незакоммиченной записи ?


Азазелло   ( 2002-12-10 18:42 ) [13]

Спасибо за поучения.. вот только где бы поподробнее почитать об этих самых опциях? (стандартный MSSQL-евский онлайновый бук не предлагать, лучше если есть возможность дать ссылочку на какой-нибудь родной урл или на книжку, которую можно купить и читать в метро или кратко поделиться здесь)к сожалению в апдейт ничего засунуть не могу, поскольку его делает BetterADODataSet, только через настройки ADOConnection (да и то они бедноватые и отчего-то не все работают, не смотня на все апдейты). Уровень изоляции ReadUncommited приходится через ADOCommand выставлять..

С уважением, Игорь.


Fiend   ( 2002-12-11 10:02 ) [14]

То Emfi
поддерживаю про провайдера, я в общем тоже хотел сказать. А вот дальше для двоих:

То Emfi и Азазелло
Уровень блокировок можно и надо устанавливать не только во время select.
Для INSERT и UPDATE можно сделать тоже самое. И ваще я честно говоря сторонник того чтобы не какими то там неведомыми опциями провайдера устанавливать параметры запросов а именно ручками их прописывать, т.е. делать так update aaa with (rowlock) set a=1 where b=123.

Т.е. можно управлять блокировками при изменении данных, а не только устанавливать их при SELECT.

То Азазелло
Я канечна извиняюсь, но я всё читаю из BOL. Где взять урл или более подробно не знаю. Там в общем нормально написано. Ну если уж что то очень не понятно, то пишите мылом или стучитесь в асю. Чем смогу помогу.

Типичные взаимные блокировки в MS SQL и способы борьбы с ними

Чаще всего deadlock описывают примерно следующим образом:
Процесс 1 блокирует ресурс А.
Процесс 2 блокирует ресурс Б.
Процесс 1 пытается получить доступ к ресурсу Б.
Процесс 2 пытается получить доступ к ресурсу А.
В итоге один из процессов должен быть прерван, чтобы другой мог продолжить выполнение.
Но это простейший вариант взаимной блокировки, в реальности приходится сталкиваться с более сложными случаями. В этой статье мы расскажем с какими взаимными блокировками в MS SQL нам приходилось встречаться и как мы с ними боремся.

Немного теории

Выбор уровня изоляции транзакции

При использовании транзакций с уровнем изоляции serializable могут происходить любые взаимные блокировки. При использовании уровня изоляции repeatable read некоторые из описанных ниже взаимных блокировок не могут произойти. У транзакций с уровнем изоляции read committed могут возникнуть только простейшие взаимные блокировки. Транзакция с уровнем изоляции read uncommitted практически не влияет на скорость работы других транзакций и в ней не могут возникнуть взаимные блокировки из-за чтения, так как она не накладывает shared блокировки (правда могут быть взаимные блокировки с транзакциями изменяющими схему БД).

  • Если транзакция изменяет данные в БД и при этом проверяет, чтобы эти данные не противоречили уже существующим записям в БД, то для нее скорее всего нужен уровень изоляции serializable. Но если вставка новых записей в параллельных транзакциях никак не может повлиять на результат текущей транзакции то можно использовать уровень изоляции repeatable read.
  • Для чтения данных обычно достаточно использовать уровень изоляции по умолчанию (read committed) без какой либо транзакции. Однако при чтении агрегатов, части которых могут быть изменены во время чтения, может понадобится использовать транзакцию с уровнем изоляции repeatable read или даже serializable, иначе можно получить из базы агрегат в некорректном состоянии, в котором он может быть только в процессе выполнения транзакции изменения.
  • Если необходимо отображать real time статистику по постоянно изменяющимся данным, то зачастую лучше использовать уровень изоляции read uncommitted. В этом случае в статистике будет некоторое количество грязных данных (хотя вряд ли это будет заметно), но зато построение отчетов практически не будет влиять на скорость работы системы.

Retry on deadlock

В достаточно сложной системе, насчитывающей десятки разнообразных типов бизнес транзакций, вряд ли получится спроектировать все транзакции таким образом, чтобы deadlock не мог возникнуть ни при каких условиях. Не стоит тратить время на предотвращение взаимных блокировок, вероятность возникновения которых крайне мала. Но, чтобы не портить user experience, в случае, когда операция прерывается из-за взаимной блокировки, ее нужно повторить. Для того, чтобы операцию можно было безопасно повторить, она не должна изменять входные данные и должна быть обернута в одну транзакцию (либо вместо всей операции, надо оборачивать в свой RetryOnDeadlock каждую SQL транзакцию в операции).

Вот пример функции RetryOnDeadlock на C#:

Важно понимать, что функция RetryOnDeadlock всего лишь улучшает user experience при изредка возникающих взаимных блокировках. Если они возникают очень часто, она лишь ухудшит ситуацию, в разы увеличив нагрузку на систему.

Борьба с простейшими взаимными блокировками

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

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

Shared->Exclusive lock escalation

  1. Транзакция 1 читает запись (накладывается S-блокировка).
  2. Транзакция 2 читает эту же запись (накладывается вторая S-блокировка).
  3. Транзакция 1 пытается изменить запись и ждет, когда транзакция 2 закончится и отпустит свою S-блокировку.
  4. Транзакция 2 пытается изменить эту же запись и ждет, когда транзакция 1 закончится и отпустит свою S-блокировку

Чтобы избежать такой взаимной блокировки, необходимо, чтобы из двух транзакций, собирающихся изменить запись, прочитать ее могла только одна. Специально для этого была введена update блокировка. Ее можно наложить следующим образом:

Если вы используете ORM и не можете управлять тем, как запрашивается сущность из БД, то вам придется выполнить отдельный запрос на чистом SQL для блокировки записи прежде чем запрашивать ее из БД. Важно, что накладывающий update блокировку запрос должен быть первым запросом, обращающимся к этой записи в данной транзакции, иначе будет возникать все та же взаимная блокировка, но при попытке наложить update блокировку, а не при изменении записи.
Накладывая update блокировку мы заставляем все транзакции, обращающиеся к одному ресурсу, выполняться по очереди, но обычно транзакции изменяющие один и тот же ресурс в принципе нельзя делать параллельно, так что это нормально.
Такая взаимная блокировка может возникнуть в любой транзакции, которая проверяет данные перед их изменением, но для редко изменяющихся сущностей, можно использовать RetryOnDeadlock. Подход с предварительной update блокировкой достаточно использовать только для сущностей, которые часто меняются разными процессами параллельно.

Пример
Пользователи заказывают призы за баллы. Количество призов каждого вида ограниченно. Система не должна позволить заказать больше призов, чем есть в наличии. Из-за особенностей промоакции периодически происходят набеги пользователей, желающих заказать один и тот же приз. Если использовать RetryOnDeadlock в данной ситуации, то во время набега пользователей заказ приза в большинстве случаев будет падать по web таймауту.

  1. Получаем запись о виде приза, накладывая update блокировку.
  2. Проверяем количество оставшихся призов. Если оно равно 0, завершаем транзакцию и возвращаем соответствующий ответ пользователю.
  3. Если призы еще есть, уменьшаем количество оставшихся призов на 1.
  4. Добавляем запись о заказанном призе.

Большинство взаимных блокировок, описанных далее, происходят похожим образом — мы пытаемся изменить данные после того как наложили на них Shared блокировку. Но в каждом из этих случаев есть свои нюансы.

Выборки по неиндексируемым полям

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

Если же добавить индекс по этому полю (или индекс по нескольким полям, первым из которых является поле, по которому мы ищем), то блокироваться будет ключ в этом индексе. Так что в serializable транзакциях еще более важно задумываться есть ли индекс по колонкам, по которым вы ищете записи.

Есть еще один нюанс, о котором важно помнить: если индекс уникален, то блокировка накладывается только на запрашиваемый ключ, а если неуникален, то также блокируются cледующее за этим ключом значение. Две транзакции, запрашивающие разные записи по неуникальному индексу, а потом изменяющие их, могут взаимно блокироваться, если запрашиваются соседние значения ключей. Обычно это редкая ситуация и достаточно использовать RetryOnDeadlock, чтобы избежать проблем, но в некоторых случаях может потребоваться накладывать update блокировку при вытаскивании записей по неуникальному ключу.

Проверка на наличие перед вставкой

Пример
Нам необходимо проверить, есть ли в БД пользователь с таким Id в Facebook, перед тем как его добавлять. Так как мы работаем с одной строчкой в БД, создается ощущение, что будет блокироваться только она и вероятность взаимной блокировки невелика. Однако если в транзакции с уровнем изоляции Serializable попытаться выбрать несуществующее значение (и эта колонка входит в индекс), то будет наложена shared блокировка на все ключи между двумя ближайшими значениям, которые есть в таблице. Например, если в базе есть Id 15 и Id 1025, и нет ни одного значения между ними, то при выполнении SELECT * FROM Users WHERE FacebookId = 500 будет наложена Shared блокировка на ключи с 15 до 1025. Если до вставки другая транзакция проверит есть ли пользователь с FacebookId = 600 и попытается его вставить, то произойдёт взаимная блокировка. Если в БД уже много потребителей, у которых заполнен FacebookId, то вероятность взаимной блокировки будет невелика и нам достаточно использовать RetryOnDeadlock. Но если выполнять множество таких транзакций на почти пустой базе, то взаимные блокировки будут возникать достаточно часто, чтобы это сильно сказалось на производительности.

У нас эта проблема возникла при параллельном импорте потребителей от новых клиентов (для каждого клиента мы создаем новую пустую БД). Так как нас на данный момент устраивает скорость однопоточного импорта, мы просто отключили параллелизм. Но в принципе проблема решается также как и в выше описанном примере, надо использовать update блокировку:

В этом случае при многопоточном импорте в пустую базу по началу потоки будут простаивать, ожидая пока освободится блокировка, но по мере заполнения базы степень параллелизма будет возрастать. Хотя если импортируемые данные упорядочены по FacebookId, то параллельно импортировать их не получится. При импорте в пустую базу такого упорядочивания стоит избегать (либо не проверять наличие пользователей в БД по FacebookId при первом импорте).

Взаимные блокировки на сложных агрегатах

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

Пример
В БД хранится персональные данные потребителя, его идентификаторы в соц сетях, заказы в интернет магазине, записи об отправленных ему письмах.

Транзакции, добавляющие идентификатор в соц сети, отправляющие письма и регистриующие покупки, также могут изменять технические поля в основной записи о потребителе. В любой из этих транзакций присутствует Id потребителя.

Для избежания взаимных блокировок нужно начинать любую транзакцию со следующего запроса:

В этом случае в один момент только одна транзакция сможет изменять данные, относящиеся к конкретному потребителю и взаимные блокировки не будут возникать в независимости от того насколько сложен агрегат потребителя.
Можно попробовать изменить схему хранения данных так, чтобы транзакции, отправляющие письма и регистрирующие покупки не меняли технические пометки в потребителе. Тогда информацию о заказах и отправленных письмах можно будет изменять параллельно с изменением потребителя. В этом случае мы фактически выносим эти данные за рамки агрегата «потребитель».

  • надо стремится к тому, чтобы любая транзакция в системе не изменяла более одного агрегата
  • в любой транзакции, изменяющей агрегат, первый запрос, обращающийся к данным агрегата, должен накладывать exclusive или update блокировку на корень агрегата
  • для увеличения степени параллелизма системы надо делать агрегаты настолько маленькими насколько это возможно

Взаимные блокировки на последовательно идущих записях

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

Пример

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *