为什么从 VARCHAR 列中剥离一些数据后 MySQL MyISAM table 的大小相同?

Why the size of MySQL MyISAM table is the same after striping some data from VARCHAR column?

我需要减小 MySQL 数据库的大小。我重新编码了一些带有“;”条纹的信息和 sources 列中的“:”(约减少 10% 的字符)。这样做之后, table 的大小与之前完全相同。这怎么可能?我正在使用 MyISAM 引擎。

顺便说一句:不幸的是,我无法用 myisampack.

压缩 tables
mysql> INSERT INTO test SELECT protid1, protid2, CS, REPLACE(REPLACE(sources, ':', ''), ';', '') FROM homologs_9606; 
Query OK, 41917131 rows affected (4 min 11.30 sec)
Records: 41917131  Duplicates: 0  Warnings: 0

mysql> select TABLE_NAME name, ROUND(TABLE_ROWS/1e6, 3) 'million rows', ROUND(DATA_LENGTH/power(2,30), 3) 'data GB', ROUND(INDEX_LENGTH/power(2,30), 3) 'index GB' from information_schema.TABLES WHERE TABLE_NAME IN ('homologs_9606', 'test') ORDER BY TABLE_ROWS DESC LIMIT 10;
+---------------+--------------+---------+----------+
| name          | million rows | data GB | index GB |
+---------------+--------------+---------+----------+
| test          |       41.917 |   0.857 |    1.075 |
| homologs_9606 |       41.917 |   0.887 |    1.075 |
+---------------+--------------+---------+----------+
2 rows in set (0.01 sec)

mysql> select * from homologs_9606 limit 10;
+---------+---------+-------+--------------------------------+
| protid1 | protid2 | CS    | sources                        |
+---------+---------+-------+--------------------------------+
| 5635338 | 1028608 | 0.000 | 10:,1                          |
| 5644385 | 1028611 | 0.947 | 5:1,1;8:0.943,35;10:1,1;11:1,1 |
| 5652325 | 1028611 | 0.947 | 5:1,1;8:0.943,35;10:1,1;11:1,1 |
| 5641128 | 1028612 | 1.000 | 8:1,10                         |
| 5636414 | 1028616 | 0.038 | 8:0.038,104;10:,1              |
| 5636557 | 1028616 | 0.000 | 8:,4                           |
| 5637419 | 1028616 | 0.011 | 5:,1;8:0.011,91;10:,1          |
| 5641196 | 1028616 | 0.080 | 5:1,1;8:0.074,94;10:,1;11:,4   |
| 5642914 | 1028616 | 0.000 | 8:,3                           |
| 5643778 | 1028616 | 0.056 | 8:0.057,70;10:,1               |
+---------+---------+-------+--------------------------------+
10 rows in set (4.55 sec)

mysql> select * from test limit 10;
+---------+---------+-------+-------------------------+
| protid1 | protid2 | CS    | sources                 |
+---------+---------+-------+-------------------------+
| 5635338 | 1028608 | 0.000 | 10,1                    |
| 5644385 | 1028611 | 0.947 | 51,180.943,35101,1111,1 |
| 5652325 | 1028611 | 0.947 | 51,180.943,35101,1111,1 |
| 5641128 | 1028612 | 1.000 | 81,10                   |
| 5636414 | 1028616 | 0.038 | 80.038,10410,1          |
| 5636557 | 1028616 | 0.000 | 8,4                     |
| 5637419 | 1028616 | 0.011 | 5,180.011,9110,1        |
| 5641196 | 1028616 | 0.080 | 51,180.074,9410,111,4   |
| 5642914 | 1028616 | 0.000 | 8,3                     |
| 5643778 | 1028616 | 0.056 | 80.057,7010,1           |
+---------+---------+-------+-------------------------+
10 rows in set (0.00 sec)

mysql> describe test;
+---------+------------------+------+-----+---------+-------+
| Field   | Type             | Null | Key | Default | Extra |
+---------+------------------+------+-----+---------+-------+
| protid1 | int(10) unsigned | YES  | PRI | NULL    |       |
| protid2 | int(10) unsigned | YES  | PRI | NULL    |       |
| CS      | float(4,3)       | YES  |     | NULL    |       |
| sources | varchar(100)     | YES  |     | NULL    |       |
+---------+------------------+------+-----+---------+-------+
4 rows in set (0.00 sec)

mysql> describe homologs_9606;
+---------+------------------+------+-----+---------+-------+
| Field   | Type             | Null | Key | Default | Extra |
+---------+------------------+------+-----+---------+-------+
| protid1 | int(10) unsigned | NO   | PRI | 0       |       |
| protid2 | int(10) unsigned | NO   | PRI | 0       |       |
| CS      | float(4,3)       | YES  |     | NULL    |       |
| sources | varchar(100)     | YES  |     | NULL    |       |
+---------+------------------+------+-----+---------+-------+
4 rows in set (0.00 sec)

EDIT1:添加平均列长度。

mysql> select AVG(LENGTH(sources)) from test; 
+----------------------+
| AVG(LENGTH(sources)) |
+----------------------+
|               5.2177 |
+----------------------+
1 row in set (10.04 sec)

mysql> select AVG(LENGTH(sources)) from homologs_9606; 
+----------------------+
| AVG(LENGTH(sources)) |
+----------------------+
|               6.8792 |
+----------------------+
1 row in set (9.95 sec)

