Я собрал образец приложения, чтобы имитировать некоторые из основных функций кода нашего приложения. Пока я работал над этим, я немного ошибся в своем понимании TransactionScope
и SqlConnection
.
Проблема, которую я вижу, заключается в том, что я явно "вижу" IsolationLevel READ COMMITTED
, где я ожидал SERIALIZABLE
. Если только мои методы диагностики не ошибочны.
В настоящее время в моей базе данных включен Read Snapshot
, поэтому на скриншотах отображается READ COMMITTED SNAPSHOT
. Если этот параметр не включен, на снимках экрана будет отображаться READ COMMITTED
.
Код
Код выполняется локально на моем компьютере, подключающемся к локальному экземпляру SQL Server 2014. Это минимальный необходимый код.
Итак, чтобы воспроизвести этот сценарий из консольного приложения:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Transactions;
using IsolationLevel = System.Transactions.IsolationLevel;
namespace IsolationLevelConsoleProblem
{
class Program
{
static void Main(string[] args)
{
using (var tran = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions
{
IsolationLevel = IsolationLevel.Serializable,
Timeout = TimeSpan.FromMinutes(3)
}))
{
using (var connectionForRead = new SqlConnection(GetConnectionString("For Read")))
{
connectionForRead.Open();
using (var commandRead = new SqlCommand("usp_GetAllSimpleTable", connectionForRead))
{
commandRead.CommandType = CommandType.StoredProcedure;
commandRead.CommandTimeout = 120;
DataSet dataSet = new DataSet();
SqlDataAdapter adapter = new SqlDataAdapter
{
SelectCommand = commandRead
};
adapter.Fill(dataSet);
}
}
using (var connectionForUpdate = new SqlConnection(GetConnectionString("For Update")))
{
connectionForUpdate.Open();
using (var commandUpdate = new SqlCommand("usp_UpdateSimpleTable", connectionForUpdate))
{
commandUpdate.CommandTimeout = 120;
commandUpdate.CommandType = CommandType.StoredProcedure;
commandUpdate.Parameters.AddWithValue("@Guid", Guid.Parse("{74B3155E-138E-4881-BEE2-67FD141E29DA}"));
commandUpdate.Parameters.AddWithValue("@Name", $"Name {DateTime.UtcNow}");
commandUpdate.ExecuteNonQuery();
}
}
tran.Complete();
}
}
private static string GetConnectionString(string name)
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
builder.DataSource = ".\\SQL2014";
builder.InitialCatalog = "AcmeTransactions";
builder.IntegratedSecurity = true;
builder.MaxPoolSize = 200;
builder.ConnectTimeout = 120;
builder.ApplicationName = name;
return builder.ToString();
}
}
}
База данных
ПРИМЕЧАНИЕ. Я вручную добавил строку в таблицу для идентификатора GUID, указанного выше в коде. Я также добавил задержку ожидания в хранимую процедуру обновления, чтобы просмотреть уровень изоляции в сеансах.
Создать таблицу
CREATE TABLE [dbo].[SimpleTable](
[Guid] [uniqueidentifier] NOT NULL,
[Name] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_SimpleTable] PRIMARY KEY CLUSTERED
(
[Guid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Сохраненная процедура для чтения
CREATE PROCEDURE [dbo].[usp_GetAllSimpleTable]
AS
BEGIN
SELECT [Guid]
,[Name]
FROM [dbo].[SimpleTable]
END
Сохраненная процедура для обновления
create PROCEDURE [dbo].[usp_UpdateSimpleTable]
(
@Guid UNIQUEIDENTIFIER,
@Name NVARCHAR(50)
)
AS
BEGIN
UPDATE [dbo].[SimpleTable]
SET [Name] = @Name
WHERE [Guid] = @Guid
waitfor delay '00:00:40'
END
Диагностика
Я использую SQL для перечисления сеансов, включая уровень изоляции и выполняемый SQL. Я верю в правильность этого утверждения. Я также попытался вернуть явную проверку уровня изоляции в самой хранимой процедуре, которая возвращает READ COMMITTED
.
SELECT
d.name
, s.session_id
, CASE
WHEN s.transaction_isolation_level = 1
THEN 'READ UNCOMMITTED'
WHEN s.transaction_isolation_level = 2
AND is_read_committed_snapshot_on = 1
THEN 'READ COMMITTED SNAPSHOT'
WHEN s.transaction_isolation_level = 2
AND is_read_committed_snapshot_on = 0 THEN 'READ COMMITTED'
WHEN s.transaction_isolation_level = 3
THEN 'REPEATABLE READ'
WHEN s.transaction_isolation_level = 4
THEN 'SERIALIZABLE'
WHEN s.transaction_isolation_level = 5
THEN 'SNAPSHOT'
ELSE NULL
END
, sqltext.TEXT
FROM sys.dm_exec_sessions AS s
CROSS JOIN sys.databases AS d
join sys.dm_exec_requests er on s.session_id = er.session_id
CROSS APPLY sys.dm_exec_sql_text(er.sql_handle) AS sqltext
where d.name ='AcmeTransactions'
and s.program_name not in ('Microsoft SQL Server Management Studio - Query' , 'Microsoft SQL Server Management Studio')
Поэтому, когда вышеуказанное выполняется во время задержки, я вижу следующее:
Дальнейшее расследование
Если блок кода connectionForRead с использованием перемещается за пределы транзакции, я вижу следующую информацию о изоляции сеанса из SSMS:
Вот как я бы воспринимал немодифицированный код.
Вопросов
- Мой диагноз правильный? Оба соединения SQL работают как READ COMMITTED?
- Это лучший способ определить уровень транзакции выполняемых запросов?