【问题标题】:Is there a way to improve performance when sending a large number of IDs to SQL server for filtering?将大量 ID 发送到 SQL Server 进行过滤时,有没有办法提高性能?
【发布时间】:2017-03-14 20:20:53
【问题描述】:

我使用以下 C# 代码将 ID 列表发送到 SQL Server 2012。它过滤 mytable 的列 ID 并返回前 50 个匹配的 ID。

实际上执行查询大约需要 180 毫秒。数据库是本地的。我想知道是否有一些方法可以提高性能。我注意到性能与发送到 SQL 服务器的 id 数量直接相关,而不是与表中的实际记录数相关。如果我只发送一千条记录,它会非常快(

用户定义的表int_list_typemytable是这样定义的:

CREATE TABLE mytable (Id int NOT NULL PRIMARY KEY CLUSTERED)
CREATE TYPE int_list_type AS TABLE(Id int NOT NULL PRIMARY KEY CLUSTERED)

C#代码:

static void Main()
{       
    List<int> idsToSend = Enumerable.Range(0, 200000).ToList();
    List<int> idsResult = new List<int>();

    Stopwatch sw = Stopwatch.StartNew();
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        connection.Open();

        SqlCommand command = new SqlCommand(@" SELECT TOP 50 t.Id FROM MyTable t
                                                INNER JOIN @ids lt ON t.Id = lt.Id", 
                                               connection);

        command.Parameters.Add(new SqlParameter("@ids", SqlDbType.Structured)
        {
            TypeName = "int_list_type",
            Direction = ParameterDirection.Input,
            Value = GetSqlDataRecords(idsToSend)
        });

        SqlDataReader reader = command.ExecuteReader();
        while (reader.Read())
        {
            idsResult.Add(reader.GetInt32(0));
        }
    }
    Console.WriteLine(sw.Elasped);
}

private static IEnumerable<SqlDataRecord> GetSqlDataRecords(IEnumerable<int> values)
{
    SqlMetaData[] metaData = { new SqlMetaData("Id", SqlDbType.Int) };

    foreach (int value in values)
    {
        SqlDataRecord rec = new SqlDataRecord(metaData);
        rec.SetInt32(0, value);
        yield return rec;
    }
}

编辑:根据 Fabio 的建议,我查看了 GetSqlDataRecords() 方法,这是大部分时间都需要的。我是这样单独测试的:

Stopwatch sw = Stopwatch.StartNew();
GetSqlDataRecords(listOfIfs).ToList();
Console.WriteLine(sw.Elapsed);

【问题讨论】:

  • 旁注:使用 TOP x 没有 ORDER BY caluse 意味着您获得 x 条记录,但并不意味着您获得 first x 条记录 - 数据库表是本质上是无序的,因此如果没有ORDER BY 子句,就无法保证SELECT 语句返回的行的顺序。
  • 您是否检查过什么是实际的“瓶颈” - 数据库或GetSqlDataRecords 中的循环?
  • 要获得更准确的测量结果,您需要在 connection.open() 语句之后启动秒表。打开连接总是很慢。理想情况下,您需要缓存它或使用连接池。此外,在输出经过时间之前停止秒表。写入控制台也很慢,因此在执行此操作之前需要停止。
  • @jason.kaisersmith 是的,它提高了精度,但在 OP 中说,这些不是瓶颈。
  • 您可以尝试SELECT TOP 50 t.Id FROM MyTable t INNER JOIN @ids lt ON t.Id = lt.Id OPTION (RECOMPILE) ,以便它可以考虑表变量中的行数。

标签: c# sql-server performance primary-key bulk


【解决方案1】:

您可以尝试将 ID 列表作为逗号分隔的字符串列表传递,然后在 SQL 中查找 ID IN(ListOfIds) 的所有位置。

没有机会对此进行测试,但它过去为我解决了类似的问题。请让我知道它是否有任何不同(好或坏)。

【讨论】:

  • 我以前试过这个,速度太慢了。我在 SSMS 分析器下运行查询,大部分时间是解析查询(有意义)。
【解决方案2】:

+1 用于使用表值类型并将其作为参数传递。这是如何使用表值类型的教科书示例。 不幸的是,正如您所指出的,在传入非常大的数据数组时,您仍然会遇到性能问题。

您可以尝试使用 XML 来传递值:xml parsing with sql query XML 解析器在处理较大的数组时可能在您的环境中性能更高,请注意警告,保持命名空间简单或完全省略它们,否则性能会提高很多对于 100s 到 1000s 的较小数组,比表值类型差,在较大的数组中,您可能会看到更好的性能。

问题,您能否重新设计此解决方案,使 ID 列表已在数据库中?或者卸载 ID 列表的提取,以便首先为您的查询做准备?

我在我的应用程序中通过允许用户手动“标记”行、运行脚本或选择一些预编译逻辑来选择 ID 来实现这一点。 我将这些 id 存储在 Tag 表中(用户可以保存标签列表以在其他会话中重复使用)

现在 ID 列表已经在数据库中,我们只需将 ID 加入我们的选择列表。查询的执行并不比使用表值类型或解析 xml、json 或字符串的任何变体快,但我们绕过了通常成本最高的解析步骤。这样一来,现在数据在数据库中,SQL Server 更容易优化和缓存查询执行计划。

注意:当以表值类型、表变量或临时表的形式发送要在查询中执行的数据列表时,必须在 SQL 临时数据库中处理此数据列表。

您可能会发现一些涉及配置环境以支持此方案的奇特解决方案,但如果您可以更改流程以确保选择列表已在数据库中,则大部分繁重的工作都已为您完成。然后,您可以使用索引和其他传统 DBA 维护来进一步优化您的查询性能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-06
    • 2015-08-24
    • 2022-11-07
    • 1970-01-01
    • 2012-09-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多