
网上一搜“KVM qcow2 raw 选哪个”出来的答案十个里有八个是“无脑 raw性能吊打 qcow2”还有两个是“我全都要”。我玩了这么多年虚拟化负责任地说这个结论既不全面也容易误导人。qcow2 和 raw 之争本质是“功能丰富度”和“极致简单性”之间的取舍没有绝对的好坏只有适不适合当前场景。这篇文章我不打算和稀泥直接把两种格式从底层结构、性能表现、空间占用、快照机制到转换实操全拆开讲一遍把网上那些没讲透的东西一次说清楚。这篇内容适合谁刚入门 KVM 的运维同学可以用它建立正确的选型框架正在纠结“要不要把线上 raw 盘转成 qcow2”的朋友可以直接跳到第 5 节看迁移思路手里已经跑着 qcow2 虚拟机但觉得磁盘变慢的人第 6 节的问题排查清单应该能帮上忙。我会把每一个结论背后的“为什么”也讲出来这样你自己遇到新情况时也能推导出合理的判断而不是继续背别人的结论。1. 先把两种格式的设计底细搞清楚1.1 raw一张没有目录的“裸盘”raw 格式是所有虚拟磁盘格式里最朴素的一种。它本质上就是一个普通文件文件内容直接对应虚拟磁盘的每一个扇区。虚拟机读哪个 LBA宿主机就从这个文件的对应偏移量读数据中间没有任何映射层也没有任何元数据。你可以把它理解成一张直接铺开的地耕地编号就是扇区号你往哪块地上种种子就直接进哪块地。这种设计带来的最直接优势是访问路径极短。QEMU 在读 raw 文件时不需要查表、不需要判断数据块是否分配、不需要做额外的状态更新直接把文件 I/O 请求转给宿主机文件系统就行。在极端高并发随机读写的场景下这个“没有中间商”的优势会被放大。raw 还有一个容易被忽略的能力稀疏文件sparse file。当你用qemu-img create -f raw disk.img 50G创建一个 50G 的 raw 盘时宿主机上这个文件的实际物理占用并不是 50G而是几乎为 0。文件系统会把这 50G 当成“空洞”只有在虚拟机真正写入数据时对应的块才被真正分配。这在 Linux 各主流文件系统ext4、xfs、btrfs上都原生支持。不过 raw 的缺点同样来自它的“裸”。它没有任何快照能力没有任何压缩能力也没有写时分配机制。你想做快照只能靠底层文件系统快照或 LVM 快照你想给虚拟磁盘加密得自己套一层 LUKS你想节省空间只能靠文件系统的稀疏特性被动回收。1.2 qcow2为快照而生的“账本式”镜像qcow2 的全称是 QEMU Copy On Write version 2注意这个 Copy On Write 不是指和 backing file 之间的 COW而是指它隶属于一种支持写时复制机制的镜像家族。从结构上看qcow2 由以下几部分组成镜像头header记录镜像的版本、大小、cluster 大小、加密信息、特征位等关键元数据。L1 表一个一维指针表每个条目指向一个 L2 表。L2 表二维指针表每个条目指向真正的数据 cluster。refcount 表和数据 cluster引用计数区域负责管理数据块的生命周期数据 cluster 是真正的存储单元。用大白话解释就是qcow2 像一套带台账的土地管理系统。每块地cluster的归属、引用次数、是否被镜像快照占用全都记录在册。你写入数据时系统先在台账里找一块空闲地登记归属再种地如果没有空闲地就先把原来那块地的内容复制一份再修改这就是 COW 的体现。这套台账结构带来三个核心能力。第一是延迟分配你创建一个 50G 的 qcow2 镜像时它只占用宿主机一点点元数据空间真正数据在首次写入时才分配 cluster天然支持瘦分配。第二是快照由于所有 cluster 都带引用计数做快照时只需要拷贝 L1/L2 表和 refcount 信息数据块可以共享因此快照极轻量。第三是压缩qcow2 支持 zlib 压缩可以让稀疏的数据在宿主层进一步瘦身。但代价也显而易见。每一次读写都可能牵涉到元数据操作写数据时可能要更新 L2 表、更新 refcount读数据时可能要查询 L2 表确认 cluster 位置。这些额外操作在随机小 IO 场景下就是实打实的性能损耗。1.3 底层映射对比速查表维度rawqcow2元数据开销无文件即磁盘有 header、L1/L2 表、refcount 等数据分配方式文件系统管理稀疏文件首次写入时分配 cluster快照支持不支持需外部机制原生支持含内部快照与外部快照压缩支持不支持原生支持 zlib 压缩加密支持需外部工具原生支持 LUKS 等加密克隆方式文件复制可配合 reflink原生支持 backing file 和克隆碎片风险低文件系统会尽量连续数据块碎片受分配时机和快照链影响故障恢复数据可直接挂载恢复容易需要 qemu-img check 等工具修复这张表就是后续所有讨论的基础。记住一个核心结论raw 是“简单但功能缺乏”qcow2 是“功能丰富但有代价”。选型就是在这二者之间找平衡。2. 性能跑分之后的真实结论2.1 同样条件下差距到底有多大先说结论顺序 I/O 的场景下qcow2 和 raw 的差距几乎可以忽略。你用 fio 测 1M 块大小的顺序读、顺序写两者差距通常在 5% 以内很多时候甚至出现 qcow2 不输 raw 的情况。因为在顺序大块 I/O 下元数据操作的频率极低数据量大头被拷贝动作吃掉查一次表更新一次引用的开销被摊薄了。真正拉开差距的是随机小 I/O尤其是 4K 随机写。我在同一台宿主机上用同一块 NVMe 盘跑过对比测试guest 内核和 fio 参数完全一致结果显示 raw 4K 随机写可以跑到某个 IOPS 基线而 qcow2 通常会有 20%~30% 的下降最差的场景同时开多个快照且没有 trim下降会更明显。随机读的差距没有写那么大一般在 10% 以内。为什么差距只在随机写如此突出因为 qcow2 每写一个 4K 数据块除了把数据写到目标 cluster还至少要更新一次 L2 表、更新一次 refcount某些情况下还要先分配新 cluster。这意味着一次 guest 写请求在宿主侧变成了多次元数据写这就是写放大。raw 没有这一步写多少就是多少。不过这里有个重要前提以上差距是在 cachewriteback 这类常见模式下测出来的。如果你把虚拟磁盘的 cache 模式设成 none即绕过宿主机 page cache 直写情况会复杂一些但 qcow2 的元数据开销依然存在。2.2 差距从哪里来写放大与 cache 模式很多刚接触 KVM 的朋友搞不清楚 cache 模式对性能的影响。这里把最典型的几种情况讲清楚。cachenoneQEMU 绕过宿主机 page cache结合O_DIRECT直写磁盘。这种模式对 raw 和 qcow2 都最“公平”两者的元数据开销会如实在 IOPS 上体现。数据库等应用常用此模式。cachewritebackQEMU 先把数据写进宿主机 page cache再由内核异步刷盘。这种模式下qcow2 的元数据更新很多会先落在内存里随机写的差距会被部分吸收。桌面虚拟机和大多数测试环境用这种模式。cacheunsafeQEMU 直接忽略持久性相当于把数据丢了也不管。我强烈不建议在生产环境使用唯一的“优点”是跑分特别好看。所以你在跑分时看到 qcow2 比 raw 慢很多先不要急着归咎于格式先检查是不是走了 cachenone 大队列深度 纯随机写。在这种组合下raw 胜出是预期的但这不代表 qcow2 就“不能用”——它只是不适合这个极端场景。2.3 性能优化的几个有效手段如果因为功能原因必须用 qcow2又不想牺牲太多性能下面几个手段实测有效调大 cluster_size。默认 cluster 是 64K对于随机写负载建议提高到 128K 甚至 256K。cluster 越大单个 L2 表能覆盖的地址空间越大查找次数变少但注意如果 cluster 太大小文件场景下的内部碎片会浪费空间。创建命令里用-o cluster_size128K即可。打开 discard 支持。在 libvirt 域定义里给 disk 增加driver discardunmap/guest 内挂载文件系统时使用discard选项或定期运行fstrim。这样 qcow2 能感知到 guest 释放的块把对应 cluster 标记为空闲既回收空间又减少后续分配时的碎片。避免过深的快照链。每个快照都引入一层 COW 关系读一个数据块可能要回溯多层。如果快照只是临时用于备份用完尽早 commit 合并不要一直挂着。把 I/O 队列深度调高。libvirt 配置driver queuesN/N 为 vCPU 数或队列数配合 virtio-blk 多队列能缓解 qcow2 元数据操作对单队列的争用。顺带说一句如果你跑的是高 IOPS 的数据库型负载我更推荐的方案是系统盘用 qcow2 保留快照能力数据盘用 raw可以是独立的 raw 文件或直接一个 zvol、LVM 卷这样功能和性能都能保住。这个“混搭”思路在我的生产实践里反复验证效果很好。3. 空间利用瘦分配与预分配那笔账3.1 raw 的稀疏文件与空间回收raw 文件默认就是稀疏的你创建 100G 的 raw 盘实际占用可能只有几百 K。这对宿主机的空间规划非常友好但也埋了一个隐患当虚拟机内部大量写入、删除数据后raw 文件在宿主机上的“实际占用”并不会自动缩小。因为文件系统只知道文件被写到了哪些块不知道虚拟机内部那些扇区已不被使用。解决办法是让虚拟机的文件系统主动通知宿主机哪些块可以释放也就是 trim 机制。在 Linux guest 里最简单的方式是fstrim -v /执行后文件系统会把空闲块通过 virtio-blk 的 discard 指令下发给宿主机。如果底层是原生 raw 文件QEMU 会调用fallocate(FALLOC_FL_PUNCH_HOLE)把对应区域重新变成空洞raw 文件的实际占用就会下降。不过fstrim是系统级操作对读写繁忙期会有短暂影响建议在低峰期执行。如果你拿到一个已经写满的 raw 盘想瘦身还有更彻底的办法virt-sparsify。它能逐块扫描镜像把所有全零块转换为稀疏空洞。以我处理过的某次迁移为例一个实际占用 80G 的 raw 系统盘经过 virt-sparsify 后物理占用降到了 30G 左右效果非常可观。命令大概是virt-sparsify --in-place vm-disk.raw注意--in-place是原地整理建议先做备份。3.2 qcow2 的预分配策略与压缩qcow2 的空间管理比 raw 精细得多。它支持三种预分配模式off默认只分配元数据所需的最小空间数据 cluster 在首次写入时才分配。这是空间利用率最高的模式但首次写入时会有分配延迟。metadata预分配 L1/L2 表和 refcount 表所需空间但不分配数据 cluster。这个模式适合创建快照链较多的场景可以避免快照时因元数据分配导致的 I/O 卡顿。full一次性把整个虚拟磁盘大小的数据 cluster 全部预分配。可以消除首次写入的分配延迟让 qcow2 的写入性能和 raw 接近但也意味着创建时就会占用完整空间瘦分配的优势荡然无存。创建时指定模式qemu-img create -f qcow2 -o preallocationmetadata vm-disk.qcow2 50G压缩方面qcow2 原生支持 zlib命令是qemu-img convert -c。压缩的效果取决于磁盘内容类型文本、日志、数据库页这类数据压缩率很高已经压缩过的媒体文件则几乎压不动。我的经验是压缩更适合用于归档和冷存储的镜像运行中的虚拟机不建议开压缩因为每次写入都要执行压缩算法CPU 开销不小。3.3 超额分配的风险与容量监控无论是 raw 稀疏文件还是 qcow2 默认模式都属于瘦分配。瘦分配的最大风险是宿主机磁盘空间被多个虚拟机同时消耗最终“账面上有空间、实际上已写满”。某个凌晨宿主机 /var/lib/libvirt/images 所在分区 100% 满了同时有三台虚拟机报 I/O error如果你经历过一次就懂了。所以我建议给每个虚拟机做实际物理占用监控而不是只看虚拟磁盘大小。宿主机上最简单的指令du -h /var/lib/libvirt/images/*.qcow2然后用它和你允许的“逻辑容量上限”对比设定 75% 告警线。另外为关键虚拟机打开 hypervisor 层面的 I/O 限速iotune配置避免某台虚机写爆全局空间。关于超额分配比例没有万能公式。我个人的经验是对普通应用服务器逻辑容量总和控制在物理空间 1.5 倍以内比较稳对回收频率低、写入量大的数据库虚拟机尽量少超卖甚至预留 20%~30% 的 buffer。4. 功能特性快照、克隆与模板分发4.1 qcow2 的快照到底是怎么工作的qcow2 的快照分为内部快照和外部快照。内部快照是把当前状态保存到同一个镜像文件内用 libvirt 命令virsh snapshot-create-as vm-name snap1 --disk-only外部快照则是创建一个新的 qcow2 文件作为当前活动层原文件变成只读的后备层。外部快照背后的核心机制是 backing file 链新文件只记录发生变更的数据块未变更的块通过指针回到旧文件读取。这样做的优点是快照瞬间完成几乎不占额外空间。但快照不是万能的灵药。如果快照链深度越来越大读取性能会逐步下降因为每次读都要从活动层回溯到相应的后备层。更危险的是如果链条中的某个文件损坏整条链后面的所有快照都会受影响。所以实践上我倾向于日常备份用外部快照但定期比如每周把链条合并掉防止链过深。4.2 backing file 让模板分发变得很轻qcow2 另一个经常被低估的能力是模板分发。假设你有 10 台相同配置的虚拟机传统的 raw 做法是把 50G 的原始盘复制 10 次即使底层文件系统支持 reflink也需要额外的管理。qcow2 可以用一个只读的基础镜像 十个差分盘来部署# 创建基础模板 qemu-img create -f qcow2 template.qcow2 50G # 在模板中安装并配置好系统 # 为每台虚拟机创建差分盘 qemu-img create -f qcow2 -b template.qcow2 -F qcow2 vm1-diff.qcow2 qemu-img create -f qcow2 -b template.qcow2 -F qcow2 vm2-diff.qcow2 # 启动时用差分盘作为磁盘 virsh start vm1在这种模式下基础模板的数据被所有虚拟机共享每台虚拟机的差分盘通常只有几十到几百 M取决于变更量。创建 10 台虚拟机只需几秒磁盘空间占用也远比复制 10 份少。Rancher、OpenStack 等平台的镜像分发逻辑本质上就是这套机制。有一点要特别提醒基础模板文件必须保持只读不能有任何虚拟机直接写它否则所有差分盘都会损坏。更新模板时要先关机或停用相关的所有差分盘更新完后再重建差分盘。4.3 raw 如何借助外部机制补齐短板raw 本身没有这些功能但它站在 Linux 生态的肩膀上也能获得近似的体验快照LVM 的 snapshot、btrfs/xfs 的文件系统快照都可以对 raw 文件做快照。缺点是和 hypervisor 联动程度低恢复时操作繁琐。克隆如果底层文件系统支持 reflink如 xfs、btrfs、ocfs2cp --reflink创建副本的代价极低和在 qcow2 上创建差分盘的体验很接近。加密raw 配合 LUKS 文件级加密可以实现全盘加密但配置和使用都比 qcow2 原生加密复杂得多。所以 raw 并不等于“原始人”它的上限取决于你能把宿主机生态的工具链玩得多熟。但如果你只想“省心”qcow2 的功能整合度确实是最高的。5. 格式转换实操与注意事项5.1 基础转换命令与参数选择格式转换的核心工具是qemu-img convert。从 raw 转 qcow2qemu-img convert -p -O qcow2 vm-disk.raw vm-disk.qcow2从 qcow2 转 rawqemu-img convert -p -O raw vm-disk.qcow2 vm-disk.raw这里的-p显示进度-O指定输出格式。如果原始 raw 文件里有很多空洞瘦分配想转成 qcow2 后依然保持瘦分配可以加-S 4k意思是小于 4K 的连续零块不复制按稀疏处理qemu-img convert -p -O qcow2 -S 4k vm-disk.raw vm-disk.qcow2如果需要压缩再加-cqemu-img convert -p -O qcow2 -c vm-disk.raw vm-disk.qcow2转换不是“原地变身”它会生成一个完整的副本。所以你必须保证宿主机有足够空间容纳转换后的镜像。以我的经验转换前先看qemu-img info里的 virtual size 和 disk size然后再准备至少 (virtual size) 的临时空间。如果空间紧张可以考虑分卷转换或者先转一部分再合并但复杂度会上升不如一开始规划好空间。5.2 在线迁移方案与停机窗口很多人最关心的是虚拟机能不关机转换吗答案是可以但有条件。核心思路是利用 qcow2 的外部快照机制把运行中的写入活动层拆开然后对底层文件做转换# 1. 为运行中的虚拟机创建外部快照得到一个新的 qcow2 增量盘 virsh snapshot-create-as vm-name migrate-snap --disk-only --atomic # 2. 此时原始磁盘文件变为只读后备层 # 3. 把原始磁盘文件复制到目标格式 qemu-img convert -p -O qcow2 -S 4k vm-disk.raw vm-disk-new.qcow2 # 4. 完成后把快照合并回新镜像或直接把增量盘 rebase 到新镜像上 virsh blockcommit vm-name migrate-snap --wait --verbose这套流程的优点是停机窗口只发生在最后 commit 的短暂时间通常在几十秒内。缺点是对运维能力要求高操作顺序错了可能导致数据不一致。我建议第一次实施前先在测试环境完整演练一遍确认 blockcommit 的语法和效果符合预期再上生产。如果虚拟机本身就是停机维护状态简单关机后直接转换即可完全不需要快照。转换完成后再修改域定义virsh edit vm-name # 将 source file/path/vm-disk.raw/ 改为 source file/path/vm-disk.qcow2/ virsh start vm-name新手容易忘的一步如果原磁盘的驱动类型配置成了busvirtio等转换后一般不需要改动配置但建议检查 domain XML 中disk的devicedisk和driver type是否与目标镜像匹配。5.3 转换后的完整校验流程转换完成不代表万事大吉我至少会做以下验证用qemu-img info对比源镜像和转换后镜像的 virtual size必须一致。用qemu-img check检查输出镜像的一致性qemu-img check -r all vm-disk.qcow2启动虚拟机进入 guest 后检查分区表和文件系统lsblk sudo fsck -f /dev/vda1 # 视分区情况调整模拟一次读全盘的操作比如dd if/dev/vda of/dev/null bs1M statusprogress确认没有 I/O 错误。这一步最费时间但也最值得做。我经历过一次转换后 guest 网络异常、ping 不通的情况最后发现是在转换过程中使用了过旧的 qemu-img 版本导致新镜像缺少某个特性标志位。升级工具版本后重新转换才恢复正常。所以转换前先确认宿主机 QEMU 版本与你目标格式匹配这也是一个容易被忽略的坑。6. 常见问题与避坑清单6.1 问题速查表问题现象可能原因解决/排查方向qcow2 虚拟机越来越慢碎片化、快照链过深、未开启 trim执行 guest 内fstrim合并快照链必要时离线转换整理镜像实际占用比qemu-img info显示大很多快照残留、未回收 cluster、日志增长qemu-img info查看快照列表blockcommit合并virt-sparsify瘦身raw 盘磁盘满无法扩展分区底层文件系统空间不足或超出了分区表限制扩展镜像大小 guest 内 growpart/resize2fs详见下文虚拟机崩溃后 qcow2 无法启动元数据损坏qemu-img check -r all尝试修复严重时回到最近一次快照多台虚拟机同时使用同一基础镜像导致数据异常backing file 被意外写入立即关闭涉及虚拟机将基础镜像置为只读重新生成差分盘cachenone 模式下 qcow2 随机写性能太差元数据写放大调大 cluster_size或考虑系统盘 qcow2 数据盘 raw 混搭guest 内删了大量文件但宿主空间不释放raw/qcow2 未开启 discard确认driver discardunmap/运行fstrim6.2 raw 盘扩容的完整流程raw 盘的扩容可能是运维同学问得最多的操作。假设原盘是 50G想扩到 100G# 宿主机上扩大镜像文件大小注意未预分配时只需改变逻辑大小 qemu-img resize vm-disk.raw 100G # 如果虚拟机在线可以热插拔重新扫描如果离线重启即可 # guest 内重新识别新容量 sudo gdisk /dev/vda # 或使用 parted/fdisk # 调整分区然后 sudo resize2fs /dev/vda1 # ext4/xfs 用 xfs_growfs整个过程里最容易出错的是分区表类型和文件系统类型判断错误。MBR 分区表和 GPT 分区表的处理方式略有不同GPT 还需要保留备份分区表操作时要小心。6.3 一系列实操心得汇总最后分享几条没有写在任何官方文档里、但实践价值很高的心得。第一库里的模板盘尽量用 qcow2。哪怕你最终交付的虚拟机数据盘是 raw用 qcow2 做模板 差分盘部署带来的便利性远超那点性能损失。部署完成后如果对性能有要求再把差分盘在线迁移到 raw 盘两者可以平滑结合。第二不要迷信 fio 的单点指标。跑 fio 和跑真实应用的差距很大。我在某次评估中测出 qcow2 随机写比 raw 慢 30%但迁回真实业务后整体性能差异连 10% 都不到。原因是真实负载中有大量的顺序成分而且 page cache 吸收了很多写请求。选型前最好用 vmstat、iostat 观察一下实际应用的 I/O 模式问自己一句业务里到底有多少是纯随机写第三qemu-img工具版本对转换结果影响很大。新版工具生成的 qcow2 可能包含旧版不认识的兼容特性跨版本转换前务必备份。我的习惯是固定宿主机 QEMU 版本并在升级后第一时间对现有镜像做一次qemu-img check验证。第四生产环境务必开启对宿主机磁盘空间和虚拟机 I/O 错误的两级告警。瘦分配模式下空间耗尽只是时间问题而不是概率问题。提前设置告警定期查看du -h是避免事故最有效的投资。说到底我自己的默认策略可以总结成一句话默认用 qcow2 管理灵活性遇到高 IO 负载和明确性能瓶颈时把对应数据盘切到 raw并且永远用混搭思维而不是二元对立思维去看待这两个格式。这样既享受了功能又没丢掉性能。最后再给一个小技巧无论最终选哪种格式记得在虚拟机的 virtio-blk 配置里打开 discardunmap选项并且在 guest 内配合 fstrim 做定期回收。这一个动作对空间利用率的提升往往比纠结格式本身还大而且它不挑格式raw 和 qcow2 都能受用。