平均行长超出可能

efr*_*eed 1 mysql storage

这不是InnoDB表大小为什么比预期的大得多的重复项 该问题的答案指出,如果我未指定主键,则将6个字节添加到该行。我确实指定了一个主键,这里有6个以上的字节来解释。


我有一个表,该表预期有数百万条记录,因此我密切注意了每一列的存储大小。 每行应占用15个字节(smallint = 2个字节,date = 3个字节,datetime = 8个字节)

CREATE TABLE archive (
  customer_id smallint(5) unsigned NOT NULL,
  calendar_date date NOT NULL,
  inserted datetime NOT NULL,
  value smallint(5) unsigned NOT NULL,
  PRIMARY KEY (`customer_id`,`calendar_date`,`inserted`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Run Code Online (Sandbox Code Playgroud)

该表现在有50万条记录,并且存储空间超出了预期。我运行此查询以从系统中获取更多详细信息:

SELECT *
  FROM information_schema.TABLES
 WHERE table_name = 'archive';


information_schema.index_length = 0
information_schema.avg_row_length = 37
information_schema.engine = InnoDB
information_schema.table_type = BASE TABLE
Run Code Online (Sandbox Code Playgroud)

怎么样!?

我原本希望每行15个字节,而这需要37个字节。 谁能给我一个下一个查找说明的想法吗?我已经阅读了很多有关Thais的文章,并且看到了一些有关在行大小上增加6或10个字节的解释,但这并不能解释22个额外的字节。

一种解释是索引也会占用存储空间。该表上没有索引。

一种解释是,information_schema.tables查询返回了不可靠的行数,这会抛出avg_row_length。我已经针对count(*)查询检查了它正在使用的行数,并且只差了一点(1%的1/20),所以这不是全部。

另一个解释是碎片。值得注意的是,该表是通过sql转储重建的,因此没有任何更新,插入和删除操作。

Sch*_*ern 7

  • 因为avg_row_lengthdata_length / rows

data_length基本上是磁盘上表的总大小。InnoDB表不仅仅是行列表。因此,存在额外的开销。

  • 因为InnoDB行不只是数据。

与上面类似,每行都有一些开销。因此,这将增加行的大小。InnoDB表也不只是一个挤在一起的数据列表。它需要一些额外的空白空间才能有效地工作。

  • 因为东西以块的形式存储在磁盘上,而这些块并不总是满的。

磁盘通常以4K,8K或16K 存储内容。有时事情并不能完全适合这些区块,因此您可以获得一些空白

如下所示,MySQL将按块分配表。而且它将分配更多的资源来避免必须增长表(这可能会很慢并导致磁盘碎片化,从而使事情变得更慢)。


为了说明这一点,让我们从一个空表开始。

mysql> create table foo ( id smallint(5) unsigned NOT NULL );
mysql> select data_length, table_rows, avg_row_length from information_schema.tables where table_name = 'foo';
+-------------+------------+----------------+
| data_length | table_rows | avg_row_length |
+-------------+------------+----------------+
|       16384 |          0 |              0 |
+-------------+------------+----------------+
Run Code Online (Sandbox Code Playgroud)

它使用16K或四个4K块来存储任何内容。空表不需要此空间,但是MySQL假设您将要在其中放入大量数据来分配它。这避免了必须在每个插件上进行昂贵的重新分配。

现在让我们添加一行。

mysql> insert into foo (id) VALUES (1);
mysql> select data_length, table_rows, avg_row_length from information_schema.tables where table_name = 'foo';
+-------------+------------+----------------+
| data_length | table_rows | avg_row_length |
+-------------+------------+----------------+
|       16384 |          1 |          16384 |
+-------------+------------+----------------+
Run Code Online (Sandbox Code Playgroud)

该表没有变大,它拥有的这4个块中有所有未使用的空间。只有一行,这意味着avg_row_length为16K。显然荒唐。让我们添加另一行。

mysql> insert into foo (id) VALUES (1);
mysql> select data_length, table_rows, avg_row_length from information_schema.tables where table_name = 'foo';
+-------------+------------+----------------+
| data_length | table_rows | avg_row_length |
+-------------+------------+----------------+
|       16384 |          2 |           8192 |
+-------------+------------+----------------+
Run Code Online (Sandbox Code Playgroud)

一样。该表分配了16K,使用该空间2行。每行8K的荒谬结果。

当我插入越来越多的行时,表的大小保持不变,它正在使用越来越多的分配空间,并且avg_row_length越来越接近实际。

mysql> select data_length, table_rows, avg_row_length from information_schema.tables where table_name = 'foo';                                                                     
+-------------+------------+----------------+
| data_length | table_rows | avg_row_length |
+-------------+------------+----------------+
|       16384 |       2047 |              8 |
+-------------+------------+----------------+
Run Code Online (Sandbox Code Playgroud)

在这里我们也开始看到table_rows变得不准确。我肯定插入了2048行。

现在,当我插入更多...

mysql> select data_length, table_rows, avg_row_length from information_schema.tables where table_name = 'foo';
+-------------+------------+----------------+
| data_length | table_rows | avg_row_length |
+-------------+------------+----------------+
|       98304 |       2560 |             38 |
+-------------+------------+----------------+
Run Code Online (Sandbox Code Playgroud)

(我插入了512行,table_rows由于某种原因已经恢复了现实)

MySQL决定该表需要更多空间,因此调整了表的大小并获取了更多磁盘空间。 avg_row_length只是再次跳了起来。

假设以后需要它,那么它占用的512行所需的空间要多得多,现在是96K或24个4K块。这样可以最大程度地减少需要重新分配的数量,并最大程度地减少磁盘碎片。

这并不意味着所有的空间都被填满了。这只是意味着MySQL认为它已满,需要更多空间才能有效运行。如果您想知道为什么会这样,请查看哈希表的工作方式。我不知道InnoDB是否使用哈希表,但是该原则适用:某些数据结构在有空白空间时运行最佳。


表使用的磁盘与表中的行数和列的类型直接相关,但是确切的公式很难弄清楚,并且会随MySQL版本的不同而变化。最好的选择是做一些实证测试,并向自己辞职,说永远不会得到确切的数字。