【问题标题】:How can I insert a 100+k rows of XML into SQL Server 2012 in a fast way如何快速将 100+k 行 XML 插入 SQL Server 2012
【发布时间】:2013-06-01 01:44:14
【问题描述】:

我有以下场景/要求,我不确定以最快的方式执行的最佳方法是什么,寻找一些使用功能的指导和示例(如果有)

我将从网络服务接收到 10k 到 100k 之间的任意实体(XML 格式),我想更新这些实体(有些行可能存在,其他行可能不存在)。

以下是一些要求:

  1. XML 的源是我从 C# 代码调用的 Web 服务。其实是两种不同的方法。对于其中一种方法,返回模式将是平坦的,我可以直接映射到我的一张表。另一方面,它将返回我可能需要在 C# 中使用的 XML 表示,以便能够将其映射到我的表的平面实体。在这种情况下,是否最好进行所需的修改,然后将文件写入 XML 以用作源?

  2. 返回的 XML 最多可以包含 150k 个 XML 实体,这些实体可能存在也可能不存在于我的表中,因此我希望更新它们。这些文件在写入磁盘时最大可达 20 兆字节。我问他们是否可以用 JSON 代替 XML,但显然这不是一个选择。

  3. SQL 数据库与我的 IIS 服务器位于不同的服务器上,因此我宁愿避免让 SQL 服务器从文件中检索 XML,而是将其作为字符串或表值参数从 C# 传递。

  4. 这些表相当简单,除了 PK 之外没有其他索引。

我从来都不喜欢 XML,虽然使用 LINQ to XML 变得更容易了,我最初用它来解析每条记录并发送单独的插入,但性能很差,所以根据我的一些研究一直在做,我想我可以使用:

  1. 通过 MERGE 语句从 SQL Server 更新插入。
  2. 将整个 XML 作为参数传递,并使用 OPENXML 作为 MERGE 语句中的源。
  3. 或者,以某种方式在 C# 中生成一个表值参数并将其传递给 SQL 以在 MERGE 上使用。
  4. 我在this similar question(它无权访问 upsert/merge)上读到,与其尝试直接从 XML 中进行 upsert,不如将所有内容插入临时表并针对临时表?

这会工作并且相当快吗?

如果有人遇到过类似情况,您能否分享一下您对哪种功能组合最好的想法/想法?

谢谢。

