【问题标题】:Does the number of fields in a table affect performance even if not referenced?即使不引用,表中的字段数是否会影响性能?
【发布时间】:2015-03-03 16:51:36
【问题描述】:

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

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

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

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

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

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

此时,处理现有的三个字段导入表(加上更多字段的计划表(200,500,1000?))正在成为代码中的逻辑噩梦,处理单个表将解决很多问题,只要我不放弃太多性能。

【问题讨论】:

  • “有一个包含 1000 个字段的表” - 糟糕的设计。
  • 一次导入需要多长时间?您对性能提升的期望是什么?
  • 可能需要几秒钟到几分钟,具体取决于文件。将文本导入数据库不是问题,问题是与许多未使用字段相关的性能损失(取决于文件宽度)。我想在不影响性能的情况下摆脱代码的复杂性。
  • @Mitch - 设计取决于上下文。在将通用 CSV 解析为数据库的上下文中,具有 1000 个原始字段的表不一定是糟糕的设计。 SQL Server 无法处理包含这么多字段(其中包含数据)的行这一事实是一个限制。
  • IMO - 这是一个糟糕的设计,表明可能有更好的方法来解决“将通用 CSV 解析为数据库”的问题

标签: sql-server sql-server-2008


【解决方案1】:

首先,回答上述问题:

即使不引用,表中的字段数是否会影响性能?

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

  • 如果字段是可变长度且为 NULL(或为空),则它们只占用页眉中的少量空间。

  • 一般来说,这个表是堆(无聚集索引)还是聚集的?您如何为每个新的导入清理表格?如果它是一个堆并且你只是在做一个DELETE,那么它可能不会摆脱所有未使用的页面。在执行sp_spaceused 时,即使看到 0 行占用的空间,您也会知道是否存在问题。下面的建议2和3自然不会有这样的问题。

现在,一些想法:

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

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

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

  4. 鉴于:

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

    如果有一个单一的 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) 字段占用更多空间,这既是由于 XML 是双字节的,又是由于元素标记的固有庞大性。但是你并没有被锁定在任何物理结构中。并且仅查看数据将更容易识别问题,因为您将查看真实的字段名称而不是 F001、F002 等。

  5. 至少在加快读取文件、拆分字段和插入的过程方面,您应该使用表值参数 (TVP) 将数据流式传输到导入表中。我在这里有几个答案显示了该方法的各种实现,主要根据数据的来源(文件与内存中的集合等)而有所不同:

【讨论】:

  • 所有字段都是 VARCHAR(MAX)。除了一些表管理字段。有一个 INT IDENTITY 作为主键。许多文件加载进入同一个表(由加载文件ID分隔。数据在一周或两周后被删除(按文件加载)(取决于业务报告问题需要多长时间)。数据库定期重新组织.
  • 1) SSIS 不是一个选项。有问题的功能是 SSIS 的替代品,它可以处理 SSIS 可以处理的更多类型的事情。并且具有更大的灵活性。 2)设计背后的要点之一是数据是可审计的和生产可支持的。因此,所有内容都会进入永久表,保留几周,然后被删除(备份后)。
  • 3) 我必须处理多种不同的 CSV 格式,其中一些是相互排斥的。设计是有一个原始文件数据表,然后数据通过上下文从那里移动。当企业发现 8 天前处理的问题时,这有助于生产支持。我在一个非常保守、非常大的国际实体。我还没有获得使用 CLR 的许可。我将研究 SQL#,但外部软件的审批流程,尤其是数据库服务器级别的审批流程,使这无法启动。
  • @Evan PK 集群了吗?如果您想将源数据保留一两个星期,听起来直接导入表和使用临时表不是选项。 LoadFileID 上有索引吗?将数据从该表移动到“真实”表时,您引用的是LoadFileID,对吗?您是否曾经通过 PK 字段引用行?我认为没有太多理由这样做。在任何一种情况下,CLUSTERED UNIQUE INDEX 都应该在 (LoadFileID, PKfield) 上,并且 PK 应该是 NonClustered。定期重组固然很好,但它会重建吗?
  • 6) 对 CSV 文件使用 TVP 将加载时间减半! 1) 解析非 CLR SQL 中的 CSV 字段很慢。 4) 正在对插入物进行批处理。 5) 对不起,我的意思是 5000 个字段。
【解决方案2】:

正如在 cmets 中正确指出的那样,即使您的表有 1000 列,但大多数是 NULL,它应该不会对性能产生太大影响,因为 NULLs 不会浪费很多空间。

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

SQL Server 支持启用可变长度的行溢出存储 列被推离行。只有一个 24 字节的根存储在 可变长度列的主记录被推出行;因为 这样,有效行数限制高于以前版本的 SQL 服务器。有关详细信息,请参阅“行溢出数据超出 8 KB" SQL Server 联机丛书中的主题。

因此,实际上您可以拥有一个仅包含 8060 / 24 = 335 nvarchar(max) 非 NULL 列的表。 (严格来说,少一点,还有其他的header)。

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

【讨论】:

  • 谢谢。事实证明,在出现问题之前我可以拥有大约 330 个字段。我已经将最终表格设置为 100 个字段,并带有一个序列指示器,因此它可以处理任意长度的字段编号。 100 个字段的大小虽然小于一行,但使得在 SQL 代码中使用字段更加人性化。
  • @Evan 如果我在你的位置,我会认真地重新考虑设计。例如,与其拥有数百个具有相同类型的列,我将拥有一个具有 nvarchar(max) 类型的 one 列的表,并添加第二个列,该列将包含标识数据顺序的内容应该处理。用您的话来说,它将包含“F001”、“F002”、“Fnnn”。通常一个简单的数字就足够了。因此,您可以在源数据中包含任意数量的列,并且导入数据的处理很可能会变得更容易。
  • @Evan,当然,像往常一样,这一切都取决于数据的性质以及您以后打算如何使用它。
  • @Evan 和 Vladimir:我不明白为什么宽表不起作用。我会假设导入数据已经很可能在 8019 字节限制内。溢出的 24 个字节仅在数据实际上溢出时才发生。未保存的数据保留在数据页上,空字段(NULL 或空字符串)占用 0 个字节(每行)。另外,我刚刚测试了 1000 个VARCHAR(MAX) NOT NULL 字段,所有字段都默认为一个空格(非空),加上[RowID] INT IDENTITY PK, [LoadFileID] INT NOT NULL,插入几行没有问题。
  • 弗拉基米尔,单字段方法本质上是我刚刚提出的,虽然我建议使用 XML,但认为解析它的能力将超过数据大小的增加。
【解决方案3】:

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

【讨论】:

  • 不管通用字段导入表有多少个字段,数据大小都是一样的。因此,加载到 20 字段表中的 20 字段文件将消耗所有 20 个字段。同一个文件,加载到 1000 个字段的表中会消耗 20 个 VARCHAR(MAX) 字段,其他 980 个 VARCHAR(MAX) 字段为 NULL,但除了这些 NULL,数据量完全相同。
  • 是的,空字段不占用空间,但已满,但即使当前查询不需要,也会占用空间。
  • @Jasen:不完全正确。 NULL 值也需要一些空间。一个字节,IIRC。
  • 并且由于 NULL 占用的空间非常小,因此具有 NULL 数据的额外 980 个字段不应增加存储需求,因此从 I/O 角度来看不应影响性能。 (不回应你的评论@James - 我只是看到它)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多