数据库空间不足时,如何定位占用最多的表和文件

百科问答3小时前发布 jdkjadmin
0 00
数据库空间不足时,如何定位占用最多的表和文件

数据库空间不足时,如何定位占用最多的表和文件

数据库所在磁盘告急时,先别急着删表、清日志或执行压缩。磁盘使用量、数据库文件大小、表中有效数据量,是三个不同的指标:它们不一定同步增长,也不一定能通过同一种命令看全。

稳妥的排查方式是从外向内逐层核对:先找出哪个文件系统紧张,再确认该文件系统中哪些目录和文件占空间,最后用数据库提供的统计信息定位表、索引、日志或临时文件。本文介绍 Linux 常用命令,以及 MySQL、PostgreSQL、SQLite 的只读查询示例,并说明各类统计结果的适用范围。

一、先区分“表占用”与“磁盘占用”

数据库空间问题通常涉及以下几类数据:

  • 表数据:行记录及其存储结构。
  • 索引:为查询、约束等维护的额外结构,索引大小有时与表数据相当,甚至更大。
  • 事务日志:例如 PostgreSQL 的 WAL、MySQL 的 redo log、SQLite 的 WAL 文件。它们不一定归属于某一张表。
  • 临时文件:排序、哈希连接、临时表或维护操作可能产生临时空间。
  • 其他文件:备份、错误日志、二进制日志、容器日志、应用文件,或已经删除但仍被进程打开的文件。

数据库报告的“表大小”不能直接代替操作系统看到的文件系统占用。比如,统计信息可能是估算值;某些表空间包含多张表;日志文件可能正在增长;已删除但仍被进程打开的文件也会继续占用磁盘,却不再出现在目录列表中。

排查时建议先记录检查时间、主机、实例和文件系统挂载点。空间持续变化时,可在短时间间隔后重复采样,避免把业务正在写入的变化误判为异常。

二、先从操作系统定位紧张的文件系统

在 Linux 上,先确认空间紧张的是哪个挂载点,而不是直接对整个根目录执行大范围清理。

df -hT
df -i

df -hT 显示文件系统类型和空间使用情况;df -i 查看 inode 使用情况。若可用空间不足,关注块空间;若 inode 用尽,即使仍有剩余字节空间,也可能无法新建文件。容器环境中还要确认看到的是宿主机、容器还是挂载卷的视图。

确定挂载点后,再查看目录占用。下面的命令把目标目录替换成实际路径:

sudo du -xhd1 /var/lib
sudo du -ahx /var/lib/mysql | sort -h | tail -n 30

-x 表示不跨越到其他文件系统,便于把结果限定在当前挂载点;-d1 在支持该选项的 GNU du 中限制目录深度。不同系统的 du 参数可能不同,可以先运行 du --help 核对。第二条命令按大小排序并列出较大的项目,扫描大型目录会产生 I/O,繁忙生产环境应控制执行频率和范围。

du 统计目录树中可见文件的占用,而 df 统计文件系统已分配空间,两者不一致并不一定意味着数据库统计错误。常见原因包括文件系统元数据、稀疏文件、快照、挂载视图差异,以及文件已删除但仍被进程打开。若有权限,可用下面的命令检查最后一种情况:

sudo lsof +L1

输出中带有已删除标记的文件,若仍被进程持有,相关空间通常要等持有进程关闭文件描述符后才释放。不要因此直接重启或终止数据库进程;先确认进程、文件用途和维护影响。

在数据库主机上,还要找出实例实际使用的数据目录和日志位置。目录名只是线索,不能凭经验假定它们都位于某个固定路径。数据库可能使用独立磁盘、符号链接、表空间或容器卷,最终应以实例配置、启动参数和操作系统挂载信息为准。

三、MySQL:查看表、索引与表空间信息

MySQL 的 information_schema.tables 可用于汇总表大小。下面的查询按数据与索引的统计值之和排序,并以 MiB 显示;将 app_db 换成实际库名。

