原创

OPTIMIZE TABLE 能否归还磁盘:先确认 InnoDB 表空间归属

删掉九成记录以后,磁盘文件没有明显变小,不代表 DELETE 没有成功。记录是否还存在、InnoDB 是否有可复用空间、操作系统是否收回文件占用,分别属于三个层次。直接接一条 OPTIMIZE TABLE,也不能跳过表空间归属。

对 MySQL 8.4 的 InnoDB 表,独立表空间重建与共享表空间内腾出空间,结果可能不同。先确定这张表实际存在哪里,再决定是否值得为回收空间支付重建成本,比看到文件大就全库执行 OPTIMIZE 更可靠。

一个全局开关,不能证明所有旧表都独立

innodb_file_per_table 影响新表默认放置,不会在开启时把所有历史表自动迁出共享表空间。旧表也可能通过显式 TABLESPACE 选项加入通用表空间。所以“服务器现在是 ON”只能作为线索,不能直接成为目标表的证据。

SELECT VERSION(), @@GLOBAL.innodb_file_per_table,
       @@GLOBAL.innodb_optimize_fulltext_only;
SHOW CREATE TABLE your_schema.your_table;

SHOW CREATE TABLE 先确认引擎与显式表空间设置;更具体的 InnoDB 表空间信息可由具备相应管理权限的连接查询。不要因为普通业务账号无法读管理视图,就临时扩大它的权限,只为让一个维护脚本继续运行。

对已有业务表,可以先从表的数据字典身份映射到空间,而不是把通用表空间名字当作“库名/表名”猜测:

SELECT t.NAME AS table_name, t.SPACE, t.SPACE_TYPE AS table_space_type,
       s.NAME AS tablespace_name, s.FILE_SIZE, s.ALLOCATED_SIZE
FROM INFORMATION_SCHEMA.INNODB_TABLES AS t
LEFT JOIN INFORMATION_SCHEMA.INNODB_TABLESPACES AS s ON s.SPACE=t.SPACE
WHERE t.NAME='your_schema/your_table';

这里使用 LEFT JOIN 保留表本身的信息;空间视图没有对应文件测量时,不能把 NULL 当成零占用。系统表空间等类型还应结合对应管理视图和实例配置检查。后文的按名称查询只针对刚建成的独立实验表,不是一条适用于所有空间类型的万能查询。

下面用专用实验库准备一个小表。要求这次建表时 innodb_file_per_table=ON,且 innodb_optimize_fulltext_only=OFF;若不满足,先停止并由实验环境管理者确认,不在业务服务器顺手改全局参数。

CREATE DATABASE IF NOT EXISTS space39_lab
  CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE space39_lab;