EDIT2:通过将 NOT NULL 设置为所有列,我能够剥离更多 MB。

mysql> drop table test
Query OK, 0 rows affected (0.42 sec)

mysql> CREATE table test (protid1 INT UNSIGNED NOT NULL DEFAULT '0', protid2 INT UNSIGNED NOT NULL DEFAULT '0', CS FLOAT(4,3) NOT NULL DEFAULT '0', sources VARCHAR(100) NOT NULL DEFAULT '0', PRIMARY KEY (protid1, protid2), KEY `idx_protid2` (protid2)) ENGINE=MyISAM CHARSET=ascii;
Query OK, 0 rows affected (0.06 sec)

mysql> INSERT INTO test SELECT protid1, protid2, CS, REPLACE(REPLACE(sources, ':', ''), ';', '') FROM homologs_9606; 
Query OK, 41917131 rows affected (2 min 7.84 sec)

mysql> select TABLE_NAME name, ROUND(TABLE_ROWS/1e6, 3) 'million rows', ROUND(DATA_LENGTH/power(2,30), 3) 'data GB', ROUND(INDEX_LENGTH/power(2,30), 3) 'index GB' from information_schema.TABLES WHERE TABLE_NAME IN ('homologs_9606', 'test');
Records: 41917131  Duplicates: 0  Warnings: 0

+---------------+--------------+---------+----------+
| name          | million rows | data GB | index GB |
+---------------+--------------+---------+----------+
| homologs_9606 |       41.917 |   0.887 |    1.075 |
| test          |       41.917 |   0.842 |    1.075 |
+---------------+--------------+---------+----------+
2 rows in set (0.02 sec)

它们并不完全相同。您的查询清楚地表明 testhomologs_9606:

小大约 30 MB
+---------------+--------------+---------+
| name          | million rows | data GB |
+---------------+--------------+---------+
| test          |       41.917 |   0.857 | <-- 0.857 < 0.887
| homologs_9606 |       41.917 |   0.887 |
+---------------+--------------+---------+

我们希望您的 table 有多少存储空间?让我们检查一下 Data Type Storage Requirements:

INTEGER(10): 4 bytes
FLOAT(4): 4 bytes
VARCHAR(100): L+1

其中 L 是字符字节数,通常每个字符一个字节,但如果使用 Unicode 字符集,有时会更多。

您的行平均需要:

INTEGER + INTEGER + FLOAT + VARCHAR =
4 + 4 + 4 + (L + 1) = L + 13 bytes

我们可以推断出您原来的平均 L 为 (0.887*1024^3 / 41917131) - 13 = 9.72。你说你从 sources 中剥离了 10%,这意味着你的新 L 是 9.72*0.9 = 8.75。这给出了 ((8.75 + 13) * 41917131) / 1024^3 = 0.849 GB

的预期新总存储要求

我怀疑差异(0.849 和 0.857 之间)可能是由于 test 有两列设置为 NULLable 而 homologs_9606 没有,但我 do not know enough 关于 MyISAM 引擎来准确计算这个。不过我能猜到!至少,每行每列需要 1 位来存储 NULL 状态,在您的情况下,这意味着每行两位或 2*41917131 = 83834262 bits = 10 479 283 bytes = 0.010 GB。总数 0.849+0.010 = 0.859 略高于目标(大约多出 2 MB)。但我做了一些四舍五入,你的 10% 的数字也是一个估计值,所以我确信其余部分在翻译中丢失了。

另一个原因可能是如果您在 test 中的 sources 上使用 Unicode 字符集,在这种情况下,某些字符可能每个使用一个以上的字节,但由于 NULLable 列似乎占对于一切,我认为您的 table.

并非如此

总结

  • 您的两个 table 大小不同,它们相差 30 MB。
  • 您的新 table 大小与预期大小相近。
  • 通过将 protid1protid2 放入 NOT NULL 列,您可以在新的 table 中节省更多 space。

"table" 存储在 .MYD 文件中。由于 UPDATEsDELETEs,此文件将 永远不会 缩小。 SHOW TABLE STATUS(或 information_schema 的等效查询)可能会显示 Data_length 缩小,但 Data_free 会增加。

您可以通过 OPTIMIZE TABLE 缩小 .MYD 文件。但这会将 table 复制过来,因此在此过程中需要额外的磁盘 space。而且这个动作很少值得做。

如果您有很多空值,则更改为 NOT NULL 可能无法释放 space -- 由于长度原因,"" 对于 VARCHAR 需要 1 或 2 个字节。 (并且您的代码可能需要以不同于 NULL 的方式处理 ''。)

每行的 space 实际上比前面提到的多 1 个字节 -- 这个字节处理知道该行是否存在或是否是一个洞的开始。

对于大型文本字段,我喜欢这样做以节省 space。 (这适用于 MyISAM 和 InnoDB。)压缩文本并将其存储到 BLOB 列(而不是 TEXT)。对于大多数文本,这是 3:1 收缩。它在客户端需要一些额外的代码和 CPU 时间,但它在服务器端节省了很多 I/O 。通常最终结果是 "faster"。我不会将它用于您拥有的 varchar;我只会在平均大于 50 个字符的列上执行此操作。

回到最初的问题。整个table听起来好像只有30M左右的冒号和分号。难道前10行不具有代表性?