【问题标题】:Can I speed up insertion into SQL Server while using INSERT INTO statements with ADO.net在 ADO.net 中使用 INSERT INTO 语句时,我可以加快插入 SQL Server 的速度吗
【发布时间】:2012-03-13 16:08:00
【问题描述】:

我正在寻找是否可以采取一些措施来显着加快 ADO.net 插入,但仍然使用多个 INSERT INTO table values (...) 命令进行插入。我正在将专有的、不可查询的数据库文件中的数据暂存到 SQL Server 中。我正在编写的实用程序将从脚本中使用。

我意识到为了获得最佳性能,INSERT INTO 是错误的追求途径,但我仍然想知道是否有一些 ADO.net 或 SQL Server 方法我应该尝试。

我正在使用 C# 2010 连接到 SQL Server 2008。我将我的性能与使用 Microsoft 的 ODBC 3.5 驱动程序和 Native Client 10.0 将数据插入 SQL Server 的非托管代码进行比较。期望能够与 ODBC 的性能相媲美是否合理?

目标表没有索引或其他约束、触发器,它只是一个临时表。源数据是一堆非标准数据类型,我必须将它们按摩成字符串。

剥离到相关细节,我的代码是:

SqlCommand comm = new SqlCommand;
SqlConnection conn = new SqlConnection(connectionString);
conn.Open();
SqlTransaction insertTransaction = conn.BeginTransaction();
comm.Connection = conn;
comm.Transaction = insertTransaction;

while(buffer.ReadNext())  // fill a buffer that I use to make my query
{
    // form my insert statement and assign it
    // It looks like: INSERT INTO myTable VALUES (5,'2016-02-16',NULL,3)
    // A good fraction of the data is numeric with decimal points. A good
    // fraction is dates.  The parsing and string building,
// extravagantly inefficient as it is, is not the culprit.
    // The  INSERT INTO does not specify the column names

    comm.CommandText = myStatement;
    if (comm.ExecuteNonQuery() != 1) {throw...}
}

insertTransaction.commit;

我尝试过指定不同的隔离级别;我无法指定.Snapshot(不想配置目标数据库以允许它)。没什么大不了的。

如果我注释掉 comm.ExecuteNoQuery 让它通过形成 INSERT 语句旋转,它会按照我认为的速度进行,如果它真的在做某事的话。如果我取消注释,它需要的时间大约是我认为应该花费的 8 倍。 “‘八倍长’从何而来?”你问。好吧,我用 Pervasive Data Integrator(ye olde Data Junction)做了一个类似的操作(端到端的相同操作)。从 Pervasive 崩溃时的诊断来看,我相信该程序正在使用连续的 INSERT INTO 进行插入 陈述,就像我一样。它比我的程序快大约 8 倍,如果我不执行它们,它只比形成我的 INSERT INTO 语句要慢一点。

Pervasive Integrator 正在通过 ODBC 添加,并且 ODBC 驱动程序设置为使用 Sql Native 客户端。而且 Data Integrator 不是 .Net 软件。我实际上并没有尝试通过 ODBC,我想我现在会尝试它,但我的目标是摆脱 ODBC,因此它只是一个数据点,而不是解决方案,即使它更快。

我尝试填充数据表以进行批量插入,但填充数据集也花费了太长时间。我认为使用批量插入的一些替代方法是使其工作的最快方法,但即使我最终停止使用我的INSERT INTO 方法,我很好奇为什么它需要这么长时间(比我认为的要长)

插入语句相当长,我的表中有大约 350 列。

