平均行长度高于可能

时间:2015-12-10 20:44:29

标签: mysql storage

这不是Why is InnoDB table size much larger than expected?的重复。该问题的答案表明,如果我没有指定主键,则会向该行添加6个字节。我确实指定了一个主键,这里有超过6个字节要解释。

我有一张预计有数百万条记录的表,因此我密切关注每列的存储大小。 每行应占用15个字节(smallint = 2个字节,日期= 3个字节,日期时间= 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;

该表现在有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

HOW!?

我预计每行15个字节,并且它需要37个。有人能让我知道下一步要查找解释的位置吗?我已经在thais上做了很多阅读,并且我已经看到一些额外的6或10个字节被添加到行大小的解释,但这并不能解释22个额外的字节。

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

一种解释是,information_schema.tables查询返回一个不可靠的行计数,它会抛弃avg_row_length。我已经检查了它对计数(*)查询使用的行计数,它只是稍微偏离(1/20的1/20),所以这不是整个故事。

另一种解释是碎片化。值得注意的是,这个表已经从一个sql转储重建,所以没有任何更新,插入和删除的锤击。

1 个答案:

答案 0 :(得分:6)

  • 因为avg_row_lengthdata_length / rows

data_length基本上是磁盘上表的总大小。 InnoDB表不仅仅是一个行列表。所以有额外的开销。

  • 因为InnoDB行不仅仅是数据。

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

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

磁盘存储的内容通常为4K,8K或16K blocks。有时候事情并不适合这些街区,所以你可以得到some empty space

正如我们将在下面看到的,MySQL将以块的形式分配表。并且它将分配比需要更多的东西以避免不得不增长表(这可能很慢并导致disk fragmentation,这会使事情变得更慢)。

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

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 |
+-------------+------------+----------------+

它使用16K或4个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 |
+-------------+------------+----------------+

桌子没有变得更大,那里有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 |
+-------------+------------+----------------+

同样的事情。为表分配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 |
+-------------+------------+----------------+

此处我们也开始看到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 |
+-------------+------------+----------------+

(我插入了512行,table_rows由于某种原因而重新回到了现实状态)

MySQL决定该表需要更多空间,因此它已经调整大小并占用了更多的磁盘空间。 avg_row_length刚刚跳了起来。

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

这并不意味着所有空间都已填满。它只是意味着MySQL认为它足够满足需要更多空间来高效运行。如果你想知道为什么会这样,那么看看hash table是如何运作的。我不知道InnoDB是否使用哈希表,但原则适用:某些数据结构在有空的空间时运行得最好。

表使用的磁盘与表中的行数和列类型直接相关,但确切的公式很难弄清楚,并且会从MySQL的版本更改为版本。你最好的选择是进行一些经验测试并让自己辞职,以免得到确切的数字。