У нас есть приложение, которое изначально было написано как настольное приложение, много лет назад. Он запускает транзакцию всякий раз, когда вы открываете экран редактирования, и фиксирует, если вы нажимаете кнопку ОК, или откатывается, если вы нажимаете кнопку Отмена. Это хорошо работает для настольных приложений, но теперь мы пытаемся перейти на ADO.NET и SQL Server, и длительные транзакции проблематичны.
Я обнаружил, что у нас будет проблема, когда несколько пользователей одновременно пытаются редактировать (разные подмножества) одну и ту же таблицу. В нашей старой базе данных транзакция каждого пользователя получала блокировки на уровне записи для каждой записи, которую они изменили во время своей транзакции; так как разные пользователи редактировали разные записи, у каждого свои блокировки и все работает. Но в SQL Server, как только один пользователь редактирует запись внутри транзакции, SQL Server блокируется на всю таблицу . Когда второй пользователь пытается отредактировать другую запись в той же таблице, приложение второго пользователя просто блокируется, потому что SqlConnection блокируется до тех пор, пока первый пользователь не выполнит фиксацию или откат.
Я знаю, что длительные транзакции плохие, и я знаю, что лучшим решением было бы изменить эти экраны, чтобы они больше не оставляли транзакции открытыми в течение длительного времени. Но поскольку это будет означать некоторые инвазивные и рискованные изменения, я также хочу исследовать, есть ли способ заставить этот код работать и работать как есть, просто чтобы я знал, какие у меня есть варианты.
Как получить транзакции двух разных пользователей в SQL Server для блокировки отдельных записей вместо всей таблицы?
Вот быстрое консольное приложение, которое иллюстрирует проблему. Я создал базу данных с именем «test1», с одной таблицей под названием «Значения», в которой есть только столбцы ID (int) и Value (nvarchar). Если вы запускаете приложение, оно запрашивает идентификатор для изменения, запускает транзакцию, изменяет эту запись, а затем оставляет транзакцию открытой, пока вы не нажмете ENTER. Я хочу быть в состоянии
- запустите программу и скажите ей обновить ID 1;
- позвольте ему получить свою транзакцию и изменить запись;
- запустите вторую копию программы и скажите ей обновить ID 2;
- может обновлять (и фиксировать), пока транзакция первого приложения еще открыта.
В настоящее время он останавливается на шаге 4, пока я не вернусь к первой копии приложения и не закрою ее или не нажму ENTER, чтобы она зафиксировалась. Вызов command.ExecuteNonQuery блокируется, пока не будет закрыто первое соединение.
public static void Main()
{
Console.Write("ID to update: ");
var id = int.Parse(Console.ReadLine());
Console.WriteLine("Starting transaction");
using (var scope = new TransactionScope())
using (var connection = new SqlConnection(@"Data Source=localhost\sqlexpress;Initial Catalog=test1;Integrated Security=True"))
{
connection.Open();
var command = connection.CreateCommand();
command.CommandText = "UPDATE [Values] SET Value = 'Value' WHERE ID = " + id;
Console.WriteLine("Updating record");
command.ExecuteNonQuery();
Console.Write("Press ENTER to end transaction: ");
Console.ReadLine();
scope.Complete();
}
}
Вот некоторые вещи, которые я уже пробовал, без изменений в поведении:
- Изменение уровня изоляции транзакции на «чтение незафиксировано»
- Указание «WITH (ROWLOCK)» в операторе UPDATE