table 中的字段数即使未被引用也会影响性能吗?

Does the number of fields in a table affect performance even if not referenced?

我正在读取 CSV 文件并将其解析到 SQL Server 2008 数据库中。此过程对所有文件使用通用 CSV 解析器。

CSV 解析器将解析的字段放入通用字段导入 table(F001 VARCHAR(MAX) NULL,F002 VARCHAR(MAX) NULL,Fnnn ...),然后另一个进程将其移入实际tables 使用 SQL 代码知道哪个解析字段 (Fnnn) 转到目标 table 中的哪个字段。所以一旦在 table 中,只有被复制的字段被引用。有些文件可能会变得非常大(一百万行)。

问题是:table 中字段的数量是否会显着影响性能或内存使用?即使大部分字段都没有被引用。对字段导入 table 执行的唯一操作是插入,然后 SELECT 将数据移动到另一个 table,字段数据上没有任何 JOIN 或 WHERE。

目前,我有三个字段导入 table,一个有 20 个字段,一个有 50 个字段,一个有 100 个字段(这是我迄今为止遇到的最大字段数)。目前存在使用尽可能小的文件的逻辑。

我想让这个过程更通用,并且有一个 table 的 1000 个字段(我知道 1024 列的限制)。是的,一些计划要处理的文件(来自第 3 方)将在 900-1000 字段范围内。

对于大多数文件,将少于 50 个字段。

此时,处理现有的三个字段导入 tables(加上计划的 tables 更多字段(200,500,1000?))正在成为代码中的后勤噩梦,并且处理单个 table 会解决很多问题,前提是我不会放弃太多性能。

是的。大记录在磁盘和内存中占用更多 space ,这意味着加载它们比小记录慢,并且更少可以容纳在内存中。这两种影响都会损害性能。

正如评论中正确指出的那样,即使您的 table 有 1000 列,但其中大部分是 NULL,也不会对性能产生太大影响,因为 NULLs 会不要浪费很多 space.

您提到您可能拥有包含 900-1000 个非 NULL 列的真实数据。如果您打算导入此类文件,您可能会遇到 SQL 服务器的另一个限制。是的,table 中的最大列数是 1024,但有 8060 bytes per row 的限制。如果您的列是 varchar(max),那么每个这样的列将占用实际行中 8060 字节中的 24 个字节,其余数据将被推到行外:

SQL Server supports row-overflow storage which enables variable length columns to be pushed off-row. Only a 24-byte root is stored in the main record for variable length columns pushed out of row; because of this, the effective row limit is higher than in previous releases of SQL Server. For more information, see the "Row-Overflow Data Exceeding 8 KB" topic in SQL Server Books Online.

所以,在实践中你可以有一个 table 只有 8060 / 24 = 335 nvarchar(max) 非 NULL 列。 (严格来说,少了一点,还有其他的header)。

所谓的wide tables最多可以有30,000列,但是宽table行的最大大小是8,019字节。所以,在这种情况下,他们不会真正帮助你。

首先,回答问题:

Does the number of fields in a table affect performance even if not referenced?

  • 如果字段是固定长度的(*INT、*MONEY、DATE/TIME/DATETIME/etc、UNIQUEIDENTIFIER 等)并且字段未标记为 SPARSE 或压缩没有' t 被启用(两者都在 SQL Server 2008 中启动),然后字段的完整大小被占用(即使 NULL)并且这确实会影响性能,即使字段不在 SELECT列表.

  • 如果字段是可变长度和NULL(或空),那么它们只是在页眉中占用少量space。

  • 一般来说space,这个table是堆(没有聚簇索引)还是聚簇?您如何为每个新导入清除 table ?如果它是一个堆并且您只是在执行 DELETE,那么它可能不会删除所有未使用的页面。通过在执行 sp_spaceused 时看到 space 即使是 0 行也被占用,您就会知道是否存在问题。下面的建议2和3自然不会出现这样的问题。

现在,一些想法:

  1. 您是否考虑过使用 SSIS 动态处理此问题?

  2. 既然你好像有一个单线程进程,为什么不在每次进程开始时创建一个全局临时文件table?或者,在 tempdb 中删除并重新创建一个真实的 table?无论哪种方式,如果您知道目的地,您甚至可以使用目的地字段名称和数据类型动态创建此导入 table。即使 CSV 导入器不知道目的地,在流程开始时您可以调用知道目的地的过程,可以创建 "temp" table,然后导入器可以如果 table 中的字段可以为 NULL 并且至少与文件中的列数一样多,则通常仍会导入到标准 table 名称中,没有指定字段并且不会出错。

  3. 传入的 CSV 数据是否嵌入了 returns、引号、and/or 分隔符?您是否在暂存 table 和目标 table 之间操作数据?可以使用适当的数据类型直接动态导入到目标 table,但没有传输中的操作。另一种选择是在 SQLCLR 中执行此操作。您可以编写一个存储过程来打开一个文件并在执行 INSERT INTO...EXEC 时吐出拆分字段。或者,如果您不想自己编写,请查看 SQL# SQLCLR 库,特别是 File_SplitIntoFields 存储过程。此 proc 仅在完整/付费版本中可用,我是 SQL# 的创建者,但它似乎非常适合这种情况。

  4. 鉴于:

    • 所有字段都导入为文本
    • 目标字段名称和类型已知
    • 目标 table 之间的字段数量不同

    有一个 XML 字段并将每一行导入为单级文档,每个字段为 <F001><F002> 等怎么样?通过这样做,您不必担心字段数量或有任何未使用的字段。事实上,由于进程已知目标字段名称,您甚至可以使用这些名称为每一行的 XML 文档中的元素命名。所以行可能看起来像:

    ID  LoadFileID  ImportLine
    1   1           <row><FirstName>Bob</FirstName><LastName>Villa</LastName></row>
    2   1           <row><Number>555-555-5555</Number><Type>Cell</Type></row>
    

    是的,数据本身将比当前的 VARCHAR(MAX) 字段占用更多 space,这既是因为 XML 是双字节的,也是因为开始的元素标签固有的庞大和。但是这样你就不会被任何物理结构所束缚。而且仅查看数据将更容易识别问题,因为您将查看真实的字段名称而不是 F001、F002 等。

  5. 就至少加快读取文件、拆分字段和插入的过程而言,您应该使用Table-Valued Parameters (TVPs) 将数据流式传输到导入 table。我在这里有一些答案,显示了该方法的各种实现,主要根据数据源(文件与内存中已有的集合等)不同:

    • How can I insert 10 million records in the shortest time possible?
    • Pass Dictionary<string,int> to Stored Procedure T-SQL
    • Storing a Dictionary<int,string> or KeyValuePair in a database