在mysql数据库中存储大文件/二进制数据,什么情况下是可行的?

7

好的,我已经搜索了关于在MySQL数据库中存储二进制数据的问题并阅读了一些观点。一般来说,我认为这是一个不好的想法,并尽量避免使用它,而是更喜欢传统的文件传输并将文件的引用存储在数据库中。

然而,我正在开发一个需要将远程/云数据库同步的项目,不仅限于文件,还包括设置和其他用户内容。因此,出于这个原因以及其他原因,我觉得在数据库中进行二进制存储可能是一个适当的情况。

我已经编写了一个通用的数据库同步系统,使用反射和XML工作良好。我也(违背了我的本能)将文件存储集成到了这个系统中。同样,它也很好地工作- 我将文件切片成64Kb的BLOB并将它们存储在一个表格中,带有一个与file_id链接的参考(链接到一个单独的表格,其中包含meta数据,如文件名/大小/mime类型)。

这使我能够在连接可用时发送零散的位和片段,并且还允许我限制每个请求的大小,以保持事情的顺畅运行。

到目前为止,我没有发现任何问题,并成功地导入和传输了超过1GB的数据(大约10-15个文件/16000行),但我担心它的可扩展性 - 一旦有20GB以上的数据,它会减慢吗?或者只要我的查询结构良好,MySQL可以处理它吗?

我决定将数据存储在数据库中的另一个原因是,如果空间不足,我认为我可以简单地向MySQL添加另一个HDD/存储设备,以期望有效的扩展/复制等。

非常感谢任何关于这是否是一个好方法或坏方法的看法或评论,我是否错过了在生产环境中使用时可能看到的任何明显问题?

编辑:我忘记提到,文件大小范围从1KB到~1GB

[粗略] 结论: 首先,非常感谢那些做出认真回答的人。选择接受的答案对我来说很困难,因为每个答案都有一些不错的东西。

最终(尽管我希望不是这样),我已经决定纯MySQL存储服务器最多只是一个可以接受的解决方案(我仍然不能帮助想知道为什么他们还包含BLOB类型)。

作为替代方案,我在@Nick Coons文件系统方法和@tadman的建议之间犹豫不决,后者使用了轻量级键值数据库引擎,如leveldb。只要在这个项目中使用leveldb的实际问题不是问题,这可能是我将要采取的方法。

基于此,我接受了tadman的答案;他的回答也最适用且对我的情况最有用。

话虽如此,对于那些感兴趣的人:到目前为止,我仅使用MySQL已经取得了相当大的成功。我测试过一个存储超过15GB二进制数据的表格,在小心地查询后,从这个大表格中插入/检索数据没有任何明显的负面影响。然而,我相信这仍然非常低效,并且提到的任意一种替代方法都会更有效。


3
如果你要给我负评,请至少给一个解释,这样我才可以避免再犯同样的错误。 - Alfie
3个回答

3

简短回答:

我不确定有一个硬性的方法来回答这个问题。你提到的文件大小从1KB到1GB不等,如果二进制数据接近于1KB,甚至1GB,我不会将其存储在数据库中。如果只是一些偶然的几个字节的二进制数据,我可能会存储在数据库中,但任何大量的数据,尤其是不需要搜索的数据,都应该存储在文件系统中:

当你将数据存储在数据库中时,你实际上是将其存储在文件系统上,你只是添加了另一层(数据库)处理。这一层有成本,因此必须有收益来弥补差异。如果您要存储数据以便根据它进行搜索或将其与其他数据连接,则这是有意义的。但是,通常情况下文件数据(无论是否为二进制)并不是以这种方式使用的。

示例实现:

有比将文件数据输入到数据库中更好的分发文件数据的方法,例如分布式文件系统(请查看GlusterFS,MooseFS等,它们将通过仅添加额外的硬盘来扩展,而MySQL不会)。

通常,我会在文件系统中使用SHA1数据哈希作为文件名来存储文件数据。如果哈希值为98a75af529f07b1ef7be7400f51344b9f07b1ef7,则我会将其存储在以下目录结构中:

./98/a7/98a75af529f07b1ef7be7400f51344b9f07b1ef7

