在MySQL InnoDB中存储大于max_allowed_pa​​cket的BLOB的最佳方法

Vla*_*nov 5 mysql innodb blob

也许这个问题应该在https://dba.stackexchange.com/上询问,我不确定.请在评论中提出建议或将其移至那里.

对于这个项目,我使用的是在Amazon RDS上托管的MySQL 5.6.19.

摘要

我要将数据库中的照片存储BLOBInnoDB表格的列中,我想知道最佳方法.我正在寻找可以比较不同变体的官方文档或某些方法.

在搜索这个主题时,有很多讨论和问题是关于将二进制文件存储在数据库中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_pa​​cket.在我的MySQL实例上,这个变量是4MB.阅读文档后我对这个变量的理解是有效的,单行不能超过4MB.拥有超过4MB的照片是很正常的,所以为了能够INSERTSELECT这样的文件我打算将文件分成小块:

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.图像不会是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,此时我意识到我想问这个问题.现在对我来说太压倒了,我希望有一个人对这个话题有实际经验.

我不想因无意中选择差的块大小和行格式而浪费一半的磁盘空间.我担心的是,如果我选择为每个块存储8000个字节,在同一行PhotoChunks表中为4个整数存储16个字节,那么它将超过页面大小的一半,我最终只花费了16KB用于每行8000字节数据.

有没有办法检查这种方式实际浪费了多少空间?在Amazon RDS环境中,我担心无法查看InnoDB表所包含的实际文件.否则,我会尝试不同的变体,看看最终的文件大小.

到目前为止,我可以看到有两个参数:行格式和块大小.也许还有其他事情需要考虑.

编辑

为什么我不考虑更改max_allowed_packet变量.来自doc:

客户端和服务器都有自己的max_allowed_pa​​cket变量,因此如果要处理大数据包,则必须在客户端和服务器中增加此变量.

我使用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回来?我不知道怎么做.

如果你指出我正确的方向,我将不胜感激.

Ric*_*mes 4

我坚持两年前在forums.mysql.com 上的回答。一些进一步的说明:

  • 16M 可能适用于max_allowed_packet,但我没有证据表明它可以超出此范围。
  • 在我几年前开发的一个应用程序中,大约 50KB 的块大小似乎是“最佳”。
  • max_allowed_pa​​cket 可以在 /etc/my.cnf 中设置。但如果你无法获得它,你就会被它的价值所困。您可以通过执行以下操作获得任何(?)版本的它SHOW VARIABLES LIKE 'max_allowed_packet'。(我相当确定会回到 4.0,但不确定 3.23。)所以这可能是块大小的上限。
  • InnoDB 会将大的 BLOB/TEXT 字段分割成 16KB 的块。可能每个块都有一些开销,因此您不会得到确切的 16KB。
  • Antelope 与 Barracuda 以及其他设置控制是否将 767 字节的 BLOB 存储在记录中。如果那里没有存储任何内容,则有一个指向块外存储的 20 字节指针。
  • 如今,16MB 似乎是图片大小的合理限制;明天就不会了。
  • 如果您运行的是足够新的MySQL版本,innodb_page_size可以从16K提高到32K或64K。(〜8000 上升到〜16000,但不是〜32000。)
  • 如果涉及复制,分块就变得更加重要。但块的“序列号”可能会存在一些额外棘手的问题。(问我是否需要朝这个方向走。)
  • 将以上评论加在一起,我建议将块大小设置为 MIN(64700, max_allowed_pa​​cket) 字节作为合理的妥协,即使您无法控制innodb_file_format. 在这个“照片”表中,只有 1-2% 的磁盘空间会被浪费(假设照片大小约为 1MB)。
  • 压缩是没有用的;JPG 已被压缩。
  • 大部分时间都在I/O;第二大是客户端和服务器之间的网络聊天。这里的要点是……在性能方面,C 与 PHP 不会有太大区别。
  • 每条记录约 8000 字节与本次讨论无关。这适用于具有大量列的表——它们加起来不能超过~8K。大多数 BLOB 将离开页,每行仅留下 60-800 字节,因此每个 16KB 块有 15-200 行(扣除其他类型的开销后的平均值)。
  • PARTITION不太可能有任何用处。
  • “分块是一种过早的优化”吗?如果您因为以下原因而碰壁,那么这不是一个“优化” max_allowed_packet

  • “两年前的答案”实际上是在这之前两年发布的。它是 Ricks 以前在论坛中的答案的链接的集合。可以在 https://forums.mysql.com/read.php?52,584053,584159#msg-584159 找到它,也可以查看 Ricks 页面 http://mysql.rjweb.org/ 有大量 mysql 的内容需要详述。他的 forum.mysql.com 最佳作品令人印象深刻:http://mysql.rjweb.org/bestof.html 干得好,Rick! (2认同)