SELECT
  table_schema,
  table_name,
  engine,
  table_rows,
  ROUND(data_length / 1024 / 1024, 2) AS data_mib,
  ROUND(index_length / 1024 / 1024, 2) AS index_mib,
  ROUND((data_length + index_length) / 1024 / 1024, 2) AS total_mib,
  ROUND(data_free / 1024 / 1024, 2) AS data_free_mib
FROM information_schema.tables
WHERE table_schema = 'app_db'
  AND table_type = 'BASE TABLE'
ORDER BY data_length + index_length DESC
LIMIT 30;

该查询适合快速发现值得进一步检查的大表,但结果不是文件系统精确账单:

  • table_rows 对 InnoDB 通常是估算值,不应拿来当精确行数。
  • data_length、index_length 的含义和精度受存储引擎影响。不要把所有引擎的统计结果都当成物理文件的精确大小。
  • data_free 表示引擎报告的可用或空闲空间,不等于一定能立即归还给操作系统的空间。对共享表空间等情况,它也不能简单解释为某一张表独占的可回收字节数。
  • 分区表可能以分区形式展示。若要逐分区分析,应另行检查 information_schema.partitions,并核对版本和引擎行为。
  • 查询结果只涵盖当前账号可见的对象。权限不足时,列表可能不完整。

若需要查看实例的目录配置,可查询相关系统变量:

SHOW VARIABLES
WHERE Variable_name IN (
  'datadir',
  'innodb_file_per_table',
  'log_bin',
  'log_bin_basename',
  'tmpdir'
);

这有助于确认数据目录、二进制日志配置和临时目录,但不能代替文件系统检查。MySQL 的实际文件布局受版本、存储引擎、表空间配置和实例配置影响。启用独立表空间时,表可能对应独立文件;若使用共享系统表空间,多张表的数据可能位于同一个或一组共享文件中。因此,不能只看某个表的统计数值,就断言删除该表能让同等大小立即从磁盘释放。

如果目录里有较大的二进制日志,不要手动删除日志文件。应先确认复制拓扑、备份需求和保留策略,再按数据库支持的方式管理过期日志;错误清理可能影响复制恢复或时间点恢复。具体保留和清理命令应按当前 MySQL 版本与运维策略执行。

四、PostgreSQL:区分表、索引、TOAST 与 WAL

PostgreSQL 提供了适合空间排查的内置函数。下面的查询汇总普通用户表的总关系大小,并拆分主表、索引和 TOAST 部分:

SELECT
  schemaname,
  relname,
  pg_size_pretty(pg_relation_size(relid)) AS main_fork,
  pg_size_pretty(pg_indexes_size(relid)) AS indexes,
  pg_size_pretty(
    pg_total_relation_size(relid)
    - pg_relation_size(relid)
    - pg_indexes_size(relid)
  ) AS toast_and_other,
  pg_size_pretty(pg_total_relation_size(relid)) AS total
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 30;

pg_relation_size 返回指定关系的大小;pg_indexes_size 汇总相关索引;pg_total_relation_size 包含该关系、索引和 TOAST 数据等所占空间。这里的 main_fork 是关系主数据文件的大小,不等同于 SQL 层能直接看到的“有效载荷字节数”。查询中的 toast_and_other 是差值,主要用于帮助识别非主表、非索引部分;不要把它当成单一文件类型的精确拆分。

若需直接查看数据库目录位置,可以运行:

SHOW data_directory;
SHOW temp_tablespaces;

使用表空间时,数据库对象可能落在数据目录之外。可查看数据库定义的表空间及其位置:

SELECT
  spcname,
  pg_tablespace_location(oid) AS location
FROM pg_tablespace
ORDER BY spcname;

