【发布时间】:2015-06-18 11:49:32
【问题描述】:
也许这个问题应该在https://dba.stackexchange.com/ 上提问,我不确定。请在 cmets 中告知或移到那里。
对于这个项目,我使用的是托管在 Amazon RDS 上的 MySQL 5.6.19。
总结
我将在InnoDB 表的BLOB 列中将照片存储在数据库中,我想知道执行此操作的最佳方法。我正在寻找可以比较不同变体的官方文档或一些方法。
在搜索这个主题时,有很多关于将二进制文件存储在数据库BLOB 还是存储在只有文件路径和名称的数据库的文件系统中更好的讨论和问题。这样的讨论超出了这个问题的范围。对于这个项目,我需要一致性和引用完整性,因此文件将存储在BLOB,问题在于具体如何执行。
数据库架构
这是架构的相关部分(到目前为止)。有一个表Contracts,其中包含有关每个合同和主要ID 键的一些一般信息。
每个合同可以拍摄几张(~10)张照片,所以我有一张桌子ContractPhotos:
CREATE TABLE `ContractPhotos` (
`ID` int(11) NOT NULL,
`ContractID` int(11) NOT NULL,
`PhotoDateTime` datetime NOT NULL,
PRIMARY KEY (`ID`),
KEY `IX_ContractID` (`ContractID`),
CONSTRAINT `FK_ContractPhotos_Contracts` FOREIGN KEY (`ContractID`) REFERENCES `Contracts` (`ID`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8
对于每张照片,我将存储原始全分辨率图像以及一些缩小版本,所以我有一张桌子ContractPhotoVersions:
CREATE TABLE `ContractPhotoVersions` (
`ID` int(11) NOT NULL,
`ContractPhotoID` int(11) NOT NULL,
`PhotoVersionTypeID` int(11) NOT NULL,
`PhotoWidth` int(11) NOT NULL,
`PhotoHeight` int(11) NOT NULL,
`FileSize` int(11) NOT NULL,
`FileMD5` char(32) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
PRIMARY KEY (`ID`),
KEY `IX_ContractPhotoID` (`ContractPhotoID`),
CONSTRAINT `FK_ContractPhotoVersions_ContractPhotos` FOREIGN KEY (`ContractPhotoID`) REFERENCES `ContractPhotos` (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
最后,有一个表格保存了所有图像的实际二进制数据。我知道 MySQL 允许在 LONGBLOB 列中存储多达 4GB,但在搜索过程中,我遇到了另一个 MySQL 限制:max_allowed_packet。在我的 MySQL 实例中,这个变量是 4MB。阅读文档后我对这个变量的理解是,有效地,单行不能超过 4MB。拥有超过 4MB 的照片是很正常的,所以为了能够 INSERT 和 SELECT 这样的文件,我打算将文件分成小块:
CREATE TABLE `PhotoChunks` (
`ID` int(11) NOT NULL,
`ContractPhotoVersionID` int(11) NOT NULL,
`ChunkNumber` int(11) NOT NULL,
`ChunkSize` int(11) NOT NULL,
`ChunkData` blob NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `IX_ContractPhotoVersionID_ChunkNumber` (`ContractPhotoVersionID`,`ChunkNumber`),
CONSTRAINT `FK_PhotoChunks_ContractPhotoVersions` FOREIGN KEY (`ContractPhotoVersionID`) REFERENCES `ContractPhotoVersions` (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
此外,我将能够一次将几块大照片上传到数据库中,并在连接断开时恢复上传。
数据量
估计数据量为 40,000 张全分辨率照片,每张约 5MB => 200GB。缩小的版本很可能是 800x600,每个约 120KB => + 额外的 5GB。图片不会是UPDATEd。它们最终会在几年后被删除。
问题
有很多方法可以将文件拆分为更小的块:您可以将其拆分为 4KB、8KB、64KB 等。使用 InnoDB 存储引擎首先最大限度地减少浪费的空间,其次是整体性能,最佳方式是什么?
我找到了这些文档:http://dev.mysql.com/doc/refman/5.6/en/innodb-file-space.html,但没有太多关于 BLOB 的详细信息。它说页面大小为16KB。
最大行长度,除了可变长度列(VARBINARY, VARCHAR、BLOB 和 TEXT),略小于数据库的一半 页。即最大行长度约为 8000 字节。
我真的希望官方文档比 大约 8000 字节更精确。以下段落最有趣:
如果一行小于半页长,则全部存储在本地 页面内。如果超过半页,可变长度列 被选为外部页外存储,直到行适合 半页。对于选择用于页外存储的列,InnoDB 存储 行中本地的前 768 个字节,其余的外部进入 溢出页面。每个这样的列都有自己的溢出页面列表。 768 字节的前缀伴随着一个 20 字节的值,用于存储 列的真实长度并指向溢出列表,其中 其余的值被存储。
考虑到上述情况,至少可以有以下策略:
- 选择这样的块大小,它将存储在页面本地而不涉及页外存储。
- 选择这样的块大小,以使整个 BLOB 存储在页外。
- 我不喜欢将 BLOB 部分存储在页面内,部分存储在页面外的想法。但是,嘿,也许我错了。
我也遇到了这个文档https://dev.mysql.com/doc/refman/5.6/en/innodb-row-format-dynamic.html,此时我意识到我想问这个问题。现在对我来说太难了,我希望有人对这个话题有过实践经验。
我不想因为无意中选择了糟糕的块大小和行格式而浪费了一半的磁盘空间。我担心的是,如果我选择在PhotoChunks 表的同一行中为每个块存储 8000 个字节加上 4 个整数的 16 个字节,它将超过页面大小的一半,我最终只为每行花费 16KB 8000 字节的数据。
有没有办法检查这种方式实际浪费了多少空间?在 Amazon RDS 环境中,恐怕无法查看 InnoDB 表所包含的实际文件。否则,我会简单地尝试不同的变体并查看最终文件大小。
到目前为止,我可以看到有两个参数:行格式和块大小。也许还有其他事情需要考虑。
编辑
为什么我不考虑更改 max_allowed_packet 变量。来自doc:
客户端和服务器都有自己的 max_allowed_packet 变量,所以如果你想处理大数据包,你必须增加这个 客户端和服务器中的变量。
我使用MySQL C API 来处理这个数据库,并且同一个 C++ 应用程序正在使用相同的libmysql.dll 与其他 200 个 MySQL 服务器(与该项目完全无关)进行通信。其中一些服务器仍然是 MySQL 3.23。所以我的应用程序必须与所有这些一起工作。坦率地说,我没有研究如何在 MySQL C API 的客户端更改 max_allowed_packet 变量的文档。
编辑 2
@akostadinov 指出有mysql_stmt_send_long_data() 将BLOB 数据以块的形式发送到服务器,而said 的人已经设法将大于max_allowed_packet 的BLOB 发送到INSERT。尽管如此,即使我设法INSERT,比如说,20MB BLOB 和max_allowed_packet=4MB 我如何SELECT 它回来?我不知道我该怎么做。
如果您指出正确的方向,我将不胜感激。
【问题讨论】:
-
您知道可以在 Amazon RDS 上使用 DB Parameter Groups 修改
max_allowed_packet吗? -
据我所知不是重复的,抱歉标记
-
我不确定你为什么说你“最终每行花费 16KB”。我只能想象您认为 InnoDB 仅限于每个索引页一条记录?那是绝对不正确的:每个索引页将被多条记录填充,直到它无法容纳更多。实际上,记录的内联存储数据长度小于半页的要求是确保每个页面包含至少两条记录!
-
我也不确定为什么你“不喜欢将 BLOB 部分存储在页面内和部分离页的想法”,因为这是一种性能增强(MySQL 必须始终考虑首先页面是为了找到对该字段的页外存储的引用,因此同时遇到前 768 个字节的数据会节省一点 I/O)。但是,如果您真的不喜欢它,
COMPRESSED和DYNAMIC格式都不会在索引页面中存储前缀。 -
我已经在上面解释过这些假设是错误的。如果您有大小为 8200 字节的块,则 BLOB 将始终存储在索引页之外——只有内部行开销、剩余列和 20 字节 BLOB 引用将被内联存储(也是如果您使用其中一种行格式,则为 768 字节前缀);因此 许多 记录仍将适合每个页面。如果您的块的大小足够小,两个记录可以放在一个索引页面中,那么根据定义,您将在每个页面中实现多个记录,但您在此过程中浪费了大量开销。