这似乎是下降到连接池。
我在连接字符串中注意到,我们有以下几点:
Pooling=true; Min Pool Size=5; Max Pool Size=100; Connect Timeout=7
我创建了一些单元测试来证明这一点。
SQL创建脚本
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[usp_GetName]
(
@id int,
@TransactionIsolation varchar(30) output
)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
set @TransactionIsolation = dbo.fn_GetTransactionIsolation();
SELECT [Id]
,[Name]
,[Status]
FROM [dbo].[NameTable]
where Id = @id
END
CREATE FUNCTION [dbo].[fn_GetTransactionIsolation]
(
)
RETURNS varchar(30)
AS
BEGIN
-- Declare the return variable here
DECLARE @til varchar(30)
select @til =
CASE transaction_isolation_level
WHEN 0 THEN 'Unspecified'
WHEN 1 THEN 'ReadUncommitted'
WHEN 2 THEN 'ReadCommitted'
WHEN 3 THEN 'Repeatable'
WHEN 4 THEN 'Serializable'
WHEN 5 THEN 'Snapshot'
ELSE 'Unknown'
END
FROM sys.dm_exec_sessions
where session_id = @@SPID
-- Return the result of the function
RETURN @til
END
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[NameTable](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[Status] [int] NOT NULL,
CONSTRAINT [PK_NameTable] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[SPLog](
[Id] [int] IDENTITY(1,1) NOT NULL,
[StoredProcName] [nvarchar](50) NOT NULL,
[LogDate] [datetime] NOT NULL,
CONSTRAINT [PK_SPLog] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
单元测试
using System;
using System.Data;
using System.Data.SqlClient;
using System.Threading;
using System.Transactions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TransactionTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void SPGet_NoTransaction_ShouldReturn_ReadCommitted()
{
using (var con = new SqlConnection(GetConnection()))
{
con.Open();
using (var cmd = new SqlCommand("usp_GetName", con))
{
cmd.CommandType = CommandType.StoredProcedure;
AddParameter(cmd, "@id", 1, SqlDbType.Int);
AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
}
reader.Close();
var transactionIsolation = (string)cmd.Parameters["@TransactionIsolation"].Value;
Assert.AreEqual("ReadCommitted", transactionIsolation);
}
}
}
[TestMethod]
public void SPUpdate_NoTransaction_ShouldReturn_ReadCommitted()
{
using (var con = new SqlConnection(GetConnection()))
{
con.Open();
using (var cmd = new SqlCommand("usp_UpdateName", con))
{
cmd.CommandType = CommandType.StoredProcedure;
AddParameter(cmd, "@id", 1, SqlDbType.Int);
AddParameter(cmd, "@name", "test update", SqlDbType.VarChar, 30);
AddParameter(cmd, "@status", 1, SqlDbType.Int);
AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
}
reader.Close();
var transactionIsolation = (string)cmd.Parameters["@TransactionIsolation"].Value;
Assert.AreEqual("ReadCommitted", transactionIsolation);
}
}
}
[TestMethod]
public void SPGet_TransactionSerializable_ShouldReturn_Serializable()
{
using (var tran = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.Serializable }))
{
using (var con = new SqlConnection(GetConnection()))
{
con.Open();
using (var cmd = new SqlCommand("usp_GetName", con))
{
cmd.CommandType = CommandType.StoredProcedure;
AddParameter(cmd, "@id", 1, SqlDbType.Int);
AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
}
reader.Close();
var transactionIsolation = (string)cmd.Parameters["@TransactionIsolation"].Value;
Assert.AreEqual("Serializable", transactionIsolation);
}
}
tran.Complete();
}
}
private static string GetConnection()
{
var builder = new SqlConnectionStringBuilder();
builder.InitialCatalog = "ACMETransactions";
builder.DataSource = ".";
builder.IntegratedSecurity = true;
builder.Pooling = true;
builder.MaxPoolSize = 100;
return builder.ToString();
}
private static void AddParameter(SqlCommand command, string name, object value, SqlDbType type, int size = -1, ParameterDirection direction = ParameterDirection.Input)
{
command.Parameters.Add(new SqlParameter
{
ParameterName = name,
SqlDbType = type,
Value = value,
Direction = direction
});
if (size != -1)
{
command.Parameters[command.Parameters.Count - 1].Size = size;
}
}
[TestMethod]
public void SPGet__MultiThread_Conflict()
{
string serializedIsolationResult = "";
string normalIsolationResult = "";
var normalThread = new Thread(new ThreadStart(() =>
{
using (var con = new SqlConnection(GetConnection()))
{
con.Open();
using (var cmd = new SqlCommand("usp_GetName", con))
{
cmd.CommandType = CommandType.StoredProcedure;
AddParameter(cmd, "@id", 1, SqlDbType.Int);
AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read()) ;
reader.Close();
normalIsolationResult = (string)cmd.Parameters["@TransactionIsolation"].Value;
}
}
}));
var serializedThread = new Thread(new ThreadStart(() =>
{
using (var tran = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.Serializable }))
{
using (var con = new SqlConnection(GetConnection()))
{
con.Open();
using (var cmd = new SqlCommand("usp_GetName", con))
{
cmd.CommandType = CommandType.StoredProcedure;
AddParameter(cmd, "@id", 1, SqlDbType.Int);
AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read()) ;
reader.Close();
serializedIsolationResult = (string)cmd.Parameters["@TransactionIsolation"].Value;
}
normalThread.Join();
Thread.Sleep(1000);
}
tran.Complete();
}
}));
serializedThread.Start();
normalThread.Start();
serializedThread.Join();
Assert.AreEqual("ReadCommitted", normalIsolationResult);
Assert.AreEqual("Serializable", serializedIsolationResult);
}
[TestMethod]
public void SPGet__MultiThread_NoTransactionScope()
{
string serializedIsolationResult = "";
string normalIsolationResult = "";
var normalThread = new Thread(new ThreadStart(() =>
{
using (var con = new SqlConnection(GetConnection()))
{
con.Open();
using (var cmd = new SqlCommand("usp_GetName", con))
{
cmd.CommandType = CommandType.StoredProcedure;
AddParameter(cmd, "@id", 1, SqlDbType.Int);
AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read()) ;
reader.Close();
normalIsolationResult = (string)cmd.Parameters["@TransactionIsolation"].Value;
}
}
}));
var serializedThread = new Thread(new ThreadStart(() =>
{
using (var con = new SqlConnection(GetConnection()))
{
con.Open();
using (var cmd = new SqlCommand("usp_GetName", con))
{
cmd.CommandType = CommandType.StoredProcedure;
AddParameter(cmd, "@id", 1, SqlDbType.Int);
AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read()) ;
reader.Close();
serializedIsolationResult = (string)cmd.Parameters["@TransactionIsolation"].Value;
}
}
}));
serializedThread.Start();
normalThread.Start();
serializedThread.Join();
Assert.AreEqual("ReadCommitted", normalIsolationResult);
Assert.AreEqual("ReadCommitted", serializedIsolationResult);
}
[TestMethod]
public void SPGet__MultiConnection()
{
string normalIsolationResult2 = "";
string normalIsolationResult1 = "";
using (var con = new SqlConnection(GetConnection()))
{
con.Open();
using (var cmd = new SqlCommand("usp_GetName", con))
{
cmd.CommandType = CommandType.StoredProcedure;
AddParameter(cmd, "@id", 1, SqlDbType.Int);
AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read()) ;
reader.Close();
normalIsolationResult1 = (string)cmd.Parameters["@TransactionIsolation"].Value;
}
}
using (var con = new SqlConnection(GetConnection()))
{
con.Open();
using (var cmd = new SqlCommand("usp_GetName", con))
{
cmd.CommandType = CommandType.StoredProcedure;
AddParameter(cmd, "@id", 1, SqlDbType.Int);
AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read()) ;
reader.Close();
normalIsolationResult2 = (string)cmd.Parameters["@TransactionIsolation"].Value;
}
}
Assert.AreEqual("ReadCommitted", normalIsolationResult1);
Assert.AreEqual("ReadCommitted", normalIsolationResult2);
}
[TestMethod]
public void SPGet__SingleConnection()
{
string normalIsolationResult2 = "";
string normalIsolationResult1 = "";
using (var con = new SqlConnection(GetConnection()))
{
con.Open();
using (var cmd = new SqlCommand("usp_GetName", con))
{
cmd.CommandType = CommandType.StoredProcedure;
AddParameter(cmd, "@id", 1, SqlDbType.Int);
AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read()) ;
reader.Close();
normalIsolationResult1 = (string)cmd.Parameters["@TransactionIsolation"].Value;
}
using (var cmd = new SqlCommand("usp_GetName", con))
{
cmd.CommandType = CommandType.StoredProcedure;
AddParameter(cmd, "@id", 1, SqlDbType.Int);
AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read()) ;
reader.Close();
normalIsolationResult2 = (string)cmd.Parameters["@TransactionIsolation"].Value;
}
}
Assert.AreEqual("ReadCommitted", normalIsolationResult1);
Assert.AreEqual("ReadCommitted", normalIsolationResult2);
}
}
}
简单地改变getConnection方法去除池/ MaxPoolSize导致检测每一次通过。
有了这些参数,一些测试将会失败。
我假设在带有死锁的实时环境中,我们正在目睹连接被重用,事务范围被设置为Serializable的其他地方,从代码执行任何更新,其中明确使用了TransactionScope。