为啥 InnoDB 表大小比预期大得多?

Posted

技术标签:

【中文标题】为啥 InnoDB 表大小比预期大得多?【英文标题】:Why is InnoDB table size much larger than expected?为什么 InnoDB 表大小比预期大得多? 【发布时间】:2011-02-24 06:18:37 【问题描述】:

我正在尝试找出不同存储引擎的存储要求。我有这张桌子:

CREATE TABLE  `mytest` (
  `num1` int(10) unsigned NOT NULL,
  KEY `key1` (`num1`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

当我插入一些值然后运行 ​​show table status; 时,我得到以下信息:

+----------------+--------+---------+------------+ ---------+----------------+-------------+--------- ---------+--------------+------------+----------- ---+----------+----------+-- ----------+-------+----------+-------- --------+---------+ |姓名 |引擎 |版本 |行格式 |行 |平均行长度 |数据长度 |最大数据长度 |索引长度 |数据免费 |自动增量 |创建时间 |更新时间 |检查时间 |整理 |校验和 |创建选项 |评论 | +----------------+--------+---------+------------+ ---------+----------------+-------------+--------- ---------+--------------+------------+----------- ---+----------+----------+-- ----------+-------+----------+-------- --------+---------+ |我的测试 |数据库 | 10 |紧凑 | 1932473 | 35 | 67715072 | 0 | 48840704 | 4194304 |空 | 2010-05-26 11:30:40 |空 |空 | latin1_swedish_ci |空 | | |

注意 avg_row_length 是 35。我很困惑,当我只存储一个不可为空的整数时,InnoDB 不会更好地利用空间。

我在 myISAM 上运行了相同的测试,默认情况下 myISAM 在此表上每行使用 7 个字节。当我跑步时

ALTER TABLE mytest MAX_ROWS=50000000, AVG_ROW_LENGTH = 4;

使 myISAM 最终正确使用 5 字节行。

当我为 InnoDB 运行相同的 ALTER TABLE 语句时,avg_row_length 不会改变。

为什么只存储 4 字节的 unsigned int 时需要这么大的 avg_row_length?

【问题讨论】:

我刚刚读到 InnoDB 将表空间用于数据和索引。这是有道理的,似乎这就是我看到如此大的 avg_row_length 的原因......也许。我还发现每个叶子节点都存储了事务 ID 和回滚指针。好吧,我本身并没有使用交易,因此对这些数据没有用处。有没有办法不存储这些值?有什么方法可以使用 InnoDB,但可以更好地利用存储?谢谢! @alessandro:是的,事务支持也确实增加了一些开销。您没有使用事务这一事实并不意味着它们没有被使用:例如,在长时间的UPDATE 操作期间被杀死的线程将在InnoDB 中正确回滚,但在MyISAM 中不会正确回滚。事务支持是使用InnoDB的重点,如果你不需要它,只需使用MyISAM @Quassnoi:我的印象是 MyISAM 不像 InnoDB 那样“成熟”或生产就绪……也许这是一种毫无根据的恐惧。除了 MyISAM 需要全表锁来保证一致性之外,MyISAM 在备份数据库时是否会给表带来任何困难。我不需要交易,我将存储大量数据。 MyISAM 是否有任何已知问题会导致您不使用它? @alessandro:“不需要交易”到底是什么意思? en.wikipedia.org/wiki/ACID 的哪一部分你不需要? @alessandro:是的,我已经在生产环境中部署了数百个 MyISAM 数据库。至于提示,在不知道您的要求的情况下,在一篇文章中推荐的提示太多了。阅读mysqlperformanceblog.com 了解系统管理技巧,阅读我的博客explainextended.com/category/mysql,了解高效SQL 【参考方案1】:

MyISAM

MyISAM,除了在真正的旧版本中,使用一个 7 字节的“指针”来定位一行,并在索引中使用一个 6 字节的指针。这些默认值会导致 巨大 最大表大小。更多详情:http://mysql.rjweb.org/doc.php/limits#myisam_specific_limits。改变这些的笨拙方法涉及您发现的ALTER .. MAX_ROWS=50000000, AVG_ROW_LENGTH = 4。服务器将这些值相乘以计算数据指针需要多少字节。因此,您偶然发现了如何缩小 avg_row_length。

但您实际上需要声明一个少于 7 个字节的表才能命中它!指针大小多处显示:

.MYD 中的可用空间链接默认为 7 个字节。因此,当您删除一行时,会提供一个指向下一个空闲位置的链接。该链接需要为 7 个字节(默认情况下),因此行大小是从 4 字节 INT 人为扩展来为其腾出空间的! (还有更多细节与列是否为 NULLable 等有关。

FIXED vs DYNAMIC row -- 当表是 FIXED 大小时,“指针”是一个行号。对于 DYNAMIC,它是 .MYD 中的字节偏移量。

索引条目还必须通过指针指向数据行。所以你的ALTER 应该也缩小了 .MYI 文件!

还有更多细节,但 MyISAM 很可能会消失,所以这段古老的历史不太可能引起任何人的关注。

InnoDB

https://***.com/a/64417275/1766831

【讨论】:

【参考方案2】:

这里有一些您可能会觉得有用的更多信息。

InnoDB 以 16KB 页面的形式分配数据,因此如果您只有几行并且表的总大小小于 16K,那么“SHOW TABLE STATUS”将给出行大小的膨胀数字。 (例如,对于 4 行,平均行大小返回为 4096。)

当空间是一个重要的考虑因素时,“不可见”主键每行额外的 6 个字节是一个关键点。如果您的表只有一列,那么这是制作主键的理想列,假设其中的值是唯一的:

CREATE TABLE `mytest2`
       (`num1` int(10) unsigned NOT NULL primary key)
ENGINE=InnoDB DEFAULT CHARSET=latin1;

通过使用这样的主键:

    不需要 INDEX 或 KEY 子句,因为您没有二级索引。 InnoDB 表的索引组织格式可让您免费根据主键值快速查找。 您不会得到另一个 NUM1 列数据的副本,当该列被显式索引时会发生这种情况。 您不会得到另一个 6 字节不可见主键值的副本。主键值在每个二级索引中重复。 (这也是为什么您可能不希望有 10 列的表上有 10 个索引,并且您可能不希望主键组合多个不同的列或者是一个长字符串列。)

因此,总的来说,仅使用主键意味着与表 + 索引关联的数据更少。为了了解整体数据大小,我喜欢使用

set innodb_file_per_table = 1;

并检查数据/数据库/*table*.ibd 文件的大小。每个 .ibd 文件都包含 InnoDB 表及其所有关联索引的数据。

为了快速建立一个大表进行测试,我通常会运行如下语句:

insert into mytest
select * from mytest;

每次都会使数据量翻倍。在使用主键的单列表的情况下,由于值必须是唯一的,我使用了一个变体来防止值相互冲突:

insert into mytest2
select num1 + (select count(*) from mytest2) from mytest2;

这样,我能够将平均行大小降低到 25。空间开销基于您希望使用指针式机制快速查找各个行的基本假设,并且大多数表将具有除了具有被求和、平均和显示的真实数据的列之外,其值用作指针(即主键)的列。

【讨论】:

【参考方案3】:

除了 Quassnoi 的非常好的答案之外,您可能应该使用重要的数据集进行尝试。

我要做的是,加载 100 万行模拟生产数据,然后测量表大小并将其用作指导。

这就是 I've done in the past anyway

【讨论】:

是的,谢谢 MarkR,我用 50M 行的数据集完成了这项工作。 InnoDB 在 3GIG 上使用了 3 倍以上的空间 根据我的研究(见上面的链接),这是典型的;您可以通过使用插件并打开压缩来使用更少的空间。有关一些数据,请参阅我的博客文章。【参考方案4】:

InnoDB 表是集群的,这意味着所有数据都包含在 B-Tree 中,PRIMARY KEY 作为键,所有其他列作为有效负载。

由于您没有明确定义 PRIMARY KEYInnoDB 使用隐藏的 6 字节列对记录进行排序。

B-Tree 组织的这个和开销(带有额外的非叶级块)需要比sizeof(int) * num_rows 更多的空间。

【讨论】:

我知道这个答案已经有 10 多年的历史了,但我仍然想知道。提到的与 OP 的差异是 30 字节。那么这是否意味着6字节占用隐藏列,4整数和剩余25字节都属于B-Tree71%B-Tree 来说是不是太过分了? @Adam: avg_row_length 是页数 * 页面大小 / 记录数。页数(相对于记录数)取决于 B-Tree 深度和碎片。碎片发生在删除、不按 PK 顺序插入或更新会增加行大小(后两个操作导致页面拆分)。分页存储(页眉等)也会导致一些小的内在开销。如果您的 MERGE_THRESHOLD 足够低并且您正在对表进行大量删除,那么 71% 的开销是一个合理的值

以上是关于为啥 InnoDB 表大小比预期大得多?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 iPhone .app 文件比 .ipa 文件大得多?

为啥我的 favicon.ico 文件比其各部分的总和大得多?

对象比相应的 .RData 文件大得多。为啥?可以手动做吗?

writeRaster 到 .img 文件的大小比源文件大得多

为啥链接区域的高度比文本大得多?

Python CSV to JSON:为啥 JSON 比 CSV 文件大得多,如何修复?