ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

yumdownloader 报 no such table: packages 的排查与修复

yumdownloader 报 no such table: packages 的排查与修复 第一次在 CentOS 7 的构建机上执行yumdownloader --source nginx几秒钟后看到 no such table: packages说实话我是有点懵的。yumdownloader 平时相当可靠二进制包下载一切正常为什么单独取源码包就抛出一个 SQLite 错误这报错既不像网络中断也不像源仓库缺失更像是某个内部数据库出了问题。后来把整个链路捋了一遍才明白这行错误是 yum 在查询本地仓库缓存时从primary_db.sqlite里读数据失败抛出来的异常。这篇文章会从根因讲起把清理缓存、重建 RPM 数据库、切换工具链三条修复路线完整写一遍并附上我实际踩过的坑和排查套路。适合系统管理员、RPM 打包维护者以及所有需要从仓库拉取 src.rpm 做构建环境的人参考。1. 错误根因为什么缓存里会缺一张 packages 表1.1 这个报错到底出自哪一步yumdownloader 名字里带 download但它的执行流程更像是一个“先查询、后下载”的客户端。执行yumdownloader --source时程序首先通过 yum 的仓库插件读取本机已经同步的元数据缓存这个缓存在 CentOS/RHEL 7 默认路径是/var/cache/yum/$basearch/$releasever/$repo/下的gen/primary_db.sqlite。这个 sqlite 文件是 yum 从仓库下载的primary.sqlite.bz2元数据解压后生成的里面通常包含packages、updateinfo、filelist等几张表。所谓no such table: packages指的是 SQLite 在执行类似SELECT * FROM packages WHERE ...的操作时发现当前连接的数据库文件里根本没有packages这张表。有意思的是这个问题不会影响普通二进制包的下载。原因在于二进制包下载时yumdownloader 走的是later_ver或whatprovides查询路径某些场景下可以退回到读repomd.xml里的原始列表而源码包查询依赖仓库中更完整的 metadata 库一旦 sqlite 元数据异常就直接暴露出来了。1.2 常规根因仓库缓存损坏导致primary_db.sqlite缺表的最常见原因是缓存文件写坏了。什么情况下会写坏根据我遇到过的现场基本可以归成以下几类一是并发冲突。比如同一台机器上同时跑yum update和yumdownloader --source两个进程同时打开同一个primary_db.sqlite一个进程在重建索引另一个进程在查数据SQLite 的锁机制在某些旧版本 yum 下处理得并不好最终就会留下一个半成品文件。二是磁盘空间不足。/var/cache/yum所在分区满了yum 在写缓存时写一半就报错退出文件头已经生成但核心表没有同步落盘这时候文件仍然能被 SQLite 正常打开但内部结构不完整。三是系统异常重启。yum 写缓存时没有做额外的事务保护机器断电或强制重启缓存文件很容易停留在“序列化一半”的状态。四是旧缓存与新元数据格式不兼容。仓库方升级了 repodata 生成方式比如从旧的 sqlite 格式切换到 zchunk 压缩格式后缓存目录里残留了旧版元数据新版本 yumdownloader 读的时候就会把表名对不上。1.3 非常规根因RPM 数据库与工具链版本不匹配还有一类比较隐蔽的问题虽然报错来自 SQLite 查询层面但本质上不是 yum 的仓库缓存而是 RPM 数据库本身发生了损坏。CentOS 7 默认的 RPM 数据库是基于 Berkeley DB 的Packages文件而 CentOS 8 之后改成了rpmdb.sqlite。如果系统经历过大版本升级或者有人手动替换过/var/lib/rpm下的文件工具链版本和数据文件 schema 之间就会错位。比如你在 CentOS 8 上装了老版本的yum-utilsyumdownloader 调用的 Python API 可能还在按老路径读数据库此时如果/var/lib/rpm下同时存在Packages和rpmdb.sqlite就会引发非常奇怪的错误。rpm --rebuilddb能解决一部分这类问题因为它会重新生成当前工具链能够识别的数据文件格式。2. 动手前的准备确认环境与可回滚方案2.1 先确认发行版和源仓库状态排查这类问题第一步永远不要急着清缓存先确认自己处于什么环境。我见过有人把生产机器上的/var/cache/yum直接删掉结果因为离线环境无法重新拉取元数据反而把问题扩大化了。先执行这三条命令cat /etc/os-release rpm -q yum-utils dnf yum yum repolist all | grep -E repo id|source第一条确认系统版本第二条确认工具链套件第三条确认源仓库里有哪些是启用的、哪些是禁用的。yumdownloader --source需要源仓库source repo处于启用状态如果源仓库本来就是 disabled报错可能更像 “Unable to find a source package”而不是no such table。但元数据缓存损坏的情况往往和源仓库启用状态叠加出现先确认可以避免后面做了无用功。2.2 备份缓存与 RPM 数据库在任何破坏性操作前必须留下可回滚的备份。缓存目录一般占用不小不需要整目录复制但如果机器上正好有空间我还是建议做一次完整备份mkdir -p /root/yum-backup cp -a /var/cache/yum /root/yum-backup/yum-cache-$(date %F) cp -a /var/lib/rpm /root/yum-backup/rpmdb-backup-$(date %F)为什么连 RPM 数据库也要备份因为后续步骤里我们可能执行rpm --rebuilddb虽然这个操作本身很安全但如果磁盘已有坏块或文件系统异常重建过程中可能把原数据覆盖掉。有备份在手心里不慌。还要顺手检查一下磁盘空间df -h /var/cache/yum /var/lib/rpm我遇到过一次/var/lib/rpm所在分区只有 200MB 剩余空间而且 rpmdb.sqlite 已经膨胀到 1.8GB。这种情况下盲目重建数据库几乎必挂必须先清理旧日志或扩容。2.3 配置可用的源仓库如果已经确定yumdownloader --source找不到源码包可以临时打开 CentOS 的 Source 仓库sed -i s/enabled0/enabled1/g /etc/yum.repos.d/CentOS-Sources.repo然后执行yum repolist确认能看到base-source、updates-source这类仓库。注意这里的源仓库和installed仓库不同它只包含 SRPM不会影响普通软件包的更新升级。如果公司内部有私有镜像源需要在镜像上同步 SRPM 路径并确保repodata目录里包含.src.rpm文件否则即使缓存正常也找不到源码包。3. 第一步修复清空缓存并从仓库重新拉取元数据3.1 三种清理命令的差异很多人上来就敲yum clean all但这条命令并没有想象中那么彻底。它实际上会做三件事清理缓存目录下的包文件、清理元数据缓存、重置expire-cache标记。问题在于如果缓存文件已经处于损坏状态yum clean all在遍历目录时可能直接跳过了它因为它只删除“索引中识别到的”文件对残缺的数据库文件反而无感。更有效的是组合命令yum clean all yum clean expire-cache如果还是不放心可以用dnf对应命令dnf clean all dnf clean dbcachednf clean dbcache是专门清理 SQLite 数据库缓存的这一点和 yum 有区别。CentOS 7 上如果安装过 dnf 工具这条命令会直接删除/var/cache/dnf下的 sqlite 文件比手动rm -rf更安全。3.2 手动删除缓存目录当yum clean all无效时就不要犹豫了直接删目录rm -rf /var/cache/yum/* rm -rf /var/cache/dnf/* # 如果存在这里有个很重要的点rm -rf删的是整个缓存目录yum 下次运行时如果发现缓存不存在会自动重新建立目录并拉取完整的元数据。不需要手动提前mkdir也不需要担心目录权限问题yum 进程会以 running 用户身份重建。但删之前要确认 LDK 上没有其他进程正在使用缓存。最简单的方式ps aux | grep -E yum|dnf如果有yum update或dnf install在跑等它结束再执行。否则删除过程中进程还在向文件写入删完又出现新的半成品等于白删。3.3 重新生成缓存并验证删除完成后执行一次完整的元数据拉取yum makecache fastmakecache fast会为所有启用的仓库下载 repomd.xml、primary.sqlite 等元数据并生成新的primary_db.sqlite。生成完成后可以手动验证一下 sqlite 的表结构sqlite3 /var/cache/yum/x86_64/7/base/gen/primary_db.sqlite .tables正常情况下会输出packages、updateinfo、filelist、group等表名。如果这一步已经能列出packages表说明问题出在旧缓存文件损坏直接重新执行yumdownloader --source nginx验证即可。我建议在验证时加一个--verbose参数这样能看到 yumdownloader 实际读取的缓存路径和仓库 ID避免误判yumdownloader --verbose --source nginx4. 第二步修复重建 RPM 数据库4.1 rpm --rebuilddb 的操作与原理如果清理缓存后问题依旧或者报错信息里同时夹杂着rpmdb:、BDB:、sqlite:等关键字就要把视野扩大到 RPM 数据库本身。rpm --rebuilddb的作用是根据/var/lib/rpm下原有的包头文件数据重新生成当前 RPM 版本能够识别的数据库文件。在 CentOS 7 上执行cp -a /var/lib/rpm /root/rpmdb-backup-$(date %F) rpm --rebuilddb重建过程中 RPM 会扫描所有已安装包的 header 信息写入新的Packages数据库。这个操作理论上不会丢包但在磁盘 IO 繁忙时容易超时建议在业务低谷期执行。重建完成后用rpm -qa | wc -l验证一下安装包数量对比备份前后差异。CentOS 8/9、Rocky Linux 8/9 使用的是 SQLite 版 RPM 数据库对应路径变成/var/lib/rpm/rpmdb.sqlitemv /var/lib/rpm/rpmdb.sqlite /root/rpmdb.sqlite.bak rpm --rebuilddb注意这里的mv不是删除而是把损坏文件移到备份目录重建时如果发现目录下没有rpmdb.sqliteRPM 会重新创建一个新的。4.2 排查 /var/lib/rpm 目录重建之前最好先看一眼/var/lib/rpm目录的完整状态ls -la /var/lib/rpm/正常情况下应该有两个核心文件CentOS 7 上是PackagesCentOS 8 以上是rpmdb.sqlite另外还有Alternatives、__db.001等辅助文件。如果我发现这里出现了一堆奇怪后缀的临时文件比如.sqlite-journal、__db.001残留说明之前有异常中断。还要检查权限和属主chown -R root:root /var/lib/rpm chmod 0755 /var/lib/rpm chmod 0644 /var/lib/rpm/*权限问题很隐蔽RPM 进程通常在 root 下运行但如果有人把/var/lib/rpm目录的属主改成了非 root 用户RPM 进程可能拒绝写入数据库会一直处于只读或不可用状态。4.3 修复 SQLite 损坏的隐藏细节如果你是在 CentOS 8/9 上操作rpm --rebuilddb之前可以先用 sqlite3 检查一下损坏程度sqlite3 /var/lib/rpm/rpmdb.sqlite PRAGMA integrity_check;输出ok代表数据库完整输出database disk image is malformed则说明索引页已经坏了。这时候不用纠结修复直接备份后重建。还有一个很容易忽略的点RPM 数据库重建后yum 自己的缓存不会自动刷新。因为 RPM 数据库和 yum 仓库缓存是两套体系重建 RPM 数据库后最好按第 3 节的流程再清一次 yum 缓存。两者配合使用才能把整条链路恢复干净。我通常的执行顺序是先重建 RPM 数据库再清理 yum 缓存最后yum makecache。5. 第三步修复切换工具链让 yumdownloader 别再触发旧缓存5.1 用 dnf download --source 替代如果系统已经支持 dnf我强烈建议直接切换过去。dnf 的缓存路径和管理方式和 yum 不同它会把元数据放在/var/cache/dnf下并且使用 libsolv 而不是老旧的 sqlite 引擎对损坏文件的容错性更好。最直接的替代命令dnf download --source nginx如果想指定仓库dnf download --source nginx --repobase-sourcednf download是 dnf-plugins-core 提供的插件命令功能上等价于 yumdownloader。它在处理 source 包时逻辑更干净先解析源仓库的 repodata然后直接下载.src.rpm不会去触碰普通仓库的primary_db.sqlite缓存。我在 Rocky Linux 8 上测试过这个方案极少出现no such table类的 SQLite 错误。如果系统还是 CentOS 7但想用 dnf可以直接安装 dnf 工具yum install -y dnf装完后用dnf download --source替代原来的命令底层路径全部切到/var/cache/dnf等于把老缓存彻底绕过去了。5.2 重装或升级 yum-utils还有一种常见的诡异情况yum-utils 本身文件没坏但和当前系统里的 python 库版本不匹配。yumdownloader 是基于 Python 的 yum API 实现的一旦 Python 库的rpm模块或者sqlite3模块出了兼容问题报错就会变得五花八门。可以先做一个快速验证rpm -V yum-utils python-yum rpm-pythonrpm -V会校验包内文件是否被修改过如果输出的内容为空说明文件完整性没问题。此时选择重装一次yum reinstall -y yum-utils python-yum rpm-python注意 CentOS 7 上python-yum是核心依赖重装后不会影响系统上已有的 yum 命令。但如果你在一个有基线管理系统的机器上操作重装前建议先确认是否有统一的多版本 Python 策略避免把系统 Python 环境搞乱。5.3 禁用或迁移多余的遗留仓库仓库配置也会诱发no such table: packages。比如/etc/yum.repos.d/下同时存在多个仓库其中一个仓库的 repodata 是损坏的yumdownloader 在遍历所有已启用仓库时遇到这个仓库的缓存就会直接抛 SQLite 错误而不是继续往后查其他仓库。排查方法yum repolist -v 21 | head -50看看哪一行报错。也可以直接打开每个仓库的缓存目录逐一检查primary_db.sqlite是否完整for d in /var/cache/yum/*/*/*/gen/primary_db.sqlite; do echo $d sqlite3 $d .tables 21 | head -2 done如果某个仓库的缓存已经完全损坏而这个仓库又是废弃不用的最好的办法不是修复它而是直接在/etc/yum.repos.d/里把对应的.repo文件注释掉或者修改enabled0。这比反复清理缓存更有效因为程序不会再去碰那个坏仓库了。手动下载也没有问题。如果不确定是否缓存问题可以用curl直接拉取源仓库元数据curl -O http://mirror.centos.org/centos/7/os/x86_64/repodata/repomd.xml然后找到primary数据项的 URL直接下载 SRPM。这种方式完全绕开本地 yum 缓存虽然笨了点但在应急场景下最可靠。6. 折腾记录这个问题通常只是表象6.1 一个问题对照速查表报错上下文最可能原因首选处理方式no such table: packages指定仓库下载源码包时repo 的 sqlite 元数据缓存损坏清缓存、重建 makecache清理缓存后仍然出现no such table: packagesRPM 数据库或工具链版本不匹配重建 rpmdb检查 python 组件同时出现database disk image is malformedsqlite 文件页损坏缓存不完整删除/var/cache/yum、/var/cache/dnf后重建同时出现unable to open database file目录权限或磁盘空间问题检查属主、权限、分区空间普通包下载正常仅源码包报错源仓库未启用或源仓库元数据缺失启用 source 仓库确认 repodata 有 src.rpm多仓库环境下偶发报错某个废弃仓库的缓存损坏禁用不用仓库或单独删除其缓存目录这张表是我这次排查过程中的实际记录不一定覆盖所有场景但方向上足够通用。遇到同类问题先按表里的顺序从前往后试大概率能定位到源头。6.2 遇到类似的 no such table 变体怎么办除了packages表我还在不同机器上见过no such table: sqlite_sequence、no such table: updateinfo、no such table:等等。这些变体本质都一样都是 SQLite 数据库文件不完整或者 schema 版本不对。我的处理原则是不要试图用 sqlite3 手工去补表因为 yum/dnf 的缓存数据结构并不稳定手工补表很容易造出更奇怪的问题。正确做法是让工具自己重新生成缓存。手动删目录后执行yum makecache这是最干净、最接近官方设计的路径。如果连yum makecache都会报no such table那问题基本不在缓存而在系统本身。考虑检查是否安装了多个版本的libsqlite3rpm -qa | grep sqlite ldconfig -p | grep sqlite3多版本 sqlite 共存会导致 yum 的 Python 模块加载了错误版本的库文件这时候需要统一版本或者重新安装系统自带的 sqlite 包。6.3 我的经验别把时间浪费在修坏文件上说一个我个人的操作习惯。遇到这类缓存损坏问题我不太会花时间研究那个损坏的 sqlite 文件为什么变成了这样而是直接删掉重建。这不是偷懒是因为 yum 缓存本身是“可再生资源”它就是从远端仓库元数据生成的临时数据删掉后重新拉取并不会失去任何重要信息。相比去修复一个可能被多个并发进程反复写入的文件重建更安全也更快。不过删除之前我始终会做备份并且确认没有正在运行的 yum/dnf 进程。这个习惯帮我避免过几次事故比如有一次在容器里删缓存删完后发现容器层有问题原缓存根本不在容器内而是在宿主机挂载目录里最后只能重建整个容器环境。从那之后我在任何删除动作之前都会先mount确认路径对应关系。如果你在网络受限环境中操作比如离线镜像站那么删除缓存前一定要确认远端仓库还能访问。不能访问的情况下就不要贸然执行rm -rf /var/cache/yum可以先尝试修复 sqlite 文件或者把备份目录里的缓存文件手动放回去。最后分享一个小技巧执行任何 yumdownloader 命令前先用ls -l /var/cache/yum/*/*/*/gen/看看缓存文件的时间戳。如果时间戳是昨天的但你的 repodata 已经更新了说明缓存一直是旧状态问题可能只是过期缓存。执行yum clean expire-cache再跑一次很多时候就能避免全套清理动作。这个检查耗时不到十秒但能大幅缩短排查路径。
返回列表