CREATE TABLE event_blob (
    id BIGINT UNSIGNED NOT NULL,
    payload MEDIUMBLOB NOT NULL,
    PRIMARY KEY (id)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;

INSERT INTO event_blob (id,payload)
WITH RECURSIVE seq AS (
    SELECT 1 AS n
    UNION ALL
    SELECT n+1 FROM seq WHERE n<400
)
SELECT n, REPEAT('x',8192) FROM seq;

SELECT COUNT(*) AS rows_before, SUM(OCTET_LENGTH(payload)) AS payload_bytes
FROM event_blob;

应有400条记录。payload 的逻辑字节总量是一个可复算值,不等于表空间文件大小:索引页、页内开销、空闲页及文件分配都会影响后者。不能把 SUM(OCTET_LENGTH(...)) 直接作为“数据库浪费空间”的基准。

在相同对象上区分三种测量

有管理视图读取权限时,记录这份表空间元数据:

SELECT NAME, SPACE_TYPE, FILE_SIZE, ALLOCATED_SIZE
FROM INFORMATION_SCHEMA.INNODB_TABLESPACES
WHERE NAME='space39_lab/event_blob';

SELECT TABLE_NAME,TABLE_ROWS,DATA_LENGTH,INDEX_LENGTH,DATA_FREE
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA='space39_lab' AND TABLE_NAME='event_blob';

第一条用于核对真实表空间类型及文件分配,不凭全局开关推断。第二条可以帮助观察表规模,但 InnoDB 的行数与大小统计有估算性质,DATA_FREE 也不是一张可以原样兑换成操作系统可回收字节的账单。

FILE_SIZE 与 ALLOCATED_SIZE 也不要混成同一数字,稀疏文件或相关存储特性可能让文件逻辑大小和实际分配不同。若还要在操作系统侧比较,应明确是观察文件长度,还是实际占用的数据块,并保证查的是同一个实例、同一份表空间文件。

现在只删实验表里的前360条:

DELETE FROM event_blob WHERE id<=360;
SELECT COUNT(*) AS remaining_rows FROM event_blob;
SELECT NAME,SPACE_TYPE,FILE_SIZE,ALLOCATED_SIZE
FROM INFORMATION_SCHEMA.INNODB_TABLESPACES
WHERE NAME='space39_lab/event_blob';

逻辑记录应剩40条,文件不必随这次提交立即缩小。InnoDB 还要处理多版本与后续清理,已经可复用的页也可以留在表空间里供将来写入。若表很快又会增长,把这些空间继续用掉,可能比反复重建再扩容更符合需求。

这里删除的是已提交的小样本。真实系统若有长事务持有旧读视图,旧版本清理还会受到影响;不能把重建表当作处理一切历史版本滞留的通用按钮。先核对事务和空间状态,再决定是否存在必须向操作系统归还容量的实际压力。

OPTIMIZE 的输出要按 InnoDB 路径理解

维护应使用没有挂着业务事务的独立连接。OPTIMIZE TABLE 会造成隐式提交,不能把前面的 DELETE 放进一个未提交事务,再期待维护后一起 ROLLBACK。示例删除应先确认提交,再进入整理步骤。

只对上述实验表执行:

OPTIMIZE TABLE space39_lab.event_blob;
SELECT COUNT(*) AS remaining_rows FROM space39_lab.event_blob;
SELECT NAME,SPACE_TYPE,FILE_SIZE,ALLOCATED_SIZE
FROM INFORMATION_SCHEMA.INNODB_TABLESPACES
WHERE NAME='space39_lab/event_blob';

InnoDB 常见的输出会说明不支持原样 OPTIMIZE,转而执行重建与分析。不能只截取 note 里的“不支持”三个字就认定维护失败,也不能无视后面的最终状态。表仍应有40条记录,实际文件变化需要与前面保存的同一组测量比较。

在独立表空间下,重建可以整理有效数据,并使不再需要的原独立文件空间得到释放。但具体回收多少不是按删除比例线性计算:页分布、保留索引、行外数据与存储分配都会影响结果。八成记录被删,不等于文件必定缩小八成。

如果表位于系统表空间或通用共享表空间,腾出的空间通常仍留给那个表空间内复用,不能期待对单张表执行 OPTIMIZE 就把共享文件尾部按相同比例截短。此时目标若是减少操作系统占用,就要评估迁移或重建相关表空间的完整方案,而非继续对同一张表重复执行。

通用表空间里还有其他表时,某张表搬出也不意味着整个文件可以直接删除。数据库的数据字典仍然管理它的归属和生命周期。不要绕过 DDL 去操作系统删除 ibd 文件,文件名看起来空闲与数据库已经允许移除,是两回事。

能整理,不等于现在值得整理

重建会消耗磁盘、I/O 与日志资源,并在相关阶段取得元数据锁。许多普通 InnoDB 场景可以使用在线重建路径,但“在线”不能理解为从开始到结束绝不等待锁,也不保证业务延迟毫无变化。表上存在长事务时,维护开始或收尾仍可能被阻塞。

空间不足时尤其不能把 OPTIMIZE 当作马上释放空间的急救动作。构造新布局本身需要工作空间,过程中的日志及并发写入也可能继续增长。实际需求应结合当前表空间、索引和具体执行算法评估,不能用一条固定“准备两倍空间”的经验句替代现场计算。

复制环境也要核对维护如何传播与执行。主库上一次操作看起来完成,并不等于所有副本都已经完成,也不能只看磁盘变化就忽略落后和业务查询影响。本文的小表演练没有覆盖复制链路,不把它包装成线上容量验收。

如果目标只是纠正优化器统计,应该先评估 ANALYZE TABLE 等对应操作,而不是为了更新统计顺带要求整表整理。若目标是稳定保存历史数据,归档与保留策略又是另一项工作。明确想得到什么结果,才能避免把多个维护目的都压到 OPTIMIZE 这个名字上。

本次实验最后保留两份独立证据:精确 COUNT(*) 说明数据还剩多少,表空间与文件测量说明容量怎样变化。前者正确、后者没缩小时,先回到空间归属和复用机制,别把一次已经生效的 DELETE 再执行一遍。

正文到此结束
评论插件初始化中...
Loading...