如何查找和修复碎片化的 MySQL 表

cur*_*cat 34 mysql

我使用了 MySQLTuner,它指出一些表是碎片化的。我用了

mysqlcheck --optimize -A

优化所有表。它修复了一些表,但 MySQLTuner 仍然发现 19 个表碎片化。如何查看哪些表需要进行碎片整理?也许 OPTIMIZE TABLE 会在 mysqlcheck 没有的地方工作?或者我还应该尝试什么?

小智 46

简短的回答:

select  ENGINE, TABLE_NAME,Round( DATA_LENGTH/1024/1024) as data_length , round(INDEX_LENGTH/1024/1024) as index_length, round(DATA_FREE/ 1024/1024) as data_free from information_schema.tables  where  DATA_FREE > 0;
Run Code Online (Sandbox Code Playgroud)

“你必须知道”的答案

首先你必须明白,当一行更新时,Mysql 表会变得碎片化,所以这是正常情况。创建表时,假设使用带有数据的转储导入,所有行都存储在许多固定大小的页面中,没有碎片。当您更新可变长度行时,包含该行的页面被分成两页或更多页以存储更改,并且这两个(或更多)新页包含填充未使用空间的空白空间。

这不会影响性能,当然除非碎片增长太多。什么是太多碎片,让我们看看你正在寻找的查询:

  select  ENGINE, TABLE_NAME,Round( DATA_LENGTH/1024/1024) as data_length , round(INDEX_LENGTH/1024/1024) as index_length, round(DATA_FREE/ 1024/1024) as data_free from information_schema.tables  where  DATA_FREE > 0;
Run Code Online (Sandbox Code Playgroud)

DATA_LENGTH 和 INDEX_LENGTH 是您的数据和索引正在使用的空间,DATA_FREE 是所有表页(分段)中未使用的总字节数。

这是一个真实的生产表的例子

| ENGINE | TABLE_NAME               | data_length | index_length | data_free |
| InnoDB | comments                 |         896 |          316 |         5 |
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们有一个使用 (896 + 316) = 1212 MB 的表,并且有 5 MB 的可用空间数据。这意味着“碎片率”为:

5/1212 = 0.0041
Run Code Online (Sandbox Code Playgroud)

...这是一个非常低的“碎片率”。

我一直在使用比率接近 0.2(意味着 20% 的空格)的表,并且从未注意到查询速度变慢,即使我优化了表,性能也是一样的。但是在 800MB 的表上应用优化表需要大量时间并阻塞该表几分钟,这在生产中是不切实际的。

因此,如果您考虑在性能方面的优势以及优化表所浪费的时间,我更喜欢 NOT OPTIMIZE。

如果您认为存储更好,请查看您的比率并查看优化时可以节省多少空间。它通常不会太多,所以我更喜欢不优化。

如果您进行优化,下一次更新将通过将页面分成两个或更多来创建空白空间。但是更新碎片表比更新未碎片表要快,因为如果表碎片化,则行上的更新不一定会拆分页面。

我希望这可以帮助你。

  • 尽管这是几年前的答案,但我想我想指出 data_free 是整个表空间的统计信息,而不是相应表的统计信息。如果您将多个表一起存储在一个表空间中,则 data_free 可能会误导您认为该表需要进行碎片整理,而这只是意味着表空间中有空闲区。运行优化表不会减少可用范围。对表进行碎片整理甚至可能*增加*可用区。 (3认同)

小智 19

只是添加到Felipe-Rojas的答案中,您可以计算片段比率作为查询的一部分:

select ENGINE,
  concat(TABLE_SCHEMA, '.', TABLE_NAME) as table_name,
  round(DATA_LENGTH/1024/1024, 2) as data_length,
  round(INDEX_LENGTH/1024/1024, 2) as index_length,
  round(DATA_FREE/1024/1024, 2) as data_free,
  (data_free/(index_length+data_length)) as frag_ratio
FROM information_schema.tables
WHERE DATA_FREE > 0
ORDER BY frag_ratio DESC;
Run Code Online (Sandbox Code Playgroud)

如果一个表被碎片化了一小部分(小于 5%?),那么你可以不理会它。

任何更大的东西,您都需要根据您的数据库使用情况、锁定表等来评估对表进行碎片整理的重要性。


dae*_*aos 2

优化表确实可以解决您遇到的问题。

如果您只有几个数据库,那么您可以使用 PHPMyAdmin 来浏览所有数据库。选择有开销的表,然后选择优化。

如果您有很多数据库,那么另一种方法可能会更好。

我在 cron 中使用以下 PHP 脚本设置每小时运行一次。

$DB = new mysqli ('localhost', 'DbUser', 'DbPassword');
$results = $DB->query('show databases');
$allDbs = array();
while ($row = $results->fetch_array(MYSQLI_NUM))
{
    $allDbs[] = $row[0];
}
$results->close();
foreach ($allDbs as $dbName)
{
    if ($dbName != 'information_schema' && $dbName != 'mysql')
    {
        $DB->select_db($dbName);
        $results = $DB->query('SHOW TABLE STATUS WHERE Data_free > 0');
        if ($results->num_rows > 0)
        {
            while ($row = $results->fetch_assoc())
            {
                $DB->query('optimize table ' . $row['Name']);
            }
        }
        $results->close();
    }
}
$DB->close();
Run Code Online (Sandbox Code Playgroud)

  • 我很确定 `mysqlcheck --optimize -A` 与 SQL `OPTIMIZE TABLE <tablename>;` 相同 (3认同)