Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Изоляция снимков повышает уровень параллелизма для приложений OLTP.
Общие сведения об изоляции моментальных снимков и версионировании строк
После включения изоляции моментального снимка обновлённые версии строк для каждой транзакции хранятся в tempdb. Уникальный порядковый номер транзакции идентифицирует каждую транзакцию, и эти уникальные числа записываются для каждой версии строки. Транзакция работает с последними версиями строк, порядковый номер которых предшествует порядковому номеру транзакции. Транзакция игнорирует более новые версии строк, созданные после начала транзакции.
Термин "моментальный снимок" означает, что все запросы в транзакции имеют одну и ту же версию или моментальный снимок базы данных, исходя из состояния базы данных в момент начала транзакции. Блокировки не накладываются на базовые строки данных или страницы данных в транзакции моментального снимка, что позволяет выполнять другие транзакции без блокировки из-за предыдущей незавершенной транзакции. Транзакции, изменяющие данные, не блокируют транзакции, считывающие данные, а транзакции, считывающие данные, не блокируют транзакции, которые записывают данные, так как в SQL Server они обычно находятся на стандартном уровне изоляции READ COMMITTED. Такое неблокирующее поведение также значительно снижает вероятность взаимоблокировок в сложных транзакциях.
Изоляция снимков использует оптимистическую модель параллелизма. Если транзакция моментального снимка пытается зафиксировать изменения в данных, которые имели место с момента начала транзакции, произойдет откат транзакции и возникнет ошибка. Этого можно избежать, используя указания UPDLOCK для инструкций SELECT, обращающихся к изменяемым данным. Дополнительные сведения см. в разделе "Locking Hints" электронной документации SQL Server.
Перед использованием в транзакциях необходимо включить изоляцию на основе моментальных снимков, установив для базы данных параметр ALLOW_SNAPSHOT_ISOLATION в значение ON. Это приводит к активизации механизма сохранения версий строк во временной базе данных (tempdb). Необходимо включить изоляцию snapshot в каждой базе данных, где она используется, с помощью инструкции Transact-SQL ALTER DATABASE. В этом отношении изоляция моментальных снимков отличается от традиционных уровней изоляции READ COMMITTED, REPEATABLE READ, SERIALIZABLE и READ UNCOMMITTED, которые не требуется настраивать. Следующие инструкции активируют изоляцию моментальных снимков и заменяют зафиксированное поведение по умолчанию READ COMMITTED на SNAPSHOT:
ALTER DATABASE MyDatabase
SET ALLOW_SNAPSHOT_ISOLATION ON
ALTER DATABASE MyDatabase
SET READ_COMMITTED_SNAPSHOT ON
Установка параметра READ_COMMITTED_SNAPSHOT в значение ON позволяет получать доступ к версионированным строкам при использовании уровня изоляции READ COMMITTED по умолчанию. Если параметр READ_COMMITTED_SNAPSHOT установлен в значение OFF, необходимо явно задать уровень изоляции SNAPSHOT в каждом сеансе, чтобы получить доступ к версионным строкам.
Управление параллелизмом с помощью уровней изоляции
Уровень изоляции, при котором выполняется инструкция Transact-SQL, определяет поведение блокировки и управления версиями строк. Уровень изоляции действует на уровне всего подключения, и после установки для подключения с помощью оператора SET TRANSACTION ISOLATION LEVEL он остается в силе до тех пор, пока подключение не будет закрыто или не будет установлен другой уровень изоляции. Когда соединение закрыто и возвращается в пул, уровень изоляции от последней SET TRANSACTION ISOLATION LEVEL инструкции сохраняется. Последующие подключения, использующие подключение из пула, применяют уровень изоляции, действовавший в момент добавления подключения в пул.
Отдельные запросы, выдаваемые в подключении, могут содержать указания блокировки, которые изменяют изоляцию для отдельной инструкции или транзакции, но не влияют на уровень изоляции подключения. Уровни изоляции или указания блокировки, заданные в хранимых процедурах или функциях, не изменяют уровень изоляции вызывающего их подключения и действуют только во время выполнения хранимой процедуры или вызова функции.
В ранних версиях SQL Server поддерживались четыре уровня изоляции, определенные в стандарте SQL-92:
READ UNCOMMITTED — это наименее строгий уровень изоляции, так как он игнорирует блокировки, размещенные другими транзакциями. Транзакции, выполняемые с помощью инструкции READ UNCOMMITTED, могут считывать измененные значения данных, которые еще не были зафиксированы другими транзакциями. Такие операции чтения называются чтением "грязных" данных.
READ COMMITTED является уровнем изоляции по умолчанию для SQL Server. Он предотвращает операции чтения "грязных" данных, указывая, что инструкции не могут считывать значения данных, которые были изменены, но еще не зафиксированы другими транзакциями. Другие транзакции по-прежнему могут изменять, вставлять или удалять данные между выполнениями отдельных инструкций в текущей транзакции, результатом чего будет неповторяемое чтение или фантомные данные.
REPEATABLE READ — это более строгий уровень изоляции, чем READ COMMITTED. Он включает в себя READ COMMITTED и дополнительно указывает, что никакие другие транзакции не могут изменять или удалять данные, которые были считаны текущей транзакцией, пока текущая транзакция не будет зафиксирована. Учитывая то, что совмещаемые блокировки на считываемых данных сохраняются до завершения транзакции и не снимаются в конце каждой инструкции, степень совпадений ниже, чем при уровне изоляции READ COMMITTED.
SERIALIZABLE — самый строгий уровень изоляции, поскольку он блокирует целые диапазоны ключей и сохраняет блокировку до завершения транзакции. Он включает в себя REPEATABLE READ и добавляет ограничение, запрещающее другим транзакциям вставлять новые строки в диапазоны, которые были прочитаны транзакцией, пока транзакция не будет завершена.
Дополнительные сведения см. в Руководстве по блокировке транзакций и управлению версиями строк.
Расширения уровня изоляции снимка
SQL Server ввел расширения уровней изоляции SQL-92 за счёт добавления уровня изоляции SNAPSHOT и дополнительной реализации READ COMMITTED. Уровень изоляции READ_COMMITTED_SNAPSHOT может прозрачно заменить READ COMMITTED для всех транзакций.
Уровень изоляции SNAPSHOT означает, что данные, считываемые в ходе транзакции, никогда не будут учитывать изменения, внесенные другими параллельными транзакциями. Транзакция использует версии строк данных, которые существуют в начале транзакции. При чтении данных на них не накладываются блокировки, поэтому транзакции SNAPSHOT не препятствуют записи данных другими транзакциями. Транзакции, записывающие данные, не блокируют чтение данных транзакциями снимков. Для использования изоляции моментальных снимков необходимо включить ее, настроив параметр базы данных ALLOW_SNAPSHOT_ISOLATION.
Параметр базы данных READ_COMMITTED_SNAPSHOT определяет поведение стандартного уровня изоляции READ COMMITTED, если изоляция моментальных снимков включена в базе данных. Если не указать параметр READ_COMMITTED_SNAPSHOT явным образом, то для всех неявных транзакций применяется уровень READ COMMITTED. Это приводит к тому же поведению, что и при установке READ_COMMITTED_SNAPSHOT в значение OFF (по умолчанию). Когда действует параметр READ_COMMITTED_SNAPSHOT со значением OFF, компонент ядро СУБД использует разделяемые блокировки для обеспечения уровня изоляции по умолчанию. Если параметру базы данных READ_COMMITTED_SNAPSHOT присвоить значение ON, то ядро СУБД использует управление версиями строк и режим изоляции моментальных снимков по умолчанию вместо блокировок для защиты данных.
Как работают изоляция моментальных снимков и управление версиями строк
Если включен уровень изоляции SNAPSHOT, то при обновлении каждой строки компонент SQL Server Database Engine сохраняет копию исходной строки в базе данных tempdb и добавляет в строку порядковый номер транзакции. Ниже приведены происходящие события в последовательном порядке.
Инициируется новая транзакция, и ей назначается порядковый номер.
Ядро СУБД считывает строку в рамках транзакции и извлекает из базы данных tempdb версию строки, номер последовательности которой наиболее близок к номеру последовательности транзакции, но меньше него.
Ядро СУБД проверяет, не находится ли порядковый номер транзакции в списке порядковых номеров незафиксированных транзакций, активных при запуске транзакции моментальных снимков.
Транзакция считывает версию строки, которая была текущей во время запуска транзакции, из базы данных tempdb. Новые строки, вставленные после запуска транзакции, не будут видны, так как эти порядковые номера будут выше, чем значение порядкового номера транзакции.
Текущая транзакция будет видеть строки, которые были удалены после ее начала, поскольку в tempdb будет версия строки с меньшим значением номера последовательности.
Общий эффект изоляции моментальных снимков заключается в том, что транзакция видит все данные в том состоянии, в котором они существовали на момент ее начала, не учитывая и не устанавливая никаких блокировок на базовые таблицы. Это может привести к улучшению производительности в ситуациях, когда возникает состязание.
Транзакция моментального снимка всегда использует оптимистический контроль параллелизма, не применяя блокировки, которые препятствовали бы обновлению строк другими транзакциями. Если транзакция моментального снимка пытается зафиксировать обновление строки, которая была изменена после начала транзакции, транзакция откатывается и генерируется ошибка.
Использование изоляции снимков в ADO.NET
Изоляция моментальных снимков поддерживается в ADO.NET классом SqlTransaction. Если в базе данных включена изоляция моментального снимка, но настройка конфигурации не выполнена с учетом параметра READ_COMMITTED_SNAPSHOT со значением ON, то необходимо инициировать транзакцию SqlTransaction с помощью значения перечисления IsolationLevel.Snapshot при вызове метода BeginTransaction. В этом фрагменте кода предполагается, что подключение является открытым объектом SqlConnection.
SqlTransaction sqlTran =
connection.BeginTransaction(IsolationLevel.Snapshot);
Пример
В следующем примере демонстрируется поведение различных уровней изоляции при попытке доступа к заблокированным данным. Этот пример не предназначен для использования в рабочем коде.
В коде устанавливается соединение с образцом базы данных AdventureWorks в SQL Server, создается таблица с именем TestSnapshot и производится вставка одной строки данных. В коде используется инструкция Transact-SQL ALTER DATABASE для включения изоляции на основе моментальных снимков для базы данных, но параметр READ_COMMITTED_SNAPSHOT не задается, в результате продолжает действовать поведение уровня изоляции READ COMMITTED, используемое по умолчанию. Затем код выполняет следующие действия.
Он начинает, но не завершает транзакцию sqlTransaction1, которая использует уровень изоляции SERIALIZABLE для запуска транзакции обновления. При этом блокируется таблица.
Он открывает второе соединение и инициирует вторую транзакцию с уровнем изоляции SNAPSHOT для чтения данных из таблицы TestSnapshot. Так как изоляция моментальных снимков включена, эта транзакция может считывать данные, существовавшие до начала транзакции sqlTransaction1.
Открывается третье подключение и инициируется транзакция с использованием уровня изоляции READ COMMITTED, чтобы попытаться прочитать данные из таблицы. В этом случае код не может прочитать данные, так как он не может пройти мимо блокировок, установленных на таблице в первой транзакции, и происходит тайм-аут. Тот же результат был бы получен при использовании уровней изоляции REPEATABLE READ и SERIALIZABLE, поскольку на этих уровнях изоляции также невозможно прочитать данные, минуя блокировки, установленные в первой транзакции.
Открывается четвертое подключение и инициируется транзакция с использованием уровня изоляции READ UNCOMMITTED, при котором выполняется "грязное" чтение незафиксированного значения из sqlTransaction1. Это значение может фактически не существовать в базе данных, если первая транзакция не будет зафиксирована.
Выполняется откат первой транзакции и очистка: удаляется таблица TestSnapshot и отключается изоляция моментальных снимков для базы данных AdventureWorks.
Примечание.
В следующих примерах используется та же строка подключения, в которой отключено использование пулов подключений. Если подключение входит в пул, сброс его уровня изоляции не приводит к сбросу уровня изоляции на сервере. В результате последующие подключения, использующие одно и то же внутреннее подключение в составе пула, запускаются с уровнями изоляции, заданными для подключения в пуле. Вместо отключения пулов подключений можно явно задать уровень изоляции для каждого подключения.
using Microsoft.Data.SqlClient;
class Program
{
static void Main()
{
// Assumes GetConnectionString returns a valid connection string
// where pooling is turned off by setting Pooling=False;.
string connectionString = GetConnectionString();
using (SqlConnection connection1 = new SqlConnection(connectionString))
{
// Drop the TestSnapshot table if it exists
connection1.Open();
SqlCommand command1 = connection1.CreateCommand();
command1.CommandText = "IF EXISTS "
+ "(SELECT * FROM sys.tables WHERE name=N'TestSnapshot') "
+ "DROP TABLE TestSnapshot";
try
{
command1.ExecuteNonQuery();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
// Enable Snapshot isolation
command1.CommandText =
"ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION ON";
command1.ExecuteNonQuery();
// Create a table named TestSnapshot and insert one row of data
command1.CommandText =
"CREATE TABLE TestSnapshot (ID int primary key, valueCol int)";
command1.ExecuteNonQuery();
command1.CommandText =
"INSERT INTO TestSnapshot VALUES (1,1)";
command1.ExecuteNonQuery();
// Begin, but do not complete, a transaction to update the data
// with the Serializable isolation level, which locks the table
// pending the commit or rollback of the update. The original
// value in valueCol was 1, the proposed new value is 22.
SqlTransaction transaction1 =
connection1.BeginTransaction(IsolationLevel.Serializable);
command1.Transaction = transaction1;
command1.CommandText =
"UPDATE TestSnapshot SET valueCol=22 WHERE ID=1";
command1.ExecuteNonQuery();
// Open a second connection to AdventureWorks
using (SqlConnection connection2 = new SqlConnection(connectionString))
{
connection2.Open();
// Initiate a second transaction to read from TestSnapshot
// using Snapshot isolation. This will read the original
// value of 1 since transaction1 has not yet committed.
SqlCommand command2 = connection2.CreateCommand();
SqlTransaction transaction2 =
connection2.BeginTransaction(IsolationLevel.Snapshot);
command2.Transaction = transaction2;
command2.CommandText =
"SELECT ID, valueCol FROM TestSnapshot";
SqlDataReader reader2 = command2.ExecuteReader();
while (reader2.Read())
{
Console.WriteLine("Expected 1,1 Actual "
+ reader2.GetValue(0).ToString()
+ "," + reader2.GetValue(1).ToString());
}
transaction2.Commit();
}
// Open a third connection to AdventureWorks and
// initiate a third transaction to read from TestSnapshot
// using ReadCommitted isolation level. This transaction
// will not be able to view the data because of
// the locks placed on the table in transaction1
// and will time out after 4 seconds.
// You would see the same behavior with the
// RepeatableRead or Serializable isolation levels.
using (SqlConnection connection3 = new SqlConnection(connectionString))
{
connection3.Open();
SqlCommand command3 = connection3.CreateCommand();
SqlTransaction transaction3 =
connection3.BeginTransaction(IsolationLevel.ReadCommitted);
command3.Transaction = transaction3;
command3.CommandText =
"SELECT ID, valueCol FROM TestSnapshot";
command3.CommandTimeout = 4;
try
{
SqlDataReader sqldatareader3 = command3.ExecuteReader();
while (sqldatareader3.Read())
{
Console.WriteLine("You should never hit this.");
}
transaction3.Commit();
}
catch (Exception ex)
{
Console.WriteLine("Expected timeout expired exception: "
+ ex.Message);
transaction3.Rollback();
}
}
// Open a fourth connection to AdventureWorks and
// initiate a fourth transaction to read from TestSnapshot
// using the ReadUncommitted isolation level. ReadUncommitted
// will not hit the table lock, and will allow a dirty read
// of the proposed new value 22 for valueCol. If the first
// transaction rolls back, this value will never actually have
// existed in the database.
using (SqlConnection connection4 = new SqlConnection(connectionString))
{
connection4.Open();
SqlCommand command4 = connection4.CreateCommand();
SqlTransaction transaction4 =
connection4.BeginTransaction(IsolationLevel.ReadUncommitted);
command4.Transaction = transaction4;
command4.CommandText =
"SELECT ID, valueCol FROM TestSnapshot";
SqlDataReader reader4 = command4.ExecuteReader();
while (reader4.Read())
{
Console.WriteLine("Expected 1,22 Actual "
+ reader4.GetValue(0).ToString()
+ "," + reader4.GetValue(1).ToString());
}
transaction4.Commit();
}
// Roll back the first transaction
transaction1.Rollback();
}
// CLEANUP
// Delete the TestSnapshot table and set
// ALLOW_SNAPSHOT_ISOLATION OFF
using (SqlConnection connection5 = new SqlConnection(connectionString))
{
connection5.Open();
SqlCommand command5 = connection5.CreateCommand();
command5.CommandText = "DROP TABLE TestSnapshot";
SqlCommand command6 = connection5.CreateCommand();
command6.CommandText =
"ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION OFF";
try
{
command5.ExecuteNonQuery();
command6.ExecuteNonQuery();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Console.WriteLine("Done!");
}
static private string GetConnectionString()
{
// To avoid storing the connection string in your code,
// you can retrieve it from a configuration file, using the
// System.Configuration.ConfigurationSettings.AppSettings property
return "Data Source=localhost;Initial Catalog=AdventureWorks;"
+ "Integrated Security=SSPI";
}
}
Пример
В следующем примере демонстрируется поведение изоляции моментальных снимков при изменении данных. Код выполняет следующие действия.
Подключается к образцовой базе данных AdventureWorks и включает изоляцию SNAPSHOT.
Создает таблицу с именем TestSnapshotUpdate и вставляет три строки из образца данных.
Запускает, но не завершает, транзакцию sqlTransaction1 с уровнем изоляции SNAPSHOT. В транзакции выбираются три строки данных.
Открывает второе соединение SqlConnection с базой данных AdventureWorks и создает вторую транзакцию, используя уровень изоляции READ COMMITTED, который обновляет значение в одной из строк, выбранных в sqlTransaction1.
Подтверждает транзакцию sqlTransaction2.
Выполняется возврат к транзакции sqlTransaction1, и осуществляется попытка обновить ту же строку, которая уже зафиксирована в sqlTransaction1. Выдается ошибка 3960, и sqlTransaction1 автоматически откатывается. Сообщения SqlException.Number и SqlException.Message отображаются в окне консоли.
Выполняет код очистки, который отключает изоляцию моментальных снимков в AdventureWorks и удаляет таблицу TestSnapshotUpdate.
using Microsoft.Data.SqlClient; using System.Data.Common; class Program { static void Main() { // Assumes GetConnectionString returns a valid connection string // where pooling is turned off by setting Pooling=False;. string connectionString = GetConnectionString(); using (SqlConnection connection1 = new SqlConnection(connectionString)) { connection1.Open(); SqlCommand command1 = connection1.CreateCommand(); // Enable Snapshot isolation in AdventureWorks command1.CommandText = "ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION ON"; try { command1.ExecuteNonQuery(); Console.WriteLine( "Snapshot Isolation turned on in AdventureWorks."); } catch (Exception ex) { Console.WriteLine("ALLOW_SNAPSHOT_ISOLATION ON failed: {0}", ex.Message); } // Create a table command1.CommandText = "IF EXISTS " + "(SELECT * FROM sys.tables " + "WHERE name=N'TestSnapshotUpdate')" + " DROP TABLE TestSnapshotUpdate"; command1.ExecuteNonQuery(); command1.CommandText = "CREATE TABLE TestSnapshotUpdate " + "(ID int primary key, CharCol nvarchar(100));"; try { command1.ExecuteNonQuery(); Console.WriteLine("TestSnapshotUpdate table created."); } catch (Exception ex) { Console.WriteLine("CREATE TABLE failed: {0}", ex.Message); } // Insert some data command1.CommandText = "INSERT INTO TestSnapshotUpdate VALUES (1,N'abcdefg');" + "INSERT INTO TestSnapshotUpdate VALUES (2,N'hijklmn');" + "INSERT INTO TestSnapshotUpdate VALUES (3,N'opqrstuv');"; try { command1.ExecuteNonQuery(); Console.WriteLine("Data inserted TestSnapshotUpdate table."); } catch (Exception ex) { Console.WriteLine(ex.Message); } // Begin, but do not complete, a transaction // using the Snapshot isolation level. SqlTransaction transaction1 = null; try { transaction1 = connection1.BeginTransaction(IsolationLevel.Snapshot); command1.CommandText = "SELECT * FROM TestSnapshotUpdate WHERE ID BETWEEN 1 AND 3"; command1.Transaction = transaction1; command1.ExecuteNonQuery(); Console.WriteLine("Snapshot transaction1 started."); // Open a second Connection/Transaction to update data // using ReadCommitted. This transaction should succeed. using (SqlConnection connection2 = new SqlConnection(connectionString)) { connection2.Open(); SqlCommand command2 = connection2.CreateCommand(); command2.CommandText = "UPDATE TestSnapshotUpdate SET CharCol=" + "N'New value from Connection2' WHERE ID=1"; SqlTransaction transaction2 = connection2.BeginTransaction(IsolationLevel.ReadCommitted); command2.Transaction = transaction2; try { command2.ExecuteNonQuery(); transaction2.Commit(); Console.WriteLine( "transaction2 has modified data and committed."); } catch (SqlException ex) { Console.WriteLine(ex.Message); transaction2.Rollback(); } finally { transaction2.Dispose(); } } // Now try to update a row in Connection1/Transaction1. // This transaction should fail because Transaction2 // succeeded in modifying the data. command1.CommandText = "UPDATE TestSnapshotUpdate SET CharCol=" + "N'New value from Connection1' WHERE ID=1"; command1.Transaction = transaction1; command1.ExecuteNonQuery(); transaction1.Commit(); Console.WriteLine("You should never see this."); } catch (SqlException ex) { Console.WriteLine("Expected failure for transaction1:"); Console.WriteLine(" {0}: {1}", ex.Number, ex.Message); } finally { transaction1.Dispose(); } } // CLEANUP: // Turn off Snapshot isolation and delete the table using (SqlConnection connection3 = new SqlConnection(connectionString)) { connection3.Open(); SqlCommand command3 = connection3.CreateCommand(); command3.CommandText = "ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION OFF"; try { command3.ExecuteNonQuery(); Console.WriteLine( "CLEANUP: Snapshot isolation turned off in AdventureWorks."); } catch (Exception ex) { Console.WriteLine("CLEANUP FAILED: {0}", ex.Message); } command3.CommandText = "DROP TABLE TestSnapshotUpdate"; try { command3.ExecuteNonQuery(); Console.WriteLine("CLEANUP: TestSnapshotUpdate table deleted."); } catch (Exception ex) { Console.WriteLine("CLEANUP FAILED: {0}", ex.Message); } } Console.WriteLine("Done"); Console.ReadLine(); } static private string GetConnectionString() { // To avoid storing the connection string in your code, // you can retrieve it from a configuration file, using the // System.Configuration.ConfigurationSettings.AppSettings property return "Data Source=(local);Initial Catalog=AdventureWorks;" + "Integrated Security=SSPI;Pooling=false"; } }
Использование указаний блокировки с изоляцией моментальных снимков
В предыдущем примере первая транзакция выбирает данные, а вторая обновляет данные до того, как первая транзакция сможет завершиться, что приведет к конфликту обновления, когда первая транзакция попытается обновить одну и ту же строку. Можно уменьшить вероятность конфликтов обновления в длительных транзакциях моментальных снимков, предоставив указания блокировки в начале транзакции. Следующая инструкция SELECT использует указание UPDLOCK для блокировки выбранных строк:
SELECT * FROM TestSnapshotUpdate WITH (UPDLOCK)
WHERE PriKey BETWEEN 1 AND 3
Использование подсказки блокировки UPDLOCK блокирует любые попытки обновить эти строки до завершения первой транзакции. Это гарантирует, что выбранные строки не будут иметь конфликтов, когда они будут обновлены позже в транзакции. См. раздел "Locking Hints" в электронной документации SQL Server.
Если в вашем приложении много конфликтов, изоляция снимков может быть не лучшим выбором. Указания следует использовать, только если это действительно требуется. Приложение не должно быть спроектировано таким образом, чтобы оно постоянно полагалось на указания блокировки для своей работы.