☰
dnf update与upgrade的区别:从YUM到DNF的软件包更新全解析
2026/10/21 4:19:53 网站建设 项目流程

写这篇文章的念头,源于我几年前在某台生产服务器上的一次操作失误。当时我执行了dnf update,以为只是打个补丁修修漏洞,结果它顺手把内核和一堆核心库全给升级了,导致一个服务因为动态链接库版本不匹配直接跑不起来。事后复盘,我发现很多人(包括当时的我)根本没搞清楚dnf update和dnf upgrade到底有什么区别,以为它们只是同一个功能的两种写法。今天就把这个老生常谈但又容易被忽视的问题彻底说清楚。

1. 先搞清楚基本盘:这两个命令到底是不是一回事

很多用 Fedora、RHEL、CentOS 等发行版的朋友,应该都看到过网上各种教程里混用这两个命令。有人说“update 是升级软件包,upgrade 是升级系统”,也有人说“update 只是刷新索引,upgrade 才是真正干活”,这些说法其实都不准确。在 DNF 的官方文档和实际行为里,dnf update和dnf upgrade在绝大多数情况下做的事情完全一样——都是把系统里已安装的软件包更新到软件源里可用的最新版本。

那为什么还要区分这两个命令?这要从 DNF 的前身 YUM 说起。YUM 当年有一个历史遗留的设计:yum update会更新所有软件包,同时也承担了更新软件源索引的任务,而yum upgrade在早期版本里还会额外处理一些过时的、被废弃的软件包替换逻辑。到了 DNF 时代,开发者重新梳理了命令体系,把upgrade定位成“更新并妥善处理替代关系”的正式指令,而update则被保留为指向upgrade的别名(alias)。

1.1 从 YUM 到 DNF:一条容易踩坑的历史沿革

我在早期用 CentOS 6 的时候,系统管理员之间流传着一个不成文的规矩:生产环境不要随便跑yum update,要么锁定内核版本,要么用好yum --exclude=kernel* update。原因就是 YUM 时代对依赖关系的处理不够细腻,一个不小心的全量更新可能把生产环境搞挂。DNF 从 YUM 手里接棒之后,重写了依赖解析器,引入了更完善的libsolv库,对事务处理、受保护软件包、弱依赖(weak dependencies)的处理都做了改进。

但有意思的是,DNF 在命名上给update留了“历史地位”。在 DNF 4 及更早版本里,dnf update和dnf upgrade指向同一个内部函数,命令行帮助里明确写了“upgrade is an alias of update”。到了 DNF 5(在 Fedora 41 前后开始全面铺开),官方文档和--help输出里已经把upgrade作为首选术语,update仍然保留,但定位是兼容旧脚本。

所以,第一个可以落地的结论是:如果你在 DNF 4 或 DNF 5 环境里,日常更新系统,dnf update和dnf upgrade在行为上没有任何区别,更不存在“update 是部分更新、upgrade 是全部更新”这种说法。这个误解在国内外论坛上流传极广,希望看到这篇文章的朋友不要再被误导。

1.2 命令别名与使用习惯:为什么两个名字都能见到

既然行为一致,为什么文档里会出现两个命令?除了历史沿革,还有一个原因是不同发行版对文档的编写习惯不同。Fedora 的官方文档、问答社区(如 Ask Fedora)倾向于推荐dnf upgrade,因为这个词更直白地表达“把旧版本替换成新版本”的语义;而很多从 RHEL 8 时代走过来的运维教程,则习惯沿用dnf update的写法。

我觉得比较稳妥的做法是:新脚本、新文档、个人日常操作统一使用dnf upgrade,因为它语义清晰、没有歧义,且在未来的 DNF 5 生态里是官方主推。但如果你接手了一台旧机器,发现 crontab 里或其他自动化脚本里写的是dnf update,不要急着改,完全可以正常工作。等你有机会重构脚本时再顺手统一。

2. 实际操作中的行为差异:缓存、事务与依赖解析的细枝末节

