在单独的线程上打开 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();
        }
    }

您还可以进一步减少线程数,或者在每条消息之后执行短暂的休眠以减少服务器上的负载(如果负载仍然过高)。