【问题讨论】:

    标签: sql-server xml sql-server-2012 bulkinsert


    【解决方案1】:

    你在正确的轨道上。我有一个类似的设置,使用 XML 在在线门户和客户端-服务器应用程序之间传输数据。其余的设置与您所拥有的非常相似。

    如果您正在比较任何不是 PK 字段的字段,无论您如何索引临时表,您的表未编入索引这一事实有点令人担忧。重要的是有一个索引包含合并匹配子句中使用的所有字段,或者每个索引都有一个索引 - 我发现前者产生更好的性能。除此之外,还可以使用 XML 参数、OpenXML 和临时表。

    以下代码尚未经过测试,因此可能需要进行一些调试,但它会让您走上正轨。几点注意事项:如果 OpenXML WITH 子句中的所有字段都是属性,那么您可以删除最后一个参数(即“, 2”)和字段源说明符(即详细信息表的“@id”)。尽管您描述中的数据是扁平的,在这种情况下您只需要一张表,但我确实经常需要导入链接记录。为了完整起见,我在下面的代码中包含了一个简单的主从关系示例。

    CREATE PROCEDURE usp_ImportFromXML (@data XML) AS
    BEGIN
    /*
        <root>
            <data>
                <match_field_1>1</match_field_1>
                <match_field_2>val2</match_field_2>
                <data_1>val3</data_1>
                <data_2>val4</data_2>
                <detail_records>
                    <detail_data id="detailID1">
                        <detail_1>blah1<detail_1>
                        <detail_2>blah2<detail_2>
                    </detail_data>
                    <detail_data id="detailID2">
                        <detail_1>blah3<detail_1>
                        <detail_2>blah4<detail_2>
                    </detail_data>
                </detail_records>
            </data>
            <data>
                ...
        </root>
    */
        DECLARE @iDoc INT
        EXEC sp_xml_preparedocument @iDoc OUTPUT, @data
        SELECT * INTO #temp
        FROM OpenXML(@iDoc, '/root/data', 2) WITH (
            match_field_1 INT,
            match_field_2 VARCHAR(50),
            data_1 VARCHAR(50),
            data_2 VARCHAR(50)
        )
    
        SELECT * INTO #detail
        FROM OpenXML(@iDoc, '/root/data/detail_data', 2) WITH (
            match_field_1 INT '../../match_field_1',
            match_field_2 VARCHAR(50) '../../match_field_2',
            detail_id VARCHAR(50) '@id',
            detail_1 VARCHAR(50),
            detail_2 VARCHAR(50)
        )
    
        EXEC sp_xml_removedocument @iDoc
    
        CREATE INDEX #IX_temp ON #temp(match_field_1, match_field_2)
        CREATE INDEX #IX_detail ON #detail(match_field_1, match_field_2, detail_id)
    
        MERGE data_table a
            USING #temp ta
            ON ta.match_field_1 = a.match_field_1 AND ta.match_field_2 = a.match_field_2
        WHEN MATCHED THEN
            UPDATE SET data_1 = ta.data_1, data_2 = ta.data_2
        WHEN NOT MATCHED THEN
            INSERT (match_field_1, match_field_2, data_1, data_2) VALUES (ta.match_field_1, ta.match_field_2, ta.data_1, ta.data_2)
    
        MERGE detail_table a
            USING (SELECT d.*, p._key FROM #detail d, data_table p WHERE d.match_field_1 = p.match_field_1 AND d.match_field_2 = p.match_field_2) ta
            ON a.id = ta.id AND a.parent_key = ta._key
        WHEN MATCHED THEN
            UPDATE SET detail_1 = ta.detail_1, detail2 = ta.detail_2
        WHEN NOT MATCHED THEN
            INSERT (parent_key, id, detail_1, detail_2) VALUES (ta._key, ta.id, ta.detail_1, ta.detail_2)    
    
        DROP TABLE #temp
        DROP TABLE #detail
    END
    

    【讨论】:

    • 感谢彼得的回复。我尝试了这种方法,但是当我将整个 XML 文件作为 Xml 参数传递给我的 Sproc(它进行合并)时,插入 50k 条记录需要 7 分钟,这是不可接受的。对于 100k 行,我已经将它降低到 1.2 秒,这非常好,一旦我完善了这个东西,我会在稍后发布更详细的答案。谢谢
    • 您是使用索引暂存表进行导入,还是尝试直接从 OpenXML 合并?我问这个问题是因为我的性能比这要好得多,尽管公认不如 SqlBulkCopy 好。我期待看到您的解决方案。
    【解决方案2】:

    使用 (3)。在 C# 中处理准备好翻转的数据。 C# 是为这种算法工作而设计的。它既是正确的编程语言,也是更快的编程语言。 T-SQL 不是正确的工具。您不想将 XML 与 T-SQL 一起用于非常高性能的东西,因为它会疯狂地消耗 CPU。而是使用快速 TDS 协议发送 TVP 或批量数据。

    然后,使用 TVP 或批量插入 (SqlBulkCopy) 将数据发送到服务器到临时表。后一种技术非常适合很多行(>10k?)。批量插入使用特殊的 TDS 功能。它不使用 SQL 批处理来传输数据。它不会比这更快。

    然后按照您的描述使用MERGE 语句。使用大批量,可能是一批中的所有行。

    【讨论】:

    • 谢谢@usr。我还在研究这个东西,但是对于 100k 来说它已经降到了大约 1.5 秒,这对于现在来说已经足够了。它使用 SqlBulkCopy 到临时表并从那里执行 MERGE。
    • 完全同意这一点。在我的博客上有一个关于这件事的例子,我还没有找到一种更快的方法来处理它。 jarloo.com/c-bulk-upsert-to-sql-server-tutorial
    【解决方案3】:

    我发现的最佳方法是从您的 C# 代码批量插入临时表,然后在数据进入 SQL Server 后发出合并。我的博客上有一个例子SQL Server Bulk Upsert

    我在生产中每天使用它来插入数百万行,但还没有找到更快的方法来做到这一点。试一试,我想您会对解决方案的性能印象深刻。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-21
      • 2013-06-02
      • 2019-07-24
      • 2017-06-25
      • 2020-01-01
      相关资源
      最近更新 更多