
数据库空间不足时,如何定位占用最多的表和文件
数据库所在磁盘告急时,先别急着删表、清日志或执行压缩。磁盘使用量、数据库文件大小、表中有效数据量,是三个不同的指标:它们不一定同步增长,也不一定能通过同一种命令看全。
稳妥的排查方式是从外向内逐层核对:先找出哪个文件系统紧张,再确认该文件系统中哪些目录和文件占空间,最后用数据库提供的统计信息定位表、索引、日志或临时文件。本文介绍 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 与运行状态,并按应用的维护方案操作。
六、把数据库统计和文件系统结果对起来
拿到“最大表”列表后,还要回答两个问题:这些对象的文件是否位于告急的挂载点上?它们的统计大小能否解释文件系统的使用量?可按以下顺序核对:
- 用
df确认真正告急的挂载点,以及是字节空间还是 inode。 - 用
du在该挂载点内按目录逐级收敛,不跨挂载点扫描。 - 用数据库只读统计查询找出大表、索引和数据库对象。
- 确认数据目录、表空间、日志目录和临时目录是否位于同一挂载点。
- 对照数据库日志、备份、WAL、redo、binlog、临时文件及其他应用目录。
- 若
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差异较大时,检查进程持有的已删除文件;处理方式应以安全释放文件句柄为目标,而不是盲目重启服务。 - 只查数据库目录:表空间、日志、临时目录或备份可能位于别的挂载点。用配置和操作系统信息逐项确认。
九、可复用的排查顺序
空间不足时,可以按下面的流程形成一份可复核的记录:
- 记录主机、实例、时间、告警挂载点,以及
df -hT和df -i的结果。 - 在目标挂载点内运行受范围限制的
du,确定大目录和大文件。 - 通过数据库查询列出最大的表与索引,同时注明查询时间、数据库版本和统计口径。
- 核对数据目录、表空间、日志、临时文件和备份是否位于同一文件系统。
- 解释数据库统计与
du不一致的部分,检查共享表空间、WAL 或二进制日志,以及已删除但仍打开的文件。 - 在充分确认用途后再制定清理、归档、扩容或维护计划;先评估恢复链路和操作影响,不在排查阶段临时删除文件。
核心原则是:数据库查询回答“哪些数据库对象值得关注”,操作系统工具回答“哪个文件系统中的哪些文件正在占空间”。两者结合,才能把表、索引、日志和普通文件区分开。先定位,再判断归属,最后才决定处理方式,比凭一条大小查询直接清理安全得多。









