为什么建议将BLOB存储在单独的SQL Server表中?


28

这个备受好评的SO答案建议将图像放在单独的表中,即使与另一个表只有1:1的关系:

如果您决定将图片放入SQL Server表中,我强烈建议使用一个单独的表来存储这些图片-不要将员工照片存储在employee表中-请将它们保存在单独的表中。这样,假设您并不一定总是选择雇员的照片作为查询的一部分,那么Employee表就可以保持精简,卑鄙和高效。

为什么?我的印象是,SQL Server只在表中存储一个指向某些专用BLOB数据结构的指针,所以为什么要手动创建另一个间接层呢?它真的可以显着提高性能吗?如果是,为什么?

Answers:


15

尽管我不同意BLOB应该只在另一个表中,但它们根本不应该在数据库中。将指针存储到文件在磁盘上的位置,然后从数据库中获取指针。

他们(对我而言)引起的主要问题是索引。将XML与查询计划一起使用,因为每个人都有自己的名字,所以让我们创建一个表:

SELECT TOP 1000
ID = IDENTITY(INT,1,1),
deq.query_plan
INTO dbo.index_test
FROM sys.dm_exec_cached_plans AS dec
CROSS APPLY sys.dm_exec_query_plan(dec.plan_handle) AS deq

ALTER TABLE dbo.index_test ADD CONSTRAINT pk_id PRIMARY KEY CLUSTERED (ID)

它只有1000行,但是要检查大小 ...

sp_BlitzIndex @DatabaseName = 'StackOverflow', @SchemaName = 'dbo', @TableName = 'index_test'

仅1000行就超过40 MB。假设每1000行添加40 MB,那么很快就会变得很丑。当您达到一百万行时会发生什么?那里只有1 TB的数据。

坚果

现在,任何需要使用聚簇索引的查询都需要将所有BLOB数据读入内存中,以澄清:引用BLOB数据列时。

与存储BLOB相比,您能想到使用SQL Server内存的更好方法吗?因为我肯定可以。

将其扩展为非聚集索引:

CREATE INDEX ix_noblob ON dbo.index_test (ID)

CREATE INDEX ix_returnoftheblob ON dbo.index_test (ID) INCLUDE (query_plan)

您可以设计非聚集索引以在很大程度上避免使用BLOB列,以便常规查询可以避免聚集索引,但是一旦需要该BLOB列,就需要聚集索引。

如果将它作为一INCLUDED列添加到非聚集索引中,以避免出现关键查找情况,则最终会出现巨大的非聚集索引:在此处输入图片说明

它们引起的更多问题:

  • 如果有人运行SELECT *查询,他们将获得所有BLOB数据。
  • 它们占用了备份和还原空间,从而降低了速度
  • 他们放慢脚步DBCC CHECKDB,因为我知道您正在检查腐败,对吗?
  • 而且,如果您进行任何索引维护,它们也会降低速度。

希望这可以帮助!


7
因为用户通常键入SELECT *。
布伦特·奥扎尔

我认为您提到的缺点是他建议将图片放在单独的表格中的部分原因。如果我正在为用户运行各种报告,则不需要他们的图片文件。如果我正在加载单个用户的个人资料页面,那么那是我加入Blob表的时间,对吗?我在这里错过了什么吗(即,即使在我描述的这种情况下,您的缺点实际上仍然适用吗?)
BVernon

11

这些图像有多大,您期望有几张?尽管我大体上同意@sp_BlitzErik,但我认为在某些情况下可以执行此操作,因此有助于更清晰地了解此处实际要求的内容。

可以考虑减轻Erik指出的大多数负面影响的一些选择是:

