【问题标题】:What's the most efficient way to update thousands of records更新数千条记录的最有效方法是什么
【发布时间】:2015-04-04 14:40:14
【问题描述】:

我们有一个从文本文件中解析数据的 C# 应用程序。然后,我们必须根据文本文件中的信息更新 sql 数据库中的记录。将数据从应用程序传递到 SQL 服务器的最有效方法是什么?

我们目前使用分隔字符串,然后在存储过程中循环遍历该字符串以更新记录。我也在使用 TVP(表值参数)进行测试。还有其他选择吗?

我们的文件包含数千条记录,我们想要一个花费最少时间的解决方案。

【问题讨论】:

  • 另一个选项是将文件批量插入到 SQL Server 中的临时表中(使用 BCP.EXESSISBULK INSERT 关键字),然后使用将其从临时表转换为最终表一个存储过程。与 C# 相比,SQL 在排序、查找、分组、汇总方面效率很高,但在文本操作方面不太好。你的文本文件是什么格式的,需要什么样的处理?
  • 数以千计的记录对于 SQL Server 来说是小菜一碟,您可以随意执行此操作,重复 10 次,仍然会很快
  • TVP 将比遍历字符串更有效。在我的测试中,它通常胜过所有其他字符串拆分机制,甚至是 CLR。但是,如果您在字符串列上有索引,另一种方法是将变量作为字符串和逗号分隔传递,例如'Smith','Jones','Frankenstein' 然后在动态 SQL 中组装它(假设这里有一堆关于 SQL 注入的免责声明,稍后再阅读)。也就是说,根据字符串识别要更新的行(尤其是当您谈论像 JSON 这样的多值字符串时)听起来像是一个糟糕的 ida。
  • 大多数人犯的错误是尝试仅发送以逗号分隔的字符串,例如Smith,Jones,Frankenstein 然后尝试将其提供给 IN(@parameter) 或构造动态 SQL - 将不起作用,因为参数是 IN() 的单个元组,并且是动态 SQL 的一组列名。
  • TVP 可能对您的 C# 代码也更有效,毕竟您可能正在以某种低效的方式构建逗号分隔的字符串,但最终基于结构更好的东西,例如 DataTable,对吧?

标签: c# sql sql-server


【解决方案1】:

请不要使用DataTable,因为这只会浪费 CPU 和内存而没有任何好处(除了可能熟悉之外)。我在对以下问题的回答中详细介绍了一种非常快速灵活的方法,与此非常相似:

How can I insert 10 million records in the shortest time possible?

该答案中显示的示例仅适用于 INSERT,但可以轻松调整以包含 UPDATE。此外,它一次性上传所有行,但也可以轻松调整为为 X 条记录设置计数器,并在传入许多记录后退出 IEnumerable 方法,以及然后在没有更多记录后关闭文件。这将需要将文件指针(即流)存储在静态变量中,以继续传递给 IEnumerable 方法,以便下次可以在最近的位置前进并拾取它。我在下面的答案中显示了这个方法的一个工作示例,虽然它使用SqlDataReader 作为输入,但技术是相同的,只需要很少的修改:

How to split one big table that has 100 million data to multiple tables?

从某些角度来看,50k 条记录甚至还没有接近“巨大”。我一直在使用我在此处显示的 400 万行文件的方法上传/合并/同步数据,并且该方法命中了几张具有 1000 万(或更多)行的表。


做的事情:

  • 使用DataTable:正如我所说,如果你只是为了使用TVP而填充它,那是对CPU、内存和时间的浪费。
  • 同时进行 1 次更新(正如对该问题的评论中所建议的那样):这太疯狂了。关系数据库引擎经过大量调整,可以最有效地处理集合,而不是单例操作。 50k 次插入不可能比 500 次每次插入 100 行更有效。即使只是行锁(它是 100k 锁 + 解锁操作),单独执行它也只能保证表上的更多争用。它可能比升级为表锁的单个 50k 行事务更快(正如 Aaron 所提到的),但这就是为什么你要小批量执行它,只要小并不意味着 1 行;)。
  • 任意设置批量大小。保持在 5000 行以下有助于减少锁升级的机会,但不要只选择 200 行。尝试几种批量大小(100、200、500、700、1000)并尝试每一种。您将看到什么最适合您的系统。只需确保批量大小可通过 app.config 文件或其他方式(数据库中的表、注册表设置等)进行配置,以便无需重新部署代码即可对其进行更改.
  • SSIS(功能强大,但体积庞大,调试起来并不有趣)

有效的东西,但不如正确完成的 TVP 灵活(即传入一个返回 IEnumerable<SqlDataRecord> 的方法)。这些都可以,但是当您可以内联完成所有操作时,为什么要将记录转储到临时表中只是为了必须将它们解析到目标?

  • BCP/OPENROWSET(BULK...)/BULK INSERT
  • .NET 的SqlBulkCopy

【讨论】:

  • 我做复杂的 ETL 过程,我喜欢 SSIS,但不得不同意你关于调试不好玩的观点。
  • @HLGEM 别误会,我绝对尊重 SSIS 的能力,尤其是能够执行并行任务和编写自定义 C# / VB.Net 任务,但除非你经常使用 SSIS并且因此熟悉它的细微差别,这是一个陡峭的学习曲线(因此非常耗时),如果一个项目是唯一使用它的东西,通常不值得。至少这是我的经验。维护/管理这些项目并不容易。
  • 我也同意陡峭的学习曲线。一个很棒的工具,但非常复杂。当我们采访时,我们会问他们不喜欢 SSIS 的哪些方面,这个答案会告诉我们他们是否真的使用过它!任何说“无”的人都被认为是该产品的新手。
  • @HLGEM 这是一个很好的聪明的技术。我喜欢。在更一般的意义上,我通过询问候选人他们对光标和/或触发器的看法来做类似的事情。回复通常不仅表明他们对这个问题有经验并且确实考虑过它而不是重复他们读过的内容,而且还表明他们的沟通能力。
  • 我可以和你聊几个小时关于触发器和游标。好的,坏的,用例,避免使用它们的地方,现实生活中出错的例子(你能说光标在触发器中?)和现实生活中正确的事情以及如何调试触发器等的例子。如果您在适当的时候避免使用触发器等,可能会发生更糟糕的事情。我永远不会得到退出面试。
【解决方案2】:

在我看来,最好的方法是创建一个临时表,然后使用 SqlBulkCopy 插入该临时表 (https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlbulkcopy%28v=vs.110%29.aspx),然后根据临时表简单地更新该表。

根据我的测试(使用 Dapper 和 LINQ),批量更新或批量更新比创建临时表并向服务器发送命令以根据临时表更新数据所花费的时间太长。这个过程更快,因为 SqlBulkCopy 以快速的方式原生填充数据,其余的在 SQL 服务器端完成,计算步骤较少,此时的数据驻留在服务器端。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-10-25
    • 1970-01-01
    • 2010-11-11
    • 2012-10-19
    • 2021-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多