空位置通常表示使用默认位置,不能因此认为该表空间没有占用空间。还要从操作系统检查实际挂载点和目录。统计查询反映数据库关系对象的大小,但文件系统上还可能有 WAL、日志、临时文件、备份或其他实例文件。它们不一定能归到某张用户表名下。

PostgreSQL 的 WAL 目录常在数据目录下的 pg_wal,但目录路径和实际占用应以实例配置及操作系统检查为准。WAL 堆积可能与复制槽、归档或消费者落后等问题有关。不要直接删除 pg_wal 中的文件;这可能导致实例无法正常恢复或启动。先检查复制状态、归档状态与相关配置,再按 PostgreSQL 的管理流程处理。

大表的文件尺寸很大,也不自动意味着执行 DELETE 后操作系统磁盘空间会立即变大。MVCC 机制下,删除产生的空间可能留在关系文件中等待后续复用;是否需要重整、采用何种维护操作,以及是否需要额外磁盘空间,必须结合版本、表状态和业务窗口评估。不能把“删了很多行”直接等同于“释放了同等磁盘空间”。

五、SQLite:数据库文件之外还要看 WAL

SQLite 常见的空间来源包括主数据库文件、预写日志模式下的 -wal 文件,以及共享内存辅助文件。不要只看主数据库文件就判断实例占用。应用运行期间,WAL 可能承载尚未合并到主数据库文件的更改。

在 SQLite shell 中,可以查看数据库页数与空闲页数:

PRAGMA page_size;
PRAGMA page_count;
PRAGMA freelist_count;
PRAGMA journal_mode;

主数据库文件的页占用可用 page_size × page_count 粗略核对;可复用空闲页数则与 freelist_count 相关。它们并不直接等于进程当前对磁盘文件系统占用的完整统计,尤其不能代替对 WAL 文件和其他目录内容的检查。数据库连接必须指向要检查的那个文件;对只读副本或错误路径查询,会得到不相关的结果。

若要在受支持的 SQLite 构建中进一步查看表和索引空间,可使用 dbstat 虚拟表:

SELECT
  name,
  SUM(pgsize) AS bytes
FROM dbstat
GROUP BY name
ORDER BY bytes DESC
LIMIT 30;

dbstat 的可用性取决于 SQLite 构建是否包含相应支持。若出现“不存在该表”之类的错误,先核对当前 SQLite 版本和构建能力;不要把此查询当成所有 SQLite 环境都必然可用的功能。

SQLite 的 VACUUM 可能重建数据库文件并回收空间,但它不是无风险的“清理命令”:可能需要额外磁盘空间,执行时会占用资源,并受连接、事务和部署方式影响。不要在空间已经耗尽的文件系统上贸然执行;也不要在应用仍写入时自行删除 WAL 文件。先备份、核实 journal mode 与运行状态,并按应用的维护方案操作。

六、把数据库统计和文件系统结果对起来

拿到“最大表”列表后,还要回答两个问题:这些对象的文件是否位于告急的挂载点上?它们的统计大小能否解释文件系统的使用量?可按以下顺序核对:

  1. 用 df 确认真正告急的挂载点,以及是字节空间还是 inode。
  2. 用 du 在该挂载点内按目录逐级收敛,不跨挂载点扫描。
  3. 用数据库只读统计查询找出大表、索引和数据库对象。
  4. 确认数据目录、表空间、日志目录和临时目录是否位于同一挂载点。
  5. 对照数据库日志、备份、WAL、redo、binlog、临时文件及其他应用目录。
  6. 若 df 明显高于 du 汇总,检查打开后已删除的文件、挂载视图、快照和文件系统特性。

单位也容易造成误判。本文 SQL 示例中的 MiB 使用 1024 × 1024 字节换算;部分系统工具默认使用十进制单位,或根据显示设置采用不同单位。比较数值前先确认单位。需要精确核对时,应比较字节数,而不是只看格式化后的“GB”。

七、参数、权限与安全边界

