也许这个问题应该在https://dba.stackexchange.com/上询问,我不确定.请在评论中提出建议或将其移至那里.
对于这个项目,我使用的是在Amazon RDS上托管的MySQL 5.6.19.
摘要
我要将数据库中的照片存储BLOB
在InnoDB
表格的列中,我想知道最佳方法.我正在寻找可以比较不同变体的官方文档或某些方法.
在搜索这个主题时,有很多讨论和问题是关于将二进制文件存储在数据库中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
Run Code Online (Sandbox Code Playgroud)
对于每张照片,我会存储原始的全分辨率图像和少量缩小版本,所以我有一张表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
Run Code Online (Sandbox Code Playgroud)
最后,有一个表格可以保存所有图像的实际二进制数据.我知道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
Run Code Online (Sandbox Code Playgroud)
此外,我将能够一次将大型照片上传到数据库中几个块,并在连接断开时恢复上传.
数据量
估计的数据量为40,000张全分辨率照片,每张约5MB => 200GB.缩小版本很可能是800x600,每个~120KB => +额外5GB.图像不会是UPDATE
d.几年后它们最终将被删除.
题
有很多方法可以将文件拆分成更小的块:您可以将其拆分为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字节的值,该值存储列的真实长度并指向溢出列表,其中存储了值的其余部分.
考虑到上述情况,至少可以采取以下策略:
我也遇到了这个文档https://dev.mysql.com/doc/refman/5.6/en/innodb-row-format-dynamic.html,此时我意识到我想问这个问题.现在对我来说太压倒了,我希望有一个人对这个话题有实际经验.
我不想因无意中选择差的块大小和行格式而浪费一半的磁盘空间.我担心的是,如果我选择为每个块存储8000个字节,在同一行PhotoChunks
表中为4个整数存储16个字节,那么它将超过页面大小的一半,我最终只花费了16KB用于每行8000字节数据.
有没有办法检查这种方式实际浪费了多少空间?在Amazon RDS环境中,我担心无法查看InnoDB表所包含的实际文件.否则,我会尝试不同的变体,看看最终的文件大小.
到目前为止,我可以看到有两个参数:行格式和块大小.也许还有其他事情需要考虑.
编辑
为什么我不考虑更改max_allowed_packet
变量.来自doc:
客户端和服务器都有自己的max_allowed_packet变量,因此如果要处理大数据包,则必须在客户端和服务器中增加此变量.
我使用MySQL C API来处理这个数据库,并且使用相同的C++应用程序与200个其他MySQL服务器(完全与此项目无关)进行通信libmysql.dll
.其中一些服务器仍然是MySQL 3.23.所以我的应用程序必须与所有这些一起工作.坦率地说,我没有研究如何max_allowed_packet
在MySQL C API的客户端更改变量的文档.
编辑2
@akostadinov指出,mysql_stmt_send_long_data()
将BLOB数据以块的形式发送到服务器,人们说他们已经设法使用INSERT
大于的BLOB max_allowed_packet
.尽管如此,即使我设法INSERT
说,20MB BLOB max_allowed_packet
= 4MB,我怎么SELECT
回来?我不知道怎么做.
如果你指出我正确的方向,我将不胜感激.
我坚持两年前在forums.mysql.com 上的回答。一些进一步的说明:
max_allowed_packet
,但我没有证据表明它可以超出此范围。SHOW VARIABLES LIKE 'max_allowed_packet'
。(我相当确定会回到 4.0,但不确定 3.23。)所以这可能是块大小的上限。innodb_page_size
可以从16K提高到32K或64K。(〜8000 上升到〜16000,但不是〜32000。)innodb_file_format
. 在这个“照片”表中,只有 1-2% 的磁盘空间会被浪费(假设照片大小约为 1MB)。PARTITION
不太可能有任何用处。max_allowed_packet
。