【问题标题】:How to Improve the ADO Lookup Speed?如何提高 ADO 查找速度?
【发布时间】:2020-02-23 09:50:49
【问题描述】:

我通过 Visual Studio 2008 + ADO(不是 ADO.net)编写了一个 C++ 应用程序。它将一一完成以下任务:

  1. 在SQL Server数据库中创建表,如下:
CREATE TABLE MyTable 
(
     [S] bigint, 
     [L] bigint, 
     [T] tinyint,   
     [I1] int, 
     [I2] smallint, 
     [P] bigint, 
     [PP] bigint, 
     [NP] bigint, 
     [D] bit, 
     [U] bit
);
  1. 通过BULK INSERT插入5,030,242条记录

  2. 在表上创建索引:

CREATE Index [MyIndex] ON MyTable ([P]);
  1. 启动一个查找 65,000,000 次的函数。每次查找都使用以下查询:
SELECT [S], [L] 
FROM MyTable 
WHERE [P] = ?

每次查询要么不返回任何内容,要么返回一行。如果用 [S] 和 [L] 获取一行,我会将 [S] 转换为文件指针,然后从 [L] 指定的偏移量读取数据。

第 4 步需要很长时间。所以我尝试分析它并找出查找查询花费的时间最多。每次查找大约需要 0.01458 秒。

我尝试通过执行以下任务来提高性能:

  1. 使用参数化 ADO 查询。见第四步

  2. 仅选择所需的列。最初我在第 4 步中使用“Select *”,现在我使用 Select [S], [L]。这将性能提高了约 1.5%。

  3. 尝试了 [P] 的聚集索引和非聚集索引。看来使用非聚集索引会好一些。

还有其他空间可以提高查找性能吗?

注意[P] 在表格中是唯一的。

非常感谢。

【问题讨论】:

  • 我的建议是不要一次看每个。RBAR 几乎总是很慢
  • 一旦你有了S和L,你会用它做什么?查找值 6500 万次并不是一个好的设计模式。
  • 您可以设置许多选项来加快查找速度,也许 sql server 中的分区可以帮助您。如果您想以最佳速度加载数据,您可以使用 In-Memory OLTP 技术。在下一个级别,您可以配置 hadoop。在执行此解决方案之前,请检查您的硬件瓶颈,例如旧存储 (hdd) 或网络工作负载
  • 您也可以在您的应用程序中而不是 SQL Server 中进行所有查找。这可能是最快的。
  • @alancc,如果您要批量加载 65M 行并执行基于集合的查询,SQL Server 将为 5M 行表构建一个哈希表,然后执行 65M 哈希查找。您可以在您的应用程序中执行相同的操作,而无需将数据推送到 SQL Server。 SQL Server 确实适用于关系数据库 DML,但您在 x-y question 中没有提到 RDBMS 持久性要求。

标签: sql-server select indexing lookup


【解决方案1】:

您需要批处理工作并执行一个返回多行的查询,而不是多个查询,每个查询只返回一行(并导致单独的数据库往返)。

在 SQL Server 中执行此操作的方法是重写查询以使用表值参数 (TVP),并一次性传递所有搜索条件(在您的问题中表示为 ?)。

首先我们需要声明 TVP 将使用的类型:

CREATE TYPE MyTableSearch AS TABLE (
    P bigint NOT NULL
);

然后新的查询将非常简单:

SELECT
    S,
    L
FROM
    @input I
    JOIN MyTable
        ON I.P = MyTable.P;

主要的复杂性在于客户端,如何将 TVP 绑定到查询。不幸的是,我对 ADO 并不熟悉——就其价值而言,这就是它在 ADO.NET 和 C# 下的实现方式:

static IEnumerable<(long S, long L)> Find(
    SqlConnection conn,
    SqlTransaction tran,
    IEnumerable<long> input
) {

    const string sql = @"
        SELECT
            S,
            L
        FROM
            @input I
            JOIN MyTable
                ON I.P = MyTable.P
    ";

    using (var cmd = new SqlCommand(sql, conn, tran)) {

        var record = new SqlDataRecord(new SqlMetaData("P", SqlDbType.BigInt));

        var param = new SqlParameter("input", SqlDbType.Structured) {
            Direction = ParameterDirection.Input,
            TypeName = "MyTableSearch",
            Value = input.Select(
                p => {
                    record.SetValue(0, p);
                    return record;
                }
            )
        };

        cmd.Parameters.Add(param);

        using (var reader = cmd.ExecuteReader())
            while (reader.Read())
                yield return (reader.GetInt64(0), reader.GetInt64(1));

    }

}

请注意,我们对所有输入行重复使用相同的SqlDataRecord,这样可以最大限度地减少分配。这是记录在案的行为,它之所以有效,是因为 ADO.NET 流式传输 TVP。


注意:[P] 在表中是唯一的。

那么你应该使 P 上的索引也唯一 - 为了正确并避免浪费唯一性上的空间。

【讨论】:

  • ADO classic 不支持 TVP 或自 SQL Server 2000 以来引入的新类型。也就是说,可以在托管 C++ 中使用这种 ADO.NET 技术。
  • TVP 相对于参数化 SQL 查询的优势是什么?看来TVP和后者差不多。
  • @alancc TVP 只是参数化查询的方法之一。与标量参数相反,表值参数允许您在一次数据库往返中将许多值传递给查询。注意Find 如何接受IEnumerable&lt;long&gt;,但只调用一次ExecuteReader
  • @alancc 还有一件事......显然是 ODBC supports TVPs,所以在这种情况下,您可以使用来自非托管 C++(而不是 ADO)的纯 ODBC。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-29
  • 1970-01-01
  • 2021-09-18
  • 1970-01-01
  • 1970-01-01
  • 2021-11-17
相关资源
最近更新 更多