上面的 SQL 都是只读查询,但查询结果会受权限影响;只读并不代表一定没有成本。扫描大量元数据、访问系统目录或在繁忙实例上反复执行统计查询,仍可能消耗 CPU、内存或 I/O。生产环境应限制范围、合理设置查询超时,并确认当前数据库版本支持相应函数或视图。

SQL 参数化有一个重要限制:参数通常用于数据值,不能直接代替表名、库名、列名或排序方向等 SQL 标识符。例如,在很多数据库驱动中,下面的写法不会把参数当作表名,而只会把它当作字符串值:

SELECT * FROM ?;

示例中的 app_db 是查询文本中的字面值。若将库名改为外部输入,不要简单拼接字符串。对于值,应使用驱动提供的绑定参数;对于标识符,应使用驱动提供的标识符引用功能,并先以允许列表校验目标库名。参数占位符格式随数据库驱动而异,不能假定一个驱动的写法适用于所有驱动。

操作系统命令也不要把不可信输入直接拼接到 shell 命令中。目录路径应通过安全的参数传递或固定配置获得,并注意正确引用特殊字符。du、lsof 等排查命令通常需要读取相关目录或进程信息的权限;尽量按最小权限执行,确有需要时才使用管理员权限。不要为了查看空间就长期扩大数据库账号权限。

数据库自带的大小统计函数一般用于观察,不会自动整理或清理数据;但“看起来只是一个 SQL”的命令也不能一概而论。本文示例不包含删除、截断、压缩或清理操作。执行任何会改变表、索引、日志或文件的操作前,应确认目标、备份、恢复能力、版本行为、锁影响和可用空间,并先在适合的环境验证。

八、常见错误与处理原则

  • 只按表数据大小排序:大索引可能才是主要占用。MySQL 查询应同时看 data_length 与 index_length;PostgreSQL 应对照主关系、索引和总大小。
  • 把表大小等同于目录文件大小:共享表空间、独立表空间、页复用和文件系统特性都会影响对应关系。继续追查实际文件路径和挂载点。
  • 把估算行数当成精确值:MySQL InnoDB 的行数常为估算值。排查空间无需为了得到精确行数而先执行可能代价很高的全表计数。
  • 看到大日志就手动删除:日志可能承担崩溃恢复、复制或归档职责。确认日志类型、保留要求及数据库状态后,使用相应数据库的管理流程处理。
  • 用删除记录换取即时空间:删除行不保证文件立即缩小。先区分逻辑删除、空间复用与归还给操作系统这三个结果。
  • 忽略已删除但仍打开的文件:df 与 du 差异较大时,检查进程持有的已删除文件;处理方式应以安全释放文件句柄为目标,而不是盲目重启服务。
  • 只查数据库目录:表空间、日志、临时目录或备份可能位于别的挂载点。用配置和操作系统信息逐项确认。

九、可复用的排查顺序

空间不足时,可以按下面的流程形成一份可复核的记录:

  1. 记录主机、实例、时间、告警挂载点,以及 df -hT 和 df -i 的结果。
  2. 在目标挂载点内运行受范围限制的 du,确定大目录和大文件。
  3. 通过数据库查询列出最大的表与索引,同时注明查询时间、数据库版本和统计口径。
  4. 核对数据目录、表空间、日志、临时文件和备份是否位于同一文件系统。
  5. 解释数据库统计与 du 不一致的部分,检查共享表空间、WAL 或二进制日志,以及已删除但仍打开的文件。
  6. 在充分确认用途后再制定清理、归档、扩容或维护计划;先评估恢复链路和操作影响,不在排查阶段临时删除文件。

核心原则是:数据库查询回答“哪些数据库对象值得关注”,操作系统工具回答“哪个文件系统中的哪些文件正在占空间”。两者结合,才能把表、索引、日志和普通文件区分开。先定位,再判断归属,最后才决定处理方式,比凭一条大小查询直接清理安全得多。

© 版权声明

相关文章