也就是说,顶层目录由前两个字符组成,第二层目录由后两个字符组成,最后是以总字符串的名称命名的文件。通过这种方式,我可以拥有数十亿个文件,而不必将它们全部放在单个目录中,以至于系统运行过慢。
然后我创建了一个包含以下列来保存元数据的DB表:
- file_id,自动递增字段 - created,带有当前时间戳默认值的字段 - prev_id,下面会详细介绍 - hash,在文件系统上的SHA1哈希值 - name,文件的文本名称(例如文件在磁盘上原本的名称)
当我需要一个分层目录结构时,我还会创建一个目录表,并将dir_id添加到上述列列表中。
如果我编辑由“./98/a7/98a75af529f07b1ef7be7400f51344b9f07b1ef7”表示的文件,我实际上没有更改磁盘上的该文件,而是创建了一个新文件(因为新文件内容将由新的SHA1哈希值表示),并在文件表中创建一个新条目,其中prev_id等于我编辑的文件的file_id。换句话说,现在我有版本控制。
如果我需要将其以分布式方式可用,我设置MySQL复制,然后使用GlusterFS将文件系统复制到多个服务器上。

非常感谢您,这是一个非常好的答案。我特别喜欢您建议的文件存储方法。像您提到的那样使用分布式文件系统也很不错,但不幸的是,我们目前并没有使用专用服务器(至少暂时没有)。我需要尽可能地使其与典型的共享Web托管设置兼容,这当然使得使用需要在服务器上安装任何东西变得困难。但再次感谢您优雅的文件系统方法 :) - Alfie

3
我不禁要想知道,如果您添加的层用于分块、存储、检索和重新组合数据,那么为什么还需要使用数据库?在一个定义良好的文件系统结构上,这些操作同样适用。MySQL希望所有数据都在一个 单一 卷上,因此这并不是随时添加另一个驱动器的情况,而且大量二进制数据的复制将非常缓慢,因为二进制日志最终会增加您需要存储的数据量。

最简单的方法通常是最好的方法。直接将其存储在文件系统中可能是最好的方法。如果您需要保留存储位置的索引,则可以使用像MySQL这样的数据库,但有许多方法可以完成同样的任务。技术越低级别越好。例如,不要排除SQLite,因为嵌入式数据库在轻读写负载下表现非常出色,并具有“只是一个文件”的备份和恢复优势。

话虽如此,您正在做的事情听起来非常类似于LevelDB,因此,在承诺采用您的方法之前,您需要查看它与该类型的键值文档存储的区别。


谢谢您的回答,您提出了一些有趣的想法。该项目已经使用了WAMP环境来完成各种其他任务,所以我觉得我可以利用已经存在的内容。此外,我也看过LevelDB,它看起来很不错,但我不认为在这种情况下它完全适合我的需求:“它没有关系数据模型,不支持SQL查询,也没有索引支持”。我不能帮助自己认为,这会给我带来更多问题。首先使用数据库的原因只是我已经为其他同步项设置了基础设施。 - Alfie
数据库非常擅长存储定义良好的关系数据,但在存储大量任意二进制数据方面则表现糟糕。你可以使用混合型的解决方案,即在LevelDB数据存储上构建一个RDBMS索引。LevelDB非常适合存储海量数据,并且可以使用标准工具(如rsync)进行复制。 - tadman
嗨@tadman,感谢您的建议。我再次查看了levelDB,并且发现我可以使用MySQL来处理关系型数据,并将levelDB的键/范围存储为引用值以便同时使用两者。这是您所考虑的吗?但是,我仍然有一个问题,那就是我基本上需要复制我所做的任何东西:我需要在WAMP环境中为桌面应用程序创建C#系统,并在LAMP环境(典型的Web主机)中创建PHP等效系统。请原谅我的无知,但据我所知,大多数Web主机默认情况下都没有提供leveldb。 - Alfie
那听起来很理想 - 如果您能指出最好的那些扩展,那将是锦上添花的事情。我当然不想重复造轮子:毕竟没有必要做别人已经做得更好的事情 :) - Alfie
那看起来是一个非常完整的库,所以我会很放心地使用它。 - tadman
显示剩余3条评论

2
我认为当我开始调查这个问题时,你会发现有很多争议。我倾向于将二进制数据存储在文件系统中并维护引用。然而,并不是说从来没有把二进制数据存储在数据库中的时候。
我认为仅仅为了保持同步并不能成为将二进制数据存储在数据库中的理由。当然,有方法可以使文件系统与数据库保持同步。
总之,这个话题存在很多争议,你必须选择适合自己的方案。如果你已经设置好了,请使用它。进行性能和负载测试以确保其正常工作。如果无法承受,请进行更改。

你说得对 - 我发现这个问题有很多争议..但是,这就是我为什么要问的原因 :) 自从发布以来,我一直在做更多的测试,现在有一个包含约15GB数据的表格,一些文件大小达到1.2GB,到目前为止一切都很好 :) 但是,尽管我很想不去管它,其他答案中提出的观点让我感到不安。此外,关于你第二段所说的,我选择这个只是因为我已经有了一个强大的序列化/同步引擎,可以很容易地添加对象:所以,我想这是懒惰和希望的混合 :) - Alfie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接