在CentOS Stream 8或9的环境中,当系统同时存在dnf和yum时,最让人头疼的莫过于事务冲突。表面上看两者都能装包,但底层调用的是同一个RPM数据库。一旦用yum装了某个包,再用dnf操作时,系统会直接抛出“事务锁定”或“已存在但版本不一致”的错误。这不是bug,而是两个包管理器在争夺同一个事务锁和状态数据库的必然结果。

解决这个问题的核心思路,不是卸载其中一个,而是理解它们如何共享状态、如何清理残留的事务数据,以及如何在混合使用后修复损坏的依赖关系。下面直接进入具体的排查和修复流程。

事务锁的本质:/var/lib/rpm/.dbenv.lock 和数据库争用

yum和dnf在执行安装或更新时,都会去获取RPM数据库的独占锁。如果其中一个进程异常中断,或者你在另一个终端同时运行了dnf和yum,锁文件就会残留。此时无论执行哪个命令,都会提示“另一个程序正在持有锁”。先别急着删锁文件,用fuser确认是否真的有进程还在跑:

fuser /var/lib/rpm/.dbenv.lock

如果返回了PID,说明确实有进程未退出,可以kill掉对应PID。如果没有任何输出,说明锁是僵尸残留,直接删除即可:

rm -f /var/lib/rpm/.dbenv.lock

删完之后,还必须重建RPM数据库的共享内存环境,否则后续操作会报“db5 error”或“无法打开数据库”。执行:

rpm --rebuilddb

这一步会重新生成/var/lib/rpm下的__db.*文件,耗时取决于已安装包的数量,通常在几十秒到几分钟。完成后,dnf和yum的事务锁问题就解决了。

yum和dnf共享状态文件带来的冲突

在CentOS Stream 8及以上版本,yum实际上已经被dnf替代,/usr/bin/yum只是指向dnf的软链接。但在某些定制化环境或从CentOS 7升级上来的系统中,可能同时存在独立的yum-deprecated包和dnf包。这时两者各自维护自己的缓存和状态目录,但共享同一个RPM数据库。问题在于,yum和dnf的已安装包状态记录文件并不完全同步。

yum的状态记录在/var/lib/yum/下的sqlite数据库中,而dnf的记录在/var/lib/dnf/下。当你用yum安装了一个包,dnf的数据库并不会立即更新,反之亦然。这会导致dnf在执行check-update或install时,发现RPM数据库中已经存在某个版本,但自己的状态记录里却没有,于是抛出“事务检查错误”或“nothing provides”这类令人困惑的提示。

解决方法是强制同步dnf的状态记录。执行一次完整的dnf makecache和distro-sync,让dnf重新扫描已安装的RPM包并更新自己的数据库:

dnf clean all
dnf makecache
dnf distro-sync --allowerasing

distro-sync这一步会将已安装的包与当前启用的仓库版本对齐,同时修复dnf状态记录中的不一致。如果担心自动降级某些包,可以先加上--assumeno预览一下变更列表,确认无误再去掉该参数执行。

混合使用导致的依赖撕裂和解决办法

更隐蔽的冲突发生在依赖关系层面。假设你用yum从EPEL仓库安装了一个包,其依赖是从某个第三方仓库拉取的。随后你用dnf执行系统更新,dnf可能会因为仓库优先级或模块流过滤规则,拒绝那个第三方仓库中的依赖版本,导致整个事务失败。报错信息通常是“模块依赖问题”或“无法解析的依赖”。

这时候需要手动检查模块流冲突。CentOS Stream默认启用了模块化,yum操作可能绕过了模块过滤,而dnf严格遵循模块上下文。执行:

dnf module list --enabled
dnf module list --installed

找出已启用的模块流,然后针对冲突的包,重置其模块流或禁用模块过滤:

dnf module reset 模块名
dnf module disable 模块名

如果确认不需要模块化约束,可以直接在/etc/dnf/dnf.conf中添加module_obsoletes=1和module_stream_switch=1,让dnf在解决依赖时更激进地切换模块流。但这样做有风险,建议先在测试环境验证。

清理yum遗留的事务和插件干扰

很多从CentOS 7迁移过来的系统,/et/yum/目录下还残留着yum插件配置,比如fastestmirror、priorities等。这些插件在dnf环境下并不会生效,但如果你安装了yum-deprecated包,它仍然会读取这些配置,造成两个包管理器行为不一致。直接移除yum-deprecated及其残留配置是最干净的方案:

rpm -e yum yum-deprecated yum-utils
rm -rf /et/yum /var/lib/yum

移除后,确保/usr/bin/yum是指向dnf-3的软链接,这样所有yum命令实际上都由dnf处理,从根本上消除双轨运行的可能。如果业务脚本中硬编码了yum命令,这个软链接也能保证兼容性。

RPM数据库底层损坏的手动修复

在极端情况下,混合使用yum和dnf可能导致RPM数据库出现重复条目或索引损坏。表现为rpm -qa列出的包有重复,或者dnf install时报“package already installed but not in dnf db”。这时仅靠dnf distro-sync无法修复,需要直接操作RPM数据库。

先备份整个数据库:

cp -a /var/lib/rpm /var/lib/rpm.bak

然后使用rpmrebuild工具进行深度修复:

rpm --rebuilddb -vv

如果还有重复包,用以下脚本找出并去重:

rpm -qa --qf '%{NAME}\n' | sort | uniq -d | while read pkg; do
    rpms=$(rpm -q "$pkg" --qf '%{NAME}-%{VERION}-%{RELEAE}.%{ARCH}\n')
    echo "Dupliate: $pkg"
    echo "$rpms"
done

对于重复的旧版本,用rpm -e --justdb --nodeps 旧包全名来仅从数据库中移除记录,而不删除文件。然后再用dnf reinstall 包名来重新注册正确版本。

预防措施:统一包管理器调用路径

彻底避免事务冲突的最佳实践,是在系统层面强制所有包管理操作走同一个工具。在/etc/profile.d/下创建一个脚本,定义别名:

alias yum='dnf'
alias yum-install='dnf install'
alias yum-update='dnf update'

同时在/etc/dnf/dnf.conf中设置clean_requirements_on_remove=True和keepcache=0,减少状态残留。对于使用配置管理工具(如Ansible、Salt)的环境,在模块调用中统一指定使用dnf模块而非yum模块,从源头杜绝混合调用。

如果系统必须保留yum-deprecated,那么请严格遵守一个原则:所有安装、更新、卸载操作只使用其中一个工具,另一个仅用于查询。并且在每次操作后,立即用主导工具执行一次check-update同步状态。虽然麻烦,但这是双轨并行下唯一能减少冲突的方式。