在单独的线程上打开 SQL 连接 50k 次是否有缺点?
Are there downsides from opening a SQL Connection 50k times on separate threads?
我想知道是否有更好的推荐方法来做到这一点。我的 DBA 已通知我以下代码导致生产中的页面锁存和页面等待以及最终超时。我必须处理到达消息传递服务器的尽可能多的消息。我能想到的唯一方法是通过循环和线程。每个线程将打开一个新连接到 SQL 并传递一个 xml 数据列表。
我不确定他为什么说页面等待正在发生,因为我虽然 SQL 可以处理数百万个连接。可能是连接字符串本身吗?我正在关闭我的联系,但这里的任何建议都会有所帮助。谢谢
这是连接字符串。我有 10k 的池,因为没有它我会收到池耗尽错误。我可以将值降低到 1000,但我不确定这是否会产生影响。
var connectionString="Server=myServerAddress;Database=myDataBase;Trusted_Connection=True;MultipleActiveResultSets=true;Asynchronous Processing=True;Pooling=True;Max Pool Size=10000;Min Pool Size=100;"
这里是线程的一个例子。我在这里选择了 50k,但在高峰时段队列中的消息可能会超过 100k。
var results = Enumerable.Range(0, 50000)
.AsParallel()
.WithDegreeofParallelism(Environment.ProcessorCount)
.Select(x => InsertPayloadtoDB(xmlList))
.ToList()
有效负载是一个 XML,它被发送到存储过程进行处理。
public void InsertPayloadtoDB(XmlElement xmlList)
{
var task = Task.Run(async () => await ExecuteNonQueryAsync("MyStoredProcedure",
new List<SqlParameter>{ new SqlParameter("@xmlList", xmlList.ToString())}));
}
public static Task<bool> ExecuteNonQueryAsync(string storedProcedureName,
List<SqlParameter> parameters)
{
using(var connection = new SqlConnection(connectionString))
{
using(var cmd = new SqlCommand(storedProcedureName, connection))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = 300;
cmd.Parameters.AddRange(parameters);
await connection.OpenAsync();
await cmd.ExecuteNonQueryAsync();
cmd.Parameters.Clear();
}
connection.Close();
}
}
这是存储过程,它只接受我发送的内容并插入 table。
DECLARE @xmlList = NULL
BEGIN
DECLARE @MyTable TABLE
(
First varchar(40),
Last varchar(40),
Address varchar(50),
-- ... 15 more columns
)
BEGIN TRANSACTION
INSERT INTO @MyTable (First varchar(40),
Last varchar(40),
Address varchar(50),
--... 15 more columns
)
SELECT
N.value('(Name)[1]', 'varchar(40)') AS Name
N.value('(Last)[1]', 'varchar(40)') AS Last
N.value('(Address)[1]', 'varchar(50)') AS Address
FROM
@xmlList.nodes('//ClientInfo') AS T(N)
// This is where the deadlock seems to happen as when
// I comment this out everything is smooth sailing.
INSERT INTO ClientInfoTable (Name, Last, Address, ...)
SELECT
Name, Last,
Address,
...
FROM @MyTable
)
COMMIT TRANSACTION
END
您有 Pooling=True
,因此 SqlConnection.Open()
从连接池中获取一个 already-open 连接,然后 SqlConnection.Close()
returns 将其放入池中。
并且您有 .WithDegreeofParallelism(Environment.ProcessorCount)
,因此每次都会有少量线程使用连接。
但是你有一个很好的、适度的并行循环,但是你同时启动了所有任务。基本上你不需要并行循环 和 异步任务。只需在您的线程上使用同步代码。例如
public void InsertPayloadtoDB(XmlElement xmlList)
{
using (var connection = new SqlConnection(connectionString))
{
using (var cmd = new SqlCommand("MyStoredProcedure", connection))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = 300;
cmd.Parameters.Add(new SqlParameter("@xmlList", xmlList.ToString()) );
connection.Open();
cmd.ExecuteNonQuery();
}
connection.Close();
}
}
您还可以进一步减少线程数,或者在每条消息之后执行短暂的休眠以减少服务器上的负载(如果负载仍然过高)。
我想知道是否有更好的推荐方法来做到这一点。我的 DBA 已通知我以下代码导致生产中的页面锁存和页面等待以及最终超时。我必须处理到达消息传递服务器的尽可能多的消息。我能想到的唯一方法是通过循环和线程。每个线程将打开一个新连接到 SQL 并传递一个 xml 数据列表。
我不确定他为什么说页面等待正在发生,因为我虽然 SQL 可以处理数百万个连接。可能是连接字符串本身吗?我正在关闭我的联系,但这里的任何建议都会有所帮助。谢谢
这是连接字符串。我有 10k 的池,因为没有它我会收到池耗尽错误。我可以将值降低到 1000,但我不确定这是否会产生影响。
var connectionString="Server=myServerAddress;Database=myDataBase;Trusted_Connection=True;MultipleActiveResultSets=true;Asynchronous Processing=True;Pooling=True;Max Pool Size=10000;Min Pool Size=100;"
这里是线程的一个例子。我在这里选择了 50k,但在高峰时段队列中的消息可能会超过 100k。
var results = Enumerable.Range(0, 50000)
.AsParallel()
.WithDegreeofParallelism(Environment.ProcessorCount)
.Select(x => InsertPayloadtoDB(xmlList))
.ToList()
有效负载是一个 XML,它被发送到存储过程进行处理。
public void InsertPayloadtoDB(XmlElement xmlList)
{
var task = Task.Run(async () => await ExecuteNonQueryAsync("MyStoredProcedure",
new List<SqlParameter>{ new SqlParameter("@xmlList", xmlList.ToString())}));
}
public static Task<bool> ExecuteNonQueryAsync(string storedProcedureName,
List<SqlParameter> parameters)
{
using(var connection = new SqlConnection(connectionString))
{
using(var cmd = new SqlCommand(storedProcedureName, connection))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = 300;
cmd.Parameters.AddRange(parameters);
await connection.OpenAsync();
await cmd.ExecuteNonQueryAsync();
cmd.Parameters.Clear();
}
connection.Close();
}
}
这是存储过程,它只接受我发送的内容并插入 table。
DECLARE @xmlList = NULL
BEGIN
DECLARE @MyTable TABLE
(
First varchar(40),
Last varchar(40),
Address varchar(50),
-- ... 15 more columns
)
BEGIN TRANSACTION
INSERT INTO @MyTable (First varchar(40),
Last varchar(40),
Address varchar(50),
--... 15 more columns
)
SELECT
N.value('(Name)[1]', 'varchar(40)') AS Name
N.value('(Last)[1]', 'varchar(40)') AS Last
N.value('(Address)[1]', 'varchar(50)') AS Address
FROM
@xmlList.nodes('//ClientInfo') AS T(N)
// This is where the deadlock seems to happen as when
// I comment this out everything is smooth sailing.
INSERT INTO ClientInfoTable (Name, Last, Address, ...)
SELECT
Name, Last,
Address,
...
FROM @MyTable
)
COMMIT TRANSACTION
END
您有 Pooling=True
,因此 SqlConnection.Open()
从连接池中获取一个 already-open 连接,然后 SqlConnection.Close()
returns 将其放入池中。
并且您有 .WithDegreeofParallelism(Environment.ProcessorCount)
,因此每次都会有少量线程使用连接。
但是你有一个很好的、适度的并行循环,但是你同时启动了所有任务。基本上你不需要并行循环 和 异步任务。只需在您的线程上使用同步代码。例如
public void InsertPayloadtoDB(XmlElement xmlList)
{
using (var connection = new SqlConnection(connectionString))
{
using (var cmd = new SqlCommand("MyStoredProcedure", connection))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = 300;
cmd.Parameters.Add(new SqlParameter("@xmlList", xmlList.ToString()) );
connection.Open();
cmd.ExecuteNonQuery();
}
connection.Close();
}
}
您还可以进一步减少线程数,或者在每条消息之后执行短暂的休眠以减少服务器上的负载(如果负载仍然过高)。