磁盘分区这件事,说小很小,一条fdisk /dev/sdb进去敲几个字母就完事;说大也很大,敲错一个d加一个w,线上跑着业务的那块盘就能在几秒钟内变成一堆无法挂载的空洞。我在维护 RHEL 9 / RedHat 9.0 这套环境的过程中,处理过太多次"磁盘加了但系统看不见""分区建完挂不上""删错了分区还能不能救"这类问题,绝大多数都不是命令不会用,而是对 fdisk 这套交互式工具的状态机理解不到位——它有一个内存中的分区表副本,你所有的增删改都先落在这个副本上,只有按下w才真正写回磁盘。搞清楚这个前提,增、删、改、查这四个动作才有讨论的基础。这篇内容适合两类人看:刚接手 Linux 服务器、需要独立完成磁盘初始化的新手,以及已经会敲命令但踩过坑、想把流程做扎实的老手。我会把每个动作背后的原理、参数含义、实测中遇到的意外情况都摊开讲,命令可以直接抄,但更重要的是知道为什么这么敲。
1. fdisk 在 RHEL 9 里的真实定位与能力边界
1.1 为什么分区工具选型先看分区表类型
很多人上手就问"用 fdisk 还是 parted",这个问题其实问反了。正确的顺序是:先确认这块盘要采用哪种分区表,再决定用哪个工具。分区表决定了工具的适用边界,而不是反过来。
MBR(dos 分区表)是老架构,最大寻址 2TiB,最多 4 个主分区,超出部分要靠扩展分区里嵌套逻辑分区来凑。GPT没有 2TiB 限制,默认支持 128 个分区,还带 CRC 校验和备份分区表头。
RedHat 9.0 内置的 fdisk 来自 util-linux 2.37.x,这个版本对 GPT 的支持已经相当完整:能建 GPT 标签、能改分区类型 GUID、能显示分区名。所以在新机器上,fdisk 完全够用,不一定非要上 parted。真正的分水岭在于自动化——如果你要在脚本里非交互地分区,fdisk 的-管道输入方式虽然能用,但容错差;这种场景sfdisk或者parted -s更合适。
我自己的习惯是这样:
| 场景 | 首选工具 | 原因 |
|---|---|---|
| 单机手工初始化一块数据盘 | fdisk | 交互直观,改错能q退出不落盘 |
| 批量脚本分区(几十台机器) | sfdisk | 可用sfdisk -d导出/导入,幂等性好 |
| 在线扩容已有分区 | growpart + resize2fs/xfs_growfs | fdisk 做不了原地扩分区 |
| GPT 分区表备份恢复 | sgdisk | 支持整表备份,比手工记录稳 |
用 fdisk 的第一件事不是分区,而是看它默认给你的是什么表。较新的 fdisk 在空盘上执行时会提示Created a new DOS disklabel with disk identifier 0x...,也就是说默认还是 MBR。如果你要 GPT,进交互界面后必须先敲g;要强制回到 MBR 就敲o。这一步漏了,后面在大容量盘上会直接卡在"分区结束扇区超出范围"上,很浪费时间。
注意:
g和o都会重建整张分区表。在一张已经有数据分区的盘上敲这两个字母,等同于把原有分区信息全部抹掉(数据块还在,但找不到入口)。老盘上操作前务必先p看一眼。
1.2 动手前必须搞清的三件事:设备名、容量、占用状态
分区出事故,一大半原因是对"我在操作哪块盘"这件事没做二次确认。RHEL 9 的设备命名规则看起来简单,实际有几个容易混淆的点。
/dev/sda通常是系统盘,/dev/sdb、/dev/sdc是后加的盘,但这个顺序不是绝对的。机器重启、换 HBA 卡、调整 BIOS 启动顺序之后,盘符可能漂移。所以我每次动盘之前,一定跑一遍组合命令确认:
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,MODEL,SERIAL这个输出比fdisk -l直观得多:TYPE 列能看出谁是 disk、谁是 part、谁是 lvm;MOUNTPOINT 直接告诉你哪些分区正在被使用;SERIAL 能帮你把/dev/sdb和机箱里那块物理盘的序列号对上。在有多块同容量盘的机器上,靠容量区分盘是极其危险的,一定要看序列号或 WWN。
第二件事是确认这块盘没有被挂载、没有被 LVM 占用、没有被 mdadm 阵列引用。三个检查动作:
findmnt -S /dev/sdb pvs 2>/dev/null | grep sdb cat /proc/mdstat只要任意一条有输出,就别急着分区,先把上层引用拆干净。特别是 LVM 场景,/dev/sdb整个盘做了 PV,此时你在 fdisk 里删掉分区,LVM 元数据会直接对不上,pvs会报unknown device,恢复起来很麻烦。
第三件事是确认内核认到了完整容量。云主机上扩容磁盘后最典型的坑:控制台上把盘从 100G 扩到 200G,机器里lsblk还是 100G。这不是 fdisk 的问题,而是内核没重读块设备大小。这种情况先echo 1 > /sys/class/block/sdb/device/rescan触发重新扫描,再lsblk确认容量变成 200G,然后才能进 fdisk 建新分区。我在早期做云上扩容时被这个卡过整整一个下午,一直以为是 fdisk 分区表有问题,其实磁盘压根还在用旧的大小。
2. 查:把磁盘底牌摸透再下手
2.1 fdisk -l 的输出到底该怎么读
fdisk -l /dev/sdb是"查"的基础动作,但很多人的读法只停留在"有没有分区"这个层面,实际上这段输出里藏着几个关键判断。
Disk /dev/sdb: 200 GiB, 214748364800 bytes, 419430400 sectors Disk model: Virtual Disk Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 4096 bytes Disklabel type: gpt Disk identifier: 8A3F2C1E-... Device Start End Sectors Size Type /dev/sdb1 2048 2099199 2097152 1G Linux filesystem /dev/sdb2 2099200 419430399 417331200 199G Linux filesystem第一行给的是总扇区数,这个数字是你后面算可用空间的基准。注意单位是 sectors,不是字节,2097152 sectors * 512 = 1GiB,很多算错容量的情况都是把扇区当成了 KB。
Sector size 那一行特别值得看:物理扇区 4096 字节、逻辑扇区 512 字节,说明这是一块 4K 物理扇区的盘(现在绝大多数企业级盘和云盘都是)。这意味着分区起点如果没对齐到 4K 边界,一次写操作会触发两次读改写,性能可以掉 20% 以上。fdisk 新版默认起点就是 2048 扇区(即 1MiB),天然对齐,但如果你是手工输入起点,就很容易踩到这个雷。
Disklabel type是判断后续能不能顺利操作的关键。显示gpt说明是老 GPT,显示dos就是 MBR。如果这一行显示gpt但只有主 GPT 头、备份头损坏(常见于盘从别的机器直接拔过来),fdisk 会提示The backup GPT table is not on the end of the disk,并问你是否修复。这种提示不要随手回车确认,先搞清楚盘原来是不是做过容量变更,否则修复动作可能把有效分区信息覆盖掉。
2.2 交互界面里 p 和 x 两个视角的区别
进到fdisk /dev/sdb之后,p打印的是分区表视图,x进专家模式后p打印的是原始参数视图。这两个视图的信息密度完全不同,前者面向"有哪些分区",后者面向"分区表本身健不健康"。
试过几次就知道,排查分区异常时专家模式的信息更值钱:
Expert command (m for help): p Disk /dev/sdb: 200 GiB, ... Device Start End Sectors Size Type /dev/sdb1 2048 2099199 2097152 1G Linux filesystem Expert command (m for help): i Selected partition 1 Device: /dev/sdb1 Start: 2048 End: 2099199 Sectors: 2097152 Type: 0FC63DAF-8483-4772-8E79-3D69D8477DE4 Partition name: Partition UUID: 3B2A...i命令能直接看到分区的类型 GUID 和分区 UUID,这在做 fstab 挂载配置时很有用——虽然我一般还是用blkid,但在分区刚建完、文件系统还没格式化的时候,i是唯一能看到分区 UUID 的地方。
另一个常被忽略的查法是u命令切换显示单位。默认 fdisk 用扇区显示起止位置,敲u可以切成柱面(cylinders)显示。现在基本用不着柱面了,但有一个场景例外:你在跟一些老旧的存储设备打交道,对方给的参数是柱面数,这时候切成柱面来对齐反而方便。日常操作就保持扇区模式,扇区是唯一精确的单位。
还有一点:交互界面上方的提示行会实时告诉你当前磁盘剩余多少扇区可用,比如Last sector, +/-sectors or +/-size{K,M,G,T,P} (2099200-419430399, default 419430399):。括号里的两个数字就是可用的起止扇区范围,这个范围每次增删分区后会重新计算,所以删完分区马上看这一行,就能知道释放出来的空间在哪。
2.3 分区表备份:两分钟换一次后悔的机会
这是我最想强调的一件事。分区表本身只有几 KB,备份成本几乎为零,但恢复价值极高。
MBR 分区表可以直接用 sfdisk 导出:
sfdisk -d /dev/sdb > /root/sdb-partition-$(date +%F).sfdiskGPT 分区表推荐用 sgdisk:
sgdisk --backup=/root/sdb-gpt-$(date +%F).bin /dev/sdb备份文件内容是可读的文本(sfdisk)或二进制(sgdisk),恢复时:
sfdisk /dev/sdb < /root/sdb-partition-2025-01-15.sfdisk sgdisk --load-backup=/root/sdb-gpt-2025-01-15.bin /dev/sdb我实际遇到过一次:某台机器上手工重建分区,手快把第一分区的起始扇区少敲了个 0,mkfs一执行,原数据入口彻底错位。当时如果有分区表备份,sfdisk一导回去、重新挂载就完事;没有备份就只能上数据恢复工具扫,耗时好几个小时。所以现在我的习惯是:任何一张盘动分区之前,先做备份,再p确认一遍,然后才开始敲命令。多花两分钟,省下的可能是半天。
3. 增:新建分区时最容易忽略的对齐与类型问题
3.1 主分区、扩展分区、逻辑分区的取舍逻辑
这套概念只在 MBR 分区表下成立,GPT 下没有扩展分区的说法,128 个分区平权。所以先看Disklabel type,是 gpt 的话这一节可以直接跳过。
MBR 的限制是:4 个分区表项,超出就要用扩展分区。扩展分区不是真正的数据容器,它只是一块"占位声明",里面可以再挂逻辑分区(/dev/sdb5起编号)。这里有几个实操中容易搞混的点:
- 扩展分区本身不能格式化、不能挂载,它只是个壳。
- 逻辑分区编号永远从 5 开始,即使你只建了一个。
- 一旦建了扩展分区,主分区数量就被锁死在"已用主分区数 + 1 个扩展分区 ≤ 4"。
- 删掉扩展分区,里面的逻辑分区会一起消失。
我在实际项目里的建议是:能用 GPT 就用 GPT,不要在新盘上选 MBR。MBR 唯一还值得保留的场景是需要在极老的系统或引导环境下兼容,RHEL 9 本身完全支持 GPT 启动。分区表用g重建一次,后面建分区不用再考虑"还剩几个表项"这种低级问题。
新建分区的交互流程是这样的:
Command (m for help): n Partition number (1-128, default 1): 1 First sector (2048-419430399, default 2048): Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-419430399, default 419430399): +50GFirst sector直接回车走默认 2048,这是对齐的,不要改。Last sector用+50G这种带单位的写法最省事,fdisk 会自动换算成扇区,并且自动处理结尾对齐。如果你手动输入一个具体扇区号,就有可能落在非 4K 边界上,虽然现代文件系统对这种轻微不对齐的容忍度比过去高,但没必要给自己找麻烦。
提示:输入
+50G后 fdisk 显示的结束扇区可能不是整数换算的结果,这是正常的——它会把结束位置向 4K 边界上取整,所以实际分区容量略大于 50GiB,属预期行为。
3.2 分区对齐的实际影响与验证方法
4K 对齐这个事,我在一次数据库盘初始化时真切体会到它的分量。当时一块盘上建了两个分区,一个手工输了起点、一个用默认值,用fio做 4K 随机写对比,手工那个的 IOPS 比默认那个低了约 18%。这个差距在普通文件存储上不明显,但在高并发小 IO 场景下就是实打实的性能损失。
验证是否对齐,最简单的办法是看起始扇区是否能被 8 整除(512 字节扇区 × 8 = 4096 字节)。2048 显然可以。如果你已经建完分区想复查:
fdisk -l /dev/sdb # 看 Start 列 # 或用更精确的方式 blockdev --getalignoff /dev/sdb1 # 输出 0 表示对齐blockdev --getalignoff返回 0 就是对好齐了,返回非 0 说明分区起点相对物理扇区有偏移。这个命令在排查"为什么这块盘写入比预期慢"的时候特别好用,比盲猜强。
另外还有一个容易忽略的点:分区表建完之后内核不一定立即感知。fdisk 里按w会提示The kernel still uses the old table. The new table will be used at the next reboot,或者更常见的Calling ioctl() to re-read partition table。前者说明内核没重读成功。
处理方式:
partprobe /dev/sdb如果partprobe报Device or resource busy,说明这块盘上有分区正在被使用(哪怕只是被 LVM 声明了)。这时候用partx单独加载新分区更稳:
partx -a /dev/sdb # 或者指定分区号 partx -a -n 1 /dev/sdb我踩过的一个具体场景:在虚机上给/dev/sdb加了第三个分区,w之后lsblk死活看不到sdb3,lsblk -f也没有。反复partprobe无效,最后发现是之前的sdb1挂载着、sdb2组了 LVM,整块设备处于"部分占用"状态。用partx -a定向加载新分区,一次就出来了。所以遇到内核不认新分区,别急着重启机器,partx往往是更优解。
3.3 从分区到可用:格式化与挂载不可跳过的那几步
分区建出来只是划了块地,要用还得格式化。RHEL 9 的默认文件系统是 XFS,命令如下:
mkfs.xfs /dev/sdb1有一个极其重要的确认点:mkfs是不可逆的,它会直接把分区前几百兆的内容覆盖掉。所以执行之前一定用lsblk -f确认这个分区上没有已有文件系统,fdisk -l确认分区号对得上。我见过不止一次在/dev/sdb1上误操作,实际想格式化的是/dev/sdc1——这种事故,一秒钟就能造成完整的数据损失。
XFS 还有几个值得注意的细节。第一,XFS不支持缩容,只能扩不能缩,所以如果对容量规划不确定,宁可先建小一点再扩。第二,XFS 扩容必须挂载状态下做:
mount /dev/sdb1 /data xfs_growfs /data第三,如果你需要缩容能力,就得选 ext4,resize2fs两个方向都支持,但 ext4 在大文件和高并发元数据操作上不如 XFS。这个选型要结合业务类型做,不是单纯的"哪个新用哪个"。
分区名有了,格式化做完了,接下来是 fstab。RHEL 9 上强烈建议用 UUID 而不是设备名:
blkid /dev/sdb1 # /dev/sdb1: UUID="a1b2c3d4-..." BLOCK_SIZE="512" TYPE="xfs" PARTUUID="..."然后写入/etc/fstab:
UUID=a1b2c3d4-... /data xfs defaults 0 0用设备名的问题是盘符可能漂移,重启后挂错分区甚至挂不上,直接进 emergency mode。用 UUID 就稳定得多。不过 UUID 也有它的代价——当你在 fdisk 里删掉分区再重建,即使起始扇区完全一致,新分区的 UUID 也会变,fstab 立刻失效。这一点我在第 6 节会详细展开,这是删分区操作后最常见的一连串问题。
写完 fstab 千万别忘了验证:
mount -a findmnt /datamount -a不报错才算配置正确。我习惯再加一个systemctl daemon-reload,虽然 fstab 变更不强制要求,但配合其他单元操作时能避免一些奇怪的状态残留。
4. 改:分区类型、标志位与"改大小"的真相
4.1 用 t 修改分区类型的完整流程
fdisk 里的"改",最常用的是t改分区类型码。这个操作只改分区表里的类型标记,不动任何数据,所以是安全的。
MBR 下常见的类型码:
| 类型码 | 含义 | 典型用途 |
|---|---|---|
| 83 | Linux | 普通 Linux 文件系统 |
| 82 | Linux swap | 交换分区 |
| 8e | Linux LVM | LVM 物理卷 |
| fd | Linux raid autodetect | 软 RAID 成员盘 |
| 07 | NTFS/exFAT | 与 Windows 共存 |
GPT 下按t会列出几十种类型让你选编号,常用的有Linux filesystem(20)、Linux swap(19)、Linux LVM(31)、Linux RAID(29)。操作流程:
Command (m for help): t Partition number (1-128, default 1): 2 Partition type or alias (type L to list all): 31 Changed type of partition 'Linux filesystem' to 'Linux LVM'.为什么要在意这个类型码?因为它是给上层工具看的标记。把分区标成Linux LVM并不会自动把它变成 PV,你还是得跑pvcreate;但反过来,如果你不标记就直接pvcreate,LVM 元数据会写进去,功能上没问题,只是后续运维看到类型是 83 会犯迷糊。更重要的是软 RAID:mdadm 在组装阵列时会检查分区的类型标记,类型对不上可能导致自动组装失败。
所以我的原则是:类型码和实际用途保持一致,纯属规范问题,但规范的东西在出故障时能省很多沟通成本。改完记得w落盘,q退出不保存。
4.2 分区大小为什么不能在 fdisk 里原地改
这是最常被误解的一点。很多人以为 fdisk 有"调整分区大小"的功能,实际上没有——fdisk 的d+n组合看起来像是"改大小",本质上是删掉分区表项再重建一个,改的只是分区表里的起止扇区记录。
这里有一个必须讲清楚的概念区分:
- 分区表记录的是"这块区域从哪个扇区到哪个扇区,属于哪个分区"。它不关心里面存了什么。
- 文件系统在自己管辖的区域内维护超级块、inode 表、数据块位图。
所以当你用d删掉分区、再用n以完全相同的起始扇区、更大的结束扇区重建时,文件系统的头部数据还在原位,上层看起来就是"分区变大了"。这时候再跑xfs_growfs或resize2fs,文件系统才能感知到新增空间并扩展自己的边界。
这个流程的关键约束有三个:
- 起始扇区必须一模一样。差一个扇区,文件系统超级块的位置就错了,
mount直接报wrong fs type或bad magic number。 - 不能跨越其他分区的位置。如果目标分区后面紧跟着另一个分区,中间没有空隙,就无法向后扩展,必须先处理后面那个分区。
- XFS 只能在挂载状态下扩,
xfs_growfs的参数是挂载点,不是设备名。
完整流程示例(把/dev/sdb1从 50G 扩到 100G,前提是后面有空间):
# 1. 备份分区表 sfdisk -d /dev/sdb > /root/sdb-before-expand.sfdisk # 2. 进 fdisk fdisk /dev/sdb # 记录下来 p 输出里 sdb1 的 Start 值,比如 2048 # 输入 d,选 1,删除 # 输入 n,选 1,First sector 手工输入 2048(必须一致) # Last sector 输入 +100G # 注意:会出现 "Partition #1 contains a xfs signature" 提示,回答 N(不删除签名) # 输入 w 保存 # 3. 重读分区表 partx -u /dev/sdb # 4. 扩文件系统 xfs_growfs /data第 2 步里那个xfs signature的提示非常关键。fdisk 检测到原位置有文件系统签名,会问你Do you want to remove the signature? [Y]es/[N]o:,必须回答 N。如果手快选了 Y,签名被抹掉,mount就会失败,只能靠xfs_repair -n探查或者从备份恢复。这个问题我在带新人时反复强调过,因为提示语本身看起来像是在问"要不要清理干净",很容易条件反射选 Y。
partx -u /dev/sdb里的-u是 update,只更新已存在分区的边界信息,比partprobe更温和,不会因为设备被占用而失败。这个细节在处理线上盘的扩容时特别有用——你不希望为了扩个分区去卸载文件系统。
4.3 用 growpart 替代手工重建的适用场景
如果你的场景是"云盘扩容后想扩展最后一个分区",其实有更省事的办法,不必手工删了重建。cloud-utils-growpart这个包提供的growpart命令能自动完成"改分区表结束扇区"的动作:
dnf install -y cloud-utils-growpart growpart /dev/sdb 1 xfs_growfs /datagrowpart的聪明之处在于它只改分区表,不碰数据区,并且自己处理了起始扇区保持和边界计算。它适用于 GPT 和 MBR,也支持 LVM PV 之上的扩展(扩完还得pvresize)。
不过它有两个限制要知道:只能扩最后一个分区(因为后面的空间必须连续),且无法处理中间有空洞的情况。所以如果你的盘上分区布局比较乱,还是得回到 fdisk 手工处理。我在实际运维里的判断标准是:布局规整、扩最后一个分区,用growpart;布局复杂或者需要重排,老老实实 fdisk 加分区表备份。
5. 删:把破坏性操作做成可控流程
5.1 删除前的四步检查清单
d是 fdisk 里唯一一个真正具有破坏性的动作(g和o更狠)。执行之前,我固定跑这套检查,一次都不省:
第一步,确认挂载状态。findmnt -S /dev/sdb1或lsof /data。分区正在被挂载使用,虽然 MBR/GPT 层面允许删分区表项,但内核还持有旧的块设备映射,后续会出现"分区没了但文件还在被访问"的诡异状态。
第二步,确认上层引用。分区是不是 LVM PV?是不是 mdadm 成员?是不是被容器挂载了?分别用pvs、cat /proc/mdstat、findmnt -R /data检查。
第三步,确认这块盘上没有业务数据在跑。这一步靠人,不靠命令。我一般的做法是看一眼挂载点里有什么,du -sh /data/*扫一下,确认没有正在写入的数据库或者日志目录。
第四步,备份分区表。前面讲过,再说一遍,因为它真的有用。
四步走完再进 fdisk 敲d。删除时的交互是这样的:
Command (m for help): d Partition number (1-128, default 2): 2 Partition 2 has been deleted.到这里分区还没有真正消失,只是从内存分区表副本里移除了。这是 fdisk 给的最后一道防线。
5.2 w 与 q:那几秒钟的决策窗口
fdisk 的设计里有一个非常好的安全机制:只有w才写入磁盘,q直接丢弃所有改动退出。
这意味着你在删完分区、甚至改完大小之后,只要还没按w,一切都来得及。按q退出,磁盘分区表原封不动。
我在实际处理中会刻意利用这个窗口。比如布局比较复杂的盘,先连续做几组d+n,然后用p检查整体布局是否符合预期,确认无误再w。有一次在盘上要重排三个分区,我在内存里反复调整了四五轮,全部通过p确认之后才落盘,一次成功。
s也是类似的思路。如果你只是在探索性操作("看看这块盘还能不能塞下 200G"),那就建个分区看看,然后q退出来,磁盘状态没有任何变化。
反过来说,一旦按了w,就别想用 fdisk 撤销了。fdisk 没有 undo。这时候能救你的只有分区表备份,或者testdisk这类工具扫描原分区的超级块特征来重建。
注意:即使
d之后按q退出,如果你之前已经w过一次(中途保存),那部分改动是已经落盘的,q只能撤销本次会话中未保存的部分。所以正确做法是一次性在内存里做完所有操作,最后统一w。
5.3 删错了之后还能救回什么
先说结论:分区表被删、w已落盘、但还没格式化的情况下,数据大概率还在,可以救。
原因是删分区只改了分区表里那 16 字节(MBR)或者 128 字节(GPT 单项)的记录,数据区和文件系统元数据都没动。恢复的思路是找到原来的起始扇区,把分区表项重建回来。
GPT 分区表恢复最简单:
sgdisk --load-backup=/root/sdb-gpt-backup.bin /dev/sdb partprobe /dev/sdb或者用gdisk的r恢复菜单(gdisk /dev/sdb进recovery & transformation菜单,选b从备份 GPT 头恢复)。GPT 在磁盘尾部有一份完整备份,所以只要尾部没被覆盖,恢复成功率很高。
MBR 分区表没有尾部备份,只能靠扫描:
dnf install -y testdisk testdisk /dev/sdb # 选 [Analyse] -> [Quick Search],它会扫描文件系统超级块特征 # 找到候选分区后可以让它写回分区表testdisk的Quick Search对 XFS 和 ext4 识别率不错,因为它找的是文件系统真正的超级块位置,而不是依赖分区表。扫描时间跟盘容量有关,1TB 盘大概几分钟。
还有一个更轻量的手工判断方式:xfs_repair -n /dev/sdb不加分区号,直接对整块盘做只读检查,有时会直接报出"在某扇区发现 XFS 超级块",这个扇区号就是原来的分区起点。这个技巧我在一次没有备份的恢复中用过,比跑 testdisk 快得多。
但必须说清楚:如果删分区之后又跑了mkfs,原来的数据就真的没了。mkfs会在分区起始位置写入新的文件系统元数据,覆盖掉旧的超级块。这时候只能上专业数据恢复,且成功率不确定。所以完整的防线是"分区表备份 + 确认数据不要了再格式化",两道防线缺一不可。
6. 踩坑实录:几个让操作者后背发凉的现场
6.1 删除重建后 fstab 挂载失败:UUID 变了
这是我遇到过频次最高的一类问题,而且是连锁反应。
现象:一台机器给数据盘扩容,用 fdisk 删了/dev/sdb1、以相同起始扇区重建、扩到更大容量。分区表、lsblk、xfs_growfs全部正常,/data也能正常读写。但重启之后机器卡在 emergency mode,日志显示Failed to mount /data。
原因:fdisk 重建分区时会生成新的分区 UUID 和文件系统 UUID。如果你在重建过程中选择了"N"保留签名,文件系统 UUID 会保留;但分区 UUID(PARTUUID)一定变了。更常见的情况是很多人直接选了默认,mkfs一遍——那文件系统 UUID 就必然变了。
排查流程:
# 1. 从 emergency mode 里可以看到设备存在 lsblk -f # 2. 对比 fstab 里的 UUID 和实际 UUID grep data /etc/fstab blkid /dev/sdb1两个 UUID 不一致,就是这么简单。修复:
# 方法一:改 fstab 用新 UUID # 方法二:把文件系统 UUID 改回原值(XFS 支持) xfs_admin -U old-uuid-here /dev/sdb1 # ext4 用 tune2fs tune2fs -U old-uuid-here /dev/sdb1xfs_admin -U需要在分区未挂载状态下执行。改回原 UUID 的好处是所有机器上的 fstab、监控、备份配置都不用动,特别是集群里几十台机器的场景,改一台的 fstab 容易,改全部就得靠自动化了。
从这以后我养成一个习惯:任何会改变 UUID 的分区操作之前,先把原 UUID 记录下来。一条blkid >> /root/disk-uuid-backup.txt就能省掉无数麻烦。
6.2 分区表没重读:mkfs 写到了错误的设备
这个事故听起来离谱,但确实有现实土壤。
场景:在 fdisk 里删掉sdb2、新建了sdb2(扩大),按了w,但内核没重读(提示Re-reading the partition table failed)。操作者没注意这条提示,直接mkfs.xfs /dev/sdb2。此时内核眼中sdb2的边界还是旧的,mkfs按旧边界写入,新的分区表记录和实际文件系统布局对不上。重启之后分区表按新的生效,文件系统头部位置错位,mount报bad magic number。
这类问题的根因是内核视图和磁盘上的分区表不同步。所以我的流程里固定加了一步验证:
# w 之后 partprobe /dev/sdb 2>&1 lsblk /dev/sdb # 必须能看到新分区且容量正确 cat /proc/partitions | grep sdb # 内核层面的确认partprobe成功时通常无输出,失败了会有明确报错。关键是要看输出,而不是敲完就往下走。如果partprobe因为设备占用失败,用partx -u /dev/sdb(更新)或partx -a /dev/sdb(添加)。两个都失败,说明有进程持有着设备,lsof /dev/sdb或fuser -m /dev/sdb找出来。
还有一个更隐蔽的变体:分区表重读成功了,但文件系统缓存还是旧的。扩容场景下,xfs_growfs之前一定要确认lsblk显示的分区容量已经是新值。如果lsblk还是旧容量就急急忙忙跑xfs_growfs,它会报data size unchanged或者干脆扩到一个错误的大小。
6.3 用整盘做 LVM 之后又想分区
这个坑的典型表现:盘上lsblk显示/dev/sdb没有分区,但pvs显示它是个 PV,vgs能看到卷组。这时候想在上面建分区,fdisk 会提示Device contains a LVM2_member signature。
原因很简单:整盘做 PV 时,LVM 元数据写在磁盘起始位置(前 1MB 左右),没有额外建立分区。这时候正确的做法不是在 fdisk 里删除签名重新分区(那样 LVM 数据就废了),而是:
- 如果只是想用掉扩容后的新空间:
pvresize /dev/sdb,然后lvextend扩逻辑卷。 - 如果确实要改成分区布局:先
vgreduce把 PV 移出卷组,pvremove清除元数据,再 fdisk 分区。这操作要求卷组里没有依赖这个 PV 的逻辑卷,或者先迁移数据。
pvremove之后 fdisk 还会提示签名残留,用wipefs -a /dev/sdb清一次是最干净的。wipefs -n可以先预览会清掉什么签名,这一点很实用:
wipefs -n /dev/sdb # 只列出,不实际清除 wipefs -a /dev/sdb # 真清除我个人的偏好是:整盘做 LVM 而不是分区做 LVM。原因是整盘直接用,扩容量的时候pvresize一条命令搞定,不用处理分区表;分区做 LVM 的话,扩容量要经历"改分区表 → partprobe → pvresize"三步,多一个环节多一份风险。当然这个选择也和规范有关,有些团队的标准化要求必须分区,那就按规范来,但要清楚多出来的那些步骤。
最后分享一个我在实际操作中的小体会。fdisk 这套交互命令本身并不难,难的是操作之前的判断:这块盘是什么分区表、有没有被占用、内核有没有认全容量、要不要留对齐、UUID 会不会变。我现在养成一个固定动作——进 fdisk 之前先在纸上(或者终端里)写下三个值:设备名、分区表类型、原分区起始扇区。这三个值一写,后面的所有操作就有了锚点,敲错命令的概率会明显下降。至于那些"按错一个键毁掉一块盘"的传说,绝大多数时候不是手快,而是没在动手前把这些基础信息确认清楚。