【问题讨论】:

    标签: ado.net isolation-level sqlclient


    【解决方案1】:

    您是否尝试过将插入值组合成简单的东西:

    INSERT INTO myTables VALUES (5,'2016-02-16',NULL,3), (6,'2015-02-16',NULL,6), (7,'2012-02-16',NULL,6)...
    

    这是一个简单的实现,它并不优雅(而且它未经测试,我在记事本中写了这个,所以很可能有错别字),但是......

    SqlCommand comm = new SqlCommand;
    SqlConnection conn = new SqlConnection(connectionString);
    conn.Open();
    SqlTransaction insertTransaction = conn.BeginTransaction();
    comm.Connection = conn;
    comm.Transaction = insertTransaction;
    
    String baseQuery = "INSERT INTO myTable VALUES ";
    
    List<String> values = new List<String>();
    
    Int32 i = 0;
    
    while(buffer.ReadNext())  // fill a buffer that I use to make my query
    {
        // Build your VALUES section here
        values.Add("(5,'2016-02-16',NULL,3)");
    
        if (i % 100 == 0)  // Chunk these every 100
        {
            myStatement = baseQuery + String.Join(", ", values.ToArray());
    
            comm.CommandText = myStatement;
    
            if (comm.ExecuteNonQuery() != 1) {throw...}
    
            insertTransaction.commit;
    
            values = new List<String>();  // Clear out our values and start a new
        }
    
        i++;
    }
    
    if (values.Count > 0)  // If any are left, INSERT them
    {
        myStatement = baseQuery + String.Join(", ", values.ToArray());
    
        comm.CommandText = myStatement;
    
       if (comm.ExecuteNonQuery() != 1) {throw...}
    
       insertTransaction.commit;
    }
    

    【讨论】:

    • 我曾经有个老板说“不要为字节感到难过”,但当我一次只放入一个 300 列的表时,我已经为我的字节感到难过了。 ..所以,不,我没有尝试过,好主意,我会尽快报告。谢谢
    • 结果确实令人惊讶。当你把更多的东西分块时,它会变得更糟。我正在使用共享服务器上的挂钟时间进行测量,这完全不科学,但有些样本要添加 1000 条记录:每次 11 秒 1 条; 2 或 10 次,26 秒(没有一致的差异); 50 一次 130 秒。不过,对建议+1,听起来像是赢家。我的 INSERT INTO 语句每个大约 1,800 个字符长,我想知道处理长文本是否有问题,或者是否有任何设置可以针对这种用法进行调整。
    • 为紧缩尝试这个(假设你可以删除你的结果)。生成 1000 个传统 INSERTS 的结果。生成 1000 个批处理 INSERTS 的结果。在 SQL 服务器上运行每个并计时。让我知道结果。谢谢!
    • 硬件规格是什么?您可能会遇到网络带宽和内存分配的硬性限制。
    • 您可能在 SQL 中遇到了缓冲区限制。一个 326 字段宽的行对于 SQL 来说是相当大的。我确信这是可调的,但是在你不太了解的虚拟机上盲目地打开开关并不是我理想的调优方式。您是否尝试过隔离查询(代码外)并按照我上面的建议直接在服务器上执行?我很好奇 .net SQL 驱动程序是否是罪魁祸首。
    【解决方案2】:
    1. 数据是否一次排成一行?换句话说,您是否让用户将数据输入应用程序并按下按钮来启动插入?
      或者
    2. 是否所有数据都可以随时插入?

    如果 (1.) 只需按照您一直在做的方式执行 INSERT INTO。

    我反对在应用程序/客户端一次累积一行中的数据。这会导致很多问题。

    如果 (2.) 进行批量导入。

    Bulk Import 和 Bulk Export 操作有 3 种类型
    http://msdn.microsoft.com/en-us/library/ms187042.aspx

    这些文件是文本格式吗?
    它们是柱状的吗?
    它们是否可以从文件系统(即 C:\pathTo\file.ext)访问?
    如果是,生成一个 bcp 格式的文件(从你的程序中打印出来,注意 包括 C:\pathTo\file.ext)。
    然后 TRUNCATE 暂存表(或上面生成的格式文件中的 TRUNCATE INTO)并通过 system() 调用调用 bcp 实用程序,将格式文件和数据库连接作为参数。
    速度超级快。
    直接 bcp 上传将击败您可以通过 ado.net 上传的任何内容。

    【讨论】:

    • 我将编辑我的帖子,说明这是一个非交互式应用程序。它从不变的专有数据库文件中读取数据并将这些文件(表示)写入 SQL Server。
    猜你喜欢
    • 1970-01-01
    • 2021-10-30
    • 1970-01-01
    • 2014-01-05
    • 2017-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-18
    相关资源
    最近更新 更多