我已经整理了一个示例应用程序来模仿我们的一些主要应用程序代码功能。在研究这个问题的同时,我对TransactionScope
和SqlConnection
的理解已经引起了一些曲解。
我看到的问题是明确地“看到”READ COMMITTED
的IsolationLevel,我期望SERIALIZABLE
。除非我的方法诊断有缺陷。
目前我的数据库已启用Read Snapshot
,因此屏幕截图显示READ COMMITTED SNAPSHOT
。如果未启用此功能,则屏幕截图将显示READ COMMITTED
。
代码在我的机器上本地运行,连接到SQL Server 2014的本地实例。这是所需的最小代码。
所以要从Console应用程序复制这个场景:
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移动到Transaction之外,那么我会从SSMS看到以下会话隔离信息:
这就是我将如何看待未经修改的代码。
答案 0 :(得分:1)
如果您运行的是早期版本的SQL Server 2014,则会看到此行为。最简单的修复方法是应用SP2(或更高版本)