这两个选项均被设计为介于完全在SQL Server中存储BLOB或完全在外部存储BLOB之间(除了保留路径的字符串colun之外)。它们允许BLOB成为数据模型的一部分,并在不浪费缓冲池(即内存)空间的情况下参与事务。BLOB数据仍包含在备份中,这确实使它们占用了更多空间,并且花费了更长的时间进行备份备份。恢复。但是,我很难将其视为真正的负面因素,因为如果它是应用程序的一部分,则需要以某种方式对其进行备份,并且仅将包含路径的字符串列完全断开并允许BLOB文件获取在数据库中没有指示的情况下被删除(即无效的指针/丢失的文件)。它还允许在数据库内“删除”文件,但文件仍存在于文件系统中,这最终需要清理(即头痛)。但是,如果文件很大,则最好将路径列之外的所有内容都留在SQL Server之外。

这有助于解决“内部还是外部”问题,但不会涉及单表还是多表问题。我可以说,除了这个特定问题之外,当然还有一些有效的情况,可以根据使用模式将表分成几列。通常,当一列有50列或更多列时,经常会访问一些列,而某些列则不会经常访问。一些列经常被写入,而另一些则经常被读取。将频繁访问与不频繁访问的列分离到具有1:1关系的多个表中通常是有好处的,因为为什么要浪费缓冲池中的空间来存储您可能不使用的数据(类似于为什么将大图像定期存储VARBINARY(MAX)列有问题)?您还可以通过减小行大小,从而在数据页上容纳更多的行,从而提高频繁访问列的性能,从而提高读取效率(物理和逻辑读取)。当然,您还需要通过重复PK来引入一些效率低下的问题,现在有时您需要将两个表连接起来,这也会使某些查询(即使只是轻微的)变得复杂。

因此,您可以采用几种方法,哪种方法最好取决于您的环境和您要实现的目标。


我的印象是,SQL Server仅在表中存储指向某些专用BLOB数据结构的指针

没那么简单。您可以在此处找到一些很好的信息,例如Varchar,Varbinary,Etc等(MAX)类型的LOB指针的大小是多少?,但基础是:

  • TEXTNTEXTIMAGE数据类型(默认情况下):16字节指针
  • VARCHAR(MAX)NVARCHAR(MAX)VARBINARY(MAX)(默认):
    • 如果数据适合该行,则将其放置在该行中
    • 如果数据小于约。40,000字节(链接的博客文章显示的上限为40,000,但我的测试显示的值略高)并且如果该结构的行上有空间,那么将有1到5个直接链接到LOB页面,从第一个链接的24个字节到前8000个字节,每增加8000个字节,每个附加链接增加12个字节,最大不超过72个字节。
    • 如果数据超过大约。40,000字节,或者没有足够的空间来存储适当数量的直接链接(例如,行上仅剩40个字节,而20,000字节的值则需要3个链接,这是第一个为24字节,外加12个为48个字节的两个附加链接总所需的行内空间),那么将只有24字节的指针指向文本树页面,其中包含到LOB页面的链接)。

7

如果出于某种原因必须将数据存储在SQL Server中,我可以想到将数据存储在单独的表中的一些好处。有些比其他更有说服力。

  1. 将数据放在单独的表中意味着您可以将其存储在单独的数据库中。这对于计划维护可能具有优势。例如,您DBCC CHECKDB只能在包含BLOB数据的数据库上运行。

  2. 如果您不总是将超过8000个字节放入BLOB中,则有可能将其存储在行中某些行。您可能不希望这样做,因为它会减慢使用聚簇索引访问数据的查询,即使查询不需要该列。将数据放在单独的表中可以消除这种风险。

  3. 当存储在行外时,SQL Server使用最多24个字节的指针来指向新页面。这会占用空间并限制了可以添加到单个表中的BLOB列的总数。有关更多详细信息,请参见srutzky的答案。

  4. 无法在包含BLOB列的表上定义集群的列存储索引。此限制已被删除,将在SQL Server 2017中删除。

  5. 如果最终决定将数据移出SQL Server,则如果数据已经在单独的表中,则进行更改可能会更容易。


1
这里有一些优点(+1)。但是要弄清楚#3(re:行外数据的24字节指针),这并不总是正确的。我在答案的底部(简短地)解释了数据类型,值的大小和行上的可用空间量如何确定指针的大小。
所罗门·鲁兹基
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.