虽然命令功能相同,但在实际操作中,你给它搭配的参数不同,行为结果会有明显差异。很多人踩坑不是因为选错了 update/upgrade,而是因为没有搭配--refresh、--security等参数,或者没有理解 DNF 的缓存机制。

2.1 软件源元数据缓存:如果不刷新,你可能在升级一个过期的“价目表”

DNF 默认会把软件源的元数据(即软件包列表、版本号、依赖关系等)缓存在本地,这个缓存有一个有效期(默认一般是 90 分钟或更久,具体取决于配置)。在缓存有效期内,你执行dnf upgrade不会重新从远程源下载完整的元数据,而是用本地缓存来判断“有哪些可用的更新”。

这个机制的初衷是减少网络请求、加快响应速度,但也会带来一个实际问题:如果你产线服务器已经很久没更新,或者软件源侧在短时间内发布了新版本,而你的本地缓存还没过期,那么即使你执行了dnf upgrade,看到的也不会是最新的软件包列表。

我之前在一家公司遇到过这样的情况:开发环境里明明刚推送了新版本的内核和驱动包,但运维在服务器上执行dnf upgrade始终看不到这些包,排查了半天,最后发现是元数据缓存没刷新。所以,如果你想确保拉到最新版本,建议使用:

sudo dnf upgrade --refresh

--refresh会强制重新同步软件源的元数据,然后再进行升级判断。在更新内核、升级大型组件(比如 Python 运行时、数据库客户端库)之前,我都会习惯性先刷新一下,宁可多等几秒,也不要用过期数据做决策。

除了--refresh,你还可以手动清理缓存:

sudo dnf clean all sudo dnf makecache

clean all会把所有缓存清空,makecache会根据当前配置重新生成元数据缓存。这套组合拳通常用在你怀疑本地缓存文件损坏、或者软件源地址发生变更之后。

2.2 同队列加载机制:为什么同时装多个包是可靠高效的

DNF 的另一个核心特性是“同队列加载”(same-queue loading),也就是在一次事务里同时处理多个软件包的更新。当你执行dnf upgrade时,DNF 会收集所有需要更新的软件包,把它们视作一个整体事务来解析依赖关系。

打个比方,这就像你搬家时找了一家搬家公司把所有家具一次性拉走,而不是自己一件一件扛下楼。DNF 的同队列机制能避免“A 包依赖 B 包的新版本,但 B 包还没更新”这类中间状态问题。在 DNF 的依赖解析逻辑里,一个事务的所有包会被放在同一个“解算空间”里,libsolv会尝试为所有包找到一组同时满足依赖关系的版本组合。

所以,当你在执行dnf upgrade时,如果它告诉你需要同时更新 300 个包,不要慌,这是正常现象——它正在为整个系统的软件包做一次“一致性对齐”。有些新手看到更新列表里出现了无关的软件包,就怀疑是不是命令用错了,其实这是因为这些包之间存在共享依赖关系(比如都依赖同一个库的不同子版本),系统必须让它们保持在同一批状态。

2.3 受保护软件包与事务保护:哪些东西不会被轻易动

DNF 提供了一套受保护机制,主要用来防止内核、系统关键库等被意外删除或替换。体现在命令行上,你经常会看到类似这样的提示:

The downloaded packages were saved in cache till next successful transaction.

或者在某些高危操作时:

Error: The operation would result in removing the following protected packages: kernel-core

RHEL 和 Fedora 系的发行版默认配置了一个受保护包列表,位置一般在/etc/dnf/protected.d/目录下。比如kernel-core、glibc这类极其关键的包,DNF 在解析事务时发现某个操作会导致它们被移除或替换,就会直接拒绝执行或要求你追加--allow-erasing之类的强制参数。

这个保护机制对生产环境来说是好事,但对一些“非要换掉某个系统组件不可”的场景(比如切换内核分支、降级 glibc)就成了障碍。如果你确实需要做这种操作,建议先看清楚受保护列表内容:

cat /etc/dnf/protected.d/*

再根据实际需要决定是否动用强制参数。我个人建议:除非在测试虚拟机里做破坏性实验,否则不要轻易绕过受保护机制。

3. 更新策略与使用场景:到底该用哪个、什么时候用

前面已经说了 update/upgrade 本身是同义的,但在不同场景下,你搭配的参数和命令组合会决定一次更新是“安全可控”还是“随时翻车”。这里我根据自己的运维和开发经验,把常见场景分成三类,分别给出推荐做法。

3.1 生产环境安全更新:用 dnf upgrade 加持 security 和 bugfix 过滤器

生产环境的原则是“稳定压倒一切”,每次更新都要尽量减少变动范围。Fedora 系发行版(尤其在 RHEL 和 CentOS 生态里)的软件源会为更新包打上类型标签,比如安全更新(security)、缺陷修复(bugfix)、增强(enhancement)三类。

如果你想在今天只打安全补丁,不引入其他无关更新,可以这样用:

sudo dnf upgrade --security

只安装有安全公告(如 CVE 修复)的更新。如果还想限制更新的具体范围,可以用:

sudo dnf upgrade --security --bugfix

有些团队还会配合--exclude参数锁定某些特定包的版本,比如:

sudo dnf upgrade --security --exclude=kernel*

这条命令的思路是:只做安全补丁更新,但跳过所有内核相关包。为什么跳过内核?因为内核更新后需要重启才能生效,而且某些私有驱动(比如定制的 GPU 驱动、安全模块)可能与新内核不兼容。锁定内核,然后在维护窗口里单独验证内核更新,是运维团队很常见的做法。

如果你用的是 RHEL 或者基于它的商业发行版,还可以配置 OVAL(Open Vulnerability and Language)源,用dnf updateinfo来查看系统当前有哪些已知漏洞尚未修复。不过 OVAL 相关配置在纯 Fedora 上不是默认可用,需要自己安装插件并指定源。

3.2 日常开发环境升级:直接 upgrade 是最简单高效的路径

如果你用的是 Fedora Workstation 或类似的桌面开发环境,不需要搞那么复杂的安全过滤器,直接全量升级反而更省心:

sudo dnf upgrade --refresh

开发环境没有生产环境那么强的稳定性约束,系统组件新一点,反而可以早点发现上游行为变化对项目的影响。比如某个依赖库升级后改变了 API 行为,你在开发环境第一时间发现,提前调整代码,比等生产环境出问题再排查要舒服得多。

不过开发环境升级也有一个需要提醒的点:定期重启。很多人升级完系统后不重启,觉得“更新完了就完事了”。实际上,如果内核、systemd、图形栈这些核心组件更新了,不重启的话,系统里跑着的进程仍然挂在旧内核甚至旧库上,升级并没有真正生效。你可以在升级后检查一下是否有“待重启”状态:

sudo dnf needs-restarting

这个命令会列出由于系统更新而需要重启才能加载新版本的进程服务。如果输出为空,说明当前跑着的进程都没有受到更新影响;如果列出了不少服务,至少要把它们逐个重启,最好直接择机重启整机。

3.3 大版本之间的升级:别用 update/upgrade,老老实实用 system-upgrade

很多人会问:“我用dnf upgrade能不能从 Fedora 39 升到 Fedora 41?”我直接说结论:不行,至少不建议。跨大版本升级(比如从 Fedora 39 升 40、从 RHEL 8 升 9)要换软件源仓库,光靠upgrade命令是做不到的,因为你的软件源默认是指向当前版本的发布仓库,没有指向新版本。

Fedora 官方推荐的大版本升级路径是使用dnf system-upgrade插件:

sudo dnf install system-upgrade sudo dnf system-upgrade download --releasever=41 sudo dnf system-upgrade reboot

这套流程会先下载新发布版本的所有所需包、更新系统固件/引导器,然后自动重启到升级流程。整个过程比直接upgrade更复杂,但可控性高,官方支持力度也大。

我有一篇专门写 Fedora 跨版本升级的文章,里面详细讲了这个插件的使用和踩坑,感兴趣的朋友可以去看那篇。简单提醒一句:跨大版本升级前,一定要先备份重要数据,并确保磁盘空间充足。升级过程出现意外断网、断电,轻则升级中断,重则系统无法引导。

3.4 distro-sync:什么情况下该用它来对齐源

除了update/upgrade,DNF 还有一个很容易被忽略但很有用的命令:dnf distro-sync。它会把系统里已安装的软件包版本,与当前启用的软件源中的版本进行“同步对齐”,允许把包降级,以匹配源里的设定状态。

举例说明:如果你之前从某个第三方源安装了比官方源更新的版本,或者在某次测试中手动降级了某个包,系统当前的状态和官方源不一致,这时dnf distro-sync就会让所有包回到官方源定义的版本集合。

这个命令在生产环境的用途通常是:当你要把系统从一个定制源切换到官方源,或者要清理测试阶段留下的实验性软件包版本时。比如:

sudo dnf distro-sync

执行前建议先加--assumeno参数看下预演结果(其实是模拟参数,DNF 里默认操作会询问确认),建议先看它会变更哪些包再决定是否真跑。我在一次切换第三方镜像源时,就是用distro-sync把测试环境的包版本拉回官方一致状态,避免了重装系统。

4. 升级过程中的常见翻车现场与排查思路

不管用哪个命令,系统更新过程都不可能百分百顺滑。我根据自己的实操经历,整理几个高频翻车问题,大家遇上了可以少走弯路。

4.1 事务检查失败的经典重灾区:依赖冲突与 GPG 签名

升级时如果你的软件源里出现了同一个包的两个不同版本,或者不同仓库之间产生了依赖冲突(比如 A 仓库的 libfoo 需要 libbar >= 2.0,而 B 仓库的 libbar 还停留在 1.8),DNF 会在事务检查阶段直接报错:

Error: The operation would result in resolving a dependency conflict

这种问题通常不是 DNF 本身的问题,而是第三方软件源没有和发行版官方源做好协同。排查思路我一般按下面这几步走:

  1. 先看错误提示里具体提到了哪个包、哪个依赖冲突。
  2. 执行dnf repolist查看当前启用了哪些软件源。
  3. 逐个禁用可疑的第三方源,尤其是那些已经很久不维护的。

比如我们可以临时禁用某个有问题的源再试:

sudo dnf upgrade --disablerepo=some-third-party-repo

如果禁用后问题消失,说明冲突根源在第三方源。这时可以等第三方源更新,或者干脆移除它。

另一个高频毛病是 GPG 签名校验失败:

Error: GPG check FAILED

这通常是软件源维护者更换了签名密钥,或你本地缓存的公钥过期。处理方式很直接——重新导入该源的官方 GPG 公钥,或者去源的官网下载最新公钥并执行rpm --import <key-file>。正规发行版(Fedora、RHEL、EPEL 等)在官方文档里都有公钥地址。不要图省事直接加--nogpgcheck参数跳过校验,除非你明确知道自己正在做什么(比如在隔离测试环境里用本地源做实验)。

4.2 不要随便加 --force 或 --allowerasing:两个危险参数的自白

DNF 有两个让新手容易误用的参数:--force(在 DNF 里一般体现为--setopt=*相关)和--allowerasing。准确说,DNF 里没有--force这个通用参数,rpm 层面才有rpm -Uvh --force,但 DNF 命令里有一些替代强制的选项。这里要重点批评的是--allowerasing。

--allowerasing允许 DNF 在解决依赖关系时,自动移除那些“看起来不再被需要”的包,以完成升级事务。危险之处在于,某些包虽然在依赖解析器里不构成依赖,但它们是你系统运行中实际需要的。

举一个真实案例:某次我在一台跑着旧版本监控代理的服务器上执行升级,日志系统里某个采集器包因为依赖变化,导致 DNF 认为它和新的 libpcap 版本冲突。我没有细看就直接加了--allowerasing跑完升级,监控代理被悄悄删掉了,直到过了一个多星期才发现监控数据断档。

所以,当你遇到依赖冲突时,正确操作顺序是:

  1. 先看错误信息,判断是哪几个包在打架。
  2. 用dnf repoquery --whatrequires <包名>查一下冲突包的被依赖关系。
  3. 考虑是通过禁用某个源解决,还是单独升级/降级某个包。
  4. 只有完全确定某个包确实被废弃、不再需要,才能用--allowerasing,且之后要重新检查系统状态。

4.3 内核更新后无法开机或模块不匹配

内核更新是升级过程中最能制造“心跳骤停”的环节之一。更新完重启,系统卡在引导界面,或者某个驱动模块加载失败,是很多人升级后最怕遇到的情况。

Fedora 系发行版默认会保留最近几个内核版本,这让回滚变得简单。如果你升级到新内核后无法正常启动,可以在 GRUB 引导菜单里选旧版本内核进入系统。进入后就地解决问题的方法有:

sudo dnf remove <new-kernel-version>

或者用grubby调整默认内核版本:

sudo grubby --set-default=<old-kernel-version>

我对生产服务器处理内核更新有一个比较朴素的原则:不要追新内核。新内核往往意味着新的硬件支持、性能优化和安全修复,但在你没有可用的备用环境验证之前,不要直接推到生产。正确的流程是先在一台测试机上跑一周,确认应用无异常,再在生产环境单独升级内核并安排维护窗口重启。

4.4 磁盘空间不足与下载中断

升级过程中系统会先下载所有需要的 RPM 包到缓存目录(默认是/var/cache/dnf),再进行事务性安装。如果你的系统盘空间不足,下载阶段可能不会报错,但在解压和安装事务执行时,会因为临时空间不足而产生莫名其妙的失败。

我建议每次做大型升级前,都先检查下磁盘使用率:

df -h /

通常/根分区至少要有 2GB 以上的剩余空间,跨大版本升级时建议留出 5GB 以上。空间不够时,优先清理旧的缓存包:

sudo dnf clean packages

这个命令只清理已下载的缓存包,不会影响已安装系统,是回收空间的性价比之选。还可以清理旧的 journal 日志(journalctl --vacuum-time=3d),或者用dnf autoremove卸载不再被依赖的孤立包。

如果下载中断,DNF 本身有断点续传能力,但如果你使用了代理或镜像源,偶尔会出现缓存文件损坏。这时清掉缓存重试即可:

sudo dnf clean all sudo dnf upgrade --refresh

5. 好习惯比命令选择更重要:我给新手的几点实操建议

既然update和upgrade本身没有本质区别,那比选命令更重要的,是围绕升级建立一套可靠的操作习惯。下面列几个我自己长期坚持的做法。

5.1 升级前先看 updateinfo 和依赖关系

在执行任何重要升级前,我会先用只读命令查看更新列表,而不是直接升级:

dnf updateinfo dnf list updates

dnf list updates会把所有可更新的软件包列出来,包括版本号、仓库来源;dnf updateinfo则显示这些更新里哪些属于安全公告、哪些是普通修复。看完这两项输出,你对自己即将面临的“动荡”会有个基本预期:是一次几十个包的大规模变动,还是仅仅几个补丁。

另外,如果某个包要升级大版本(比如从 3.x 升到 4.x),我会额外查一下它的更新日志:

dnf changelog <软件包名>

这个命令会列出最近几个版本的变化说明。如果更新日志里出现了不兼容变更(比如配置格式变化、默认行为调整),我就知道要先看官方迁移文档,或者调整自己的配置文件。

5.2 合理使用事务历史与事务回滚

DNF 的事务历史功能是一个宝藏,可惜很少被人提起。每次执行成功的升级、安装、移除操作,DNF 都会把它记录成一条事务,你可以随时查看:

dnf history

如果某次升级后系统异常,需要让某些包回到升级前版本,可以用:

sudo dnf history rollback <事务ID>

不过 rollback 并不是万能的,它有很严格的先决条件——DNF 必须能拿到原来的软件包版本并计算出可行的回滚事务。如果升级过程中涉及大量依赖变更,或者旧版本包已经在下游源中被删除,rollback 可能会失败,此时只能手工去源里找对应的 RPM 包,或者用dnf downgrade指定旧版本号逐包处理。

即使 rollback 有各种限制,我仍然建议你在做高危升级前,先运行dnf history记录当前事务列表,至少心里有数最近的变更基础是什么。

5.3 使用 exclude 锁定关键包版本

对生产系统和重要的开发环境,我一般会用exclude锁定一些关键包的自动更新。锁定的对象通常是:

  • 内核相关包(kernel*)
  • 特定的驱动模块(nvidia*或自定义驱动)
  • 关键运行时(如自编译的 OpenSSL、libc 相关组件)

配置方式有临时和持久两种。临时的方式是命令行直接加:

sudo dnf upgrade --exclude=kernel*

持久的方式更推荐,在 DNF 配置里设置:

[main] excludepkgs=kernel* nvidia*

位置在/etc/dnf/dnf.conf。这样配置后,dnf upgrade默认就会跳过这些包,当你确实要升级它们时,再临时用--disableexcludes=all参数解除锁定。

注意别把 exclude 写得过于宽泛,我曾经见过有人把glibc*也锁了,结果系统上的其他包都因为无法更新动态链接库而逐渐变得陈旧,最后不得不手动解除锁定再集中更新,反而增加了风险。

5.4 升级完不是终点:验证与监控

系统升级只是一个开始,真正重要的是升级后的验证。我会在升级完成后做三件事:

  1. 检查系统版本和关键包版本:
uname -r cat /etc/os-release dnf list installed | grep glibc
  1. 查看是否存在“需要重启的服务”:
sudo dnf needs-restarting
  1. 确保系统服务运行正常。如果你在用 systemd,可以快速检查异常单元:
systemctl --failed --no-pager

这三个检查做完,我才认为一次升级流程画上了句号。很多人升级完不检查就直接跑,结果部分服务挂了一个星期才发现,这时候排查起来可没那么轻松。

5.5 定期升级比一次憋个大更新更安全

运维老手常说“小步快跑”,系统升级也是同样的道理。如果你三个月不升级,一次憋个一千个软件包的大事务,风险远高于每周或每两周做一次小规模升级。升级本质上是在“功能演进”和“稳定风险”之间做平衡,频率太低会让每次事务的变动集太大,一旦出问题,排查面也会大很多。

我的建议是:

  • 个人桌面机:每周执行一次dnf upgrade --refresh,配合重启。
  • 开发测试机:可以根据迭代节奏,每两周做一次安全更新 + 全量更新。
  • 生产服务器:每月至少一次dnf upgrade --security,每季度或半年结合维护窗口做一次全量更新。

当然,具体节奏还要看你所在团队对变更管理的偏好,但原则无非是“定期、可控、可回滚”。

6. 最后分享一个我自己的小习惯

写了这么多,其实核心就是一句话:与其纠结update和upgrade哪个更好,不如关心你升级时带了什么参数、有没有刷新索引、有没有锁定关键包。从 DNF 4 到 DNF 5,update都只是upgrade的别名,两个命令在行为上没有本质区别,把力气花在参数组合和升级策略上,才是真正能让你少踩坑的地方。

我个人倾向于在所有脚本和文档里统一用dnf upgrade,因为它在语义上更清晰,未来兼容性也更好。但如果你在惯用的脚本里看到dnf update,完全不用紧张,它和dnf upgrade干的是同样的活。

升级系统前多看几眼dnf history、dnf updateinfo,更新完跑一下dnf needs-restarting,这几分钟的成本,换来的是半夜不用被监控告警电话叫醒,我觉得非常划算。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询