yumdownloader报错no such table packages?根源在sqlite缓存损坏
上周帮一台 CentOS 7.9 的构建机排查源码包下载问题现象不复杂但错误的抽象程度让人印象深刻执行yumdownloader --source想拉某个软件的 src.rpm 时命令直接甩出一句Error: no such table: packages。当时我第一反应是软件源配置写错了反复检查 repo 文件却发现没有任何异常。后来把目光转向 yum 的本地缓存机制才找到真正的病根。这篇文章把完整的排查链路和能落地的几个解决办法都写出来给遇到同样报错的朋友一条直达路径不用再从头踩一遍。1. 报错现场yumdownloader 下载源码包时翻车了1.1 这条命令本来的正常使命先对齐一下背景知识。yumdownloader来自yum-utils工具包它的职责很简单只下载软件包不安装。默认情况下它下载的是二进制 rpm加上--source参数后下载的就是对应软件的源码包 src.rpm。我们这次的真实需求是把某个自研组件的依赖源码包全部拉下来做离线代码审计和二次编译。命令长这样yumdownloader --source nginx正常情况下这条命令会先解析 nginx 的源码包在哪个仓库然后把.src.rpm文件下载到当前目录。源码包不像二进制包那样直接安装运行它的价值在于你可以解包看源码、打补丁、改 spec 文件再用rpmbuild重新构建出属于自己的 rpm。所以凡是做 RPM 二次开发、制作内网镜像源、或者单纯想“读一读这个包到底怎么编译”的人都会频繁用到这条命令。1.2 翻车的具体表现和我的第一反应那天我执行命令后终端输出是这样的Loaded plugins: fastestmirror, langpacks Repository updates-source is listed more than once in the configuration ... Error: no such table: packages没有下载进度没有具体的包名直接中断。no such table: packages这句话从字面上看像是某个数据库查询失败了但 yum 明明是一个包管理器跟“表”有什么关系这就是它让人迷惑的地方。我的第一反应是执行yum clean all清掉所有缓存重试。清完之后命令真的能正常跑通了源码包也顺利下载下来。但问题并没有彻底了结——过了大约两天同一个环境再次出现一模一样的报错。这说明不是一次性偶发问题而是 yum 缓存机制里有某个深层隐患在反复发作。正因如此只靠“清缓存”治标不治本必须把背后的机制彻底搞清楚。2. 先搞明白 “no such table: packages” 错在哪里2.1 yum 缓存里的 sqlite 数据库到底是什么很多人用 yum/dnf 很多年从来没注意过/var/cache/yum/这个目录。它表面上是缓存实际上是 yum 能够快速工作的核心基础设施。当你执行yum makecache或第一次访问某个软件源时yum 会从远程仓库拉取元数据文件主要是一个叫repomd.xml的索引以及它引用的 primary、filelists、other 等数据文件。这些原始数据通常是 XML 格式几十 MB 甚至上百 MB直接解析效率太低了。于是 yum 会把它们转换成 sqlite 数据库存放在本地缓存目录里之后所有查询都走 sqlite速度能快一个数量级。以 x86_64 架构的 CentOS 7 为例缓存目录结构大概是这样的/var/cache/yum/x86_64/7/ ├── base │ └── primary_db.sqlite ├── epel │ └── primary_db.sqlite └── updates └── primary_db.sqlite每一次yum install、yum search、yumdownloader本质上都是在这些 sqlite 文件里执行 SQL 查询。primary_db.sqlite里最重要的表之一就是packages这张表记录了仓库里每个软件包的名称、版本、架构、路径、校验和等关键信息。yumdownloader --source nginx要做的第一件事就是去packages表里查 nginx 这个包对应的源码包条目。2.2 为什么偏偏是 packages 表丢了既然数据结构清楚了那句报错的含义就明朗了yum 在打开某个仓库的 sqlite 缓存文件时没有找到packages这张表。换句话说primary_db.sqlite这个文件存在但它不是一个“健康”的仓库数据库。我整理了几种常见的病根按概率排序触发原因具体表现为什么会产生sqlite 文件写入被截断文件大小正常但表不完整磁盘空间满、进程被 kill -9 中断多个 yum 进程并发抢占缓存缓存文件互相覆盖定时任务和手动操作同时执行缓存 meta 与实际数据库不对应yum 误认为缓存可用跳过重建手动删除 sqlite 文件但未清 meta源配置多次重复引用同一个源加载多次导致文件错位repo 文件里重复定义同名仓库特别说明一下第一种情况yum 在生成primary_db.sqlite时如果磁盘空间恰好耗尽sqlite 写入过程会中止。此时文件里可能只创建了一部分表或者连表结构都没来得及建立。no such table: packages就是这种半成品数据库的典型报错。2.3 为什么 yumdownloader 比 yum install 更容易触发同样的缓存问题有时候你执行yum install并不会有任何异常但执行yumdownloader --source却百分百报错。这不只是因为运气背后有一个执行路径的差异。yum install在安装过程中会经过依赖解析、事务处理等多个阶段如果发现缓存异常yum 会尝试重新拉取仓库元数据来修正而yumdownloader的查询路径更短更贴近底层直接去读缓存数据库一旦缓存有问题就立刻报错没有给 yum 留下“自动修复”的余地。再加上--source参数还有个特殊之处yumdownloader 需要在元数据中定位源码包条目而源码包往往来自独立的 source 仓库。如果你没有在 repo 配置里启用 source 仓库yumdownloader 会去主仓库里找 src.rpm这个查询本身依赖缓存库中的packages表。主仓库的缓存文件本来就多、更新频繁出问题的概率自然更大。3. 完整排查链路从缓存目录到数据库文件的层层验证3.1 先确认故障范围避免瞎猜排错第一步不是急着改东西而是确认故障范围。我在报错环境里先换了另一个包试了一次yumdownloader --source openssl结果报错完全一样。然后把范围再扩大试一下普通的二进制包下载yumdownloader tree如果普通包能下载、只有源码包报错那问题大概率出在 source 仓库的配置上如果普通包也报错那问题就指向全局缓存。实测下来yumdownloader tree同样报no such table: packages这就说明不是某个包的源数据问题而是整个本地元数据缓存都处于异常状态。接着检查软件源配置是否重复加载。执行yum repolist命令输出正常仓库列表和 ID 都正确至少说明 repos 文件本身能被正常解析。但注意yum repolist本身就可能会触发缓存重建如果它成功执行会把问题暂时掩盖掉。这就是排错时要小心的一个点——必须先记录异常状态再执行可能产生“修复副作用”的命令。3.2 检查缓存目录里的实际文件状态确认故障范围后进入第二层查看缓存目录。ls -l /var/cache/yum/x86_64/7/看到 base、epel、updates 等仓库目录都在。继续深入ls -l /var/cache/yum/x86_64/7/base/关键信息来了primary_db.sqlite文件存在但大小显示是 0 字节。一个 0 字节的 sqlite 文件你去读它的时候sqlite 会直接返回“文件不是数据库”的错误而 yum 在更高层把它抽象成了“no such table”的信息。再顺着这个思路把所有仓库的缓存文件都检查一遍用一条命令找出可疑文件find /var/cache/yum/ -name *.sqlite -size 0果然输出包含了 base 和 updates 两个仓库的 primary_db.sqlite。到这里问题已经浮出水面缓存数据库文件被截断成了空文件yum 在查询时自然找不到任何表。结合前几天这台机器出现过磁盘空间告警写入中断是最合理的解释。还要留意文件时间戳是否异常。如果 sqlite 文件的修改时间明显晚于日志里的报错时间说明在报错后有人或某个进程又触发了写入可能的现场已经被“污染”了这时候更要依赖日志来推断原始原因。3.3 用 sqlite3 命令行直接验证数据库完整性如果缓存文件不是 0 字节但仍然报同样的错我们就需要直接打开 sqlite 文件验证表结构。命令如下sqlite3 /var/cache/yum/x86_64/7/base/primary_db.sqlite .tables一个健康的 primary 缓存文件.tables输出会包含packages、updates、installed等表。如果输出为空或者直接报file is not a database就说明文件已经损坏。我后来在另一个环境里见过一种更有迷惑性的情况primary_db.sqlite文件有 30 GB 大小.tables命令也能执行但表名全是rpm_*之类的临时表。原因是某个同步脚本把另一个数据库文件的内容直接覆盖到了这个路径上。这种错位的缓存比 0 字节文件更难排查因为从文件系统层面看一切都很正常。遇到这种情况不要试图修复文件直接删除让 yum 重建是最稳妥的选择。3.4 翻日志找出制造问题的罪魁祸首数据库文件已经确认损坏接下来要搞清楚为什么反复坏。光修一次不解决问题必须找到是谁在“制造事故”。先查看 yum 的操作日志tail -100 /var/log/yum.log日志里如果有最近几天的yum clean all、yum makecache、yum install记录结合起来看时间线基本能还原问题出现的时间点。我这次排查发现日志里有非高峰时段的yum makecache执行记录再对照 cron 配置确认是某个监控脚本在每小时自动刷新缓存。脚本本身没问题但它和手动执行的 yumdownloader 存在时间重叠两个进程同时写同一个 sqlite 文件就可能导致数据库文件互相覆盖。这个发现也解释了一个现象为什么单次执行yum clean all后恢复正常过一两天又复发——因为定时刷新一直在跑损坏条件一直存在。所以排错时一定不要只看报错本身的表层原因要把“周期性任务”这个变量拉进来。4. 能直接解决问题的几个方案由快到慢排列4.1 最快方案yum clean all 重建全部缓存如果你现在就在报错现场最省事的办法是yum clean all yum makecacheyum clean all会把所有仓库的元数据和 sqlite 缓存全部清掉下次 yumdownloader 执行时会强制重新下载并生成全新的缓存数据库。原理上讲再干净的“重建”也不会有历史遗留问题。但这个方法有两个代价。第一所有仓库的全部元数据要重新下载如果启用了 epel、remi 这类大仓库耗时可能达到几分钟甚至更久第二它把正常仓库的缓存也一起清掉了下次首次查询会明显变慢。我在实际处理时还会加一步先确认磁盘空间df -h /var/cache /var/tmp如果磁盘空间严重不足直接执行 makecache 可能再次触发截断清完缓存反而更糟。先腾出空间再谈重建。4.2 精确打击只清理出问题的仓库目录如果不想全量清理可以精确到单个仓库。第一步先定位哪个仓库缓存有问题然后只删除对应的目录rm -rf /var/cache/yum/x86_64/7/base/然后重新生成缓存yum makecache或者干脆不提前 makecache直接再执行一次yumdownloader --source nginxyum 在发现缓存缺失后会自动重新拉取该仓库的元数据。这样做的好处是只重建损坏的仓库其他仓库的缓存文件原封不动耗时和 IO 开销都小很多。但这个方案需要你叠加使用一个前提先搞清楚哪些仓库的缓存损坏了。如果你拿不准用前面提到的find /var/cache/yum/ -name *.sqlite -size 0先扫描一遍再决定删哪个目录。4.3 顺手处理权限与 SELinux 上下文清理重建后如果仍然报错下一个排查点是文件权限和 SELinux 上下文。/var/cache/yum/目录的属主必须允许当前用户读写。常规情况下是 root 属主如果你用了 sudo 用户但环境变量紊乱yum 可能以错误身份写缓存导致文件权限异常chown -R root:root /var/cache/yumSELinux 开启的 RHEL/CentOS 系统上/var/cache/yum有固定的安全上下文。如果之前有人手动移动过文件或者把别的目录挂载到了这个路径下上下文标签可能已经错了。恢复命令restorecon -Rv /var/cache/yum这两个操作很便宜可以在排错链路里顺手做掉不用单独耗费太多时间。4.4 绕开 yumdownloader 的三种替代下载方式有时候你遇到的问题是工具链本身已经损坏或者构建机上 yum-utils 版本过旧与其修复不如绕道。这里分享三个实测可行的替代方案。方案 A用 dnf download如果你的系统是 CentOS 8 或 Fedora 系列系统默认的包管理器是 dnf对应的下载工具是dnf downloaddnf download --source nginx注意要先启用 source 仓库否则 dnf 找不到源码包。CentOS 8 上可以用dnf config-manager --set-enabled powertools如果你使用的是 CentOS 7dnf 不在默认安装范围可以在干净环境里安装 dnf 再尝试但没必要为了下载 src.rpm 专门装一套新工具链更建议用方案 C。方案 B用 wget 直接拉取 src.rpm 链接这个方法最粗暴也最可靠。先去软件源仓库的目录里找到源码包所在路径比如 EPEL 源的 source 目录wget https://mirrors.example.com/epel/7/SRPMS/nginx-1.20.1-1.el7.src.rpm它的优点是完全绕开 yum 缓存机制不受任何本地数据库问题影响缺点是必须手动确认包名、版本号、仓库路径无法自动解析依赖关系。适合只需要下载一两个包的场景。方案 C给 yumdownloader 指定源仓库全名如果仓库配置文件里确实有 source 源只是在默认查询路径中没启用可以显式指定 repoidyumdownloader --source nginx --repoidbase-source实际执行中这个参数能解决一部分“明明有 source 源却报错”的边界情况。不过它不能解决 sqlite 缓存本身损坏的问题只能说是一个便捷的规避手段。4.5 根治措施配置 source 仓库并保持缓存健康“根治”不是一个单点操作而是一组配套动作。第一检查/etc/yum.repos.d/下是否启用了 source 源。以 base 源为例标准配置片段长这样[base-source] nameCentOS-$releasever - Base Sources baseurlhttp://mirror.centos.org/centos/$releasever/os/Source/ gpgcheck1 enabled1把enabled从 0 改成 1或者在执行 yumdownloader 时临时用--enablerepobase-source启用。source 源单独启用后yumdownloader 的源码包查询路径会清晰很多不再去主仓库里找 src 条目从机制上减少一类错误。第二把缓存重建周期固定下来而不是依赖“坏了再清”。在 cron 里加一条低峰期任务比如凌晨 3 点0 3 * * * /usr/bin/yum makecache /dev/null 21这样能保证每次手动执行 yumdownloader 时缓存大概率是足够新鲜的降低“缓存刚生成一半就被打断”的概率。注意任务频率不要太高每小时一次属于过度设计反而容易和其他操作撞车。第三对长期运行的构建机建议把不用的仓库直接禁用。仓库越多元数据越大缓存出问题的可能性和恢复成本越高。yum repolist里如果有一堆从不使用的第三方源逐个禁用掉环境会干净很多。5. 这类问题的另外几个常见触发场景与预防习惯5.1 哪些操作最容易把 yum 缓存搞坏我给问题做了归档之后按出现频率整理出这几类高危操作每一条都亲眼见过磁盘写满后强行执行 yum 操作这是头号杀手。sqlite 写入过程中磁盘满轻则缓存表缺失重则整个文件损坏。用 kill -9 强制杀掉 yum 进程有些人觉得 yum 慢直接强杀。进程中断瞬间如果正在写数据库就会留下半成品文件。多个 yum/dnf 进程并发执行同一台机器上同时跑 yum install 和 yumdownloader写同一个缓存文件互相覆盖。手动删除缓存文件但不清理 meta你以为删掉 sqlite 文件就能强制重建但 yum 的元数据索引还记着旧状态导致读取异常。把 /var/cache/yum 换到特殊文件系统比如放到 NFS 或某些网络挂载上锁机制和本地文件系统不同也可能导致数据库状态不一致。其中最隐蔽的是第二种。yum 在 rpm 事务处理阶段强杀通常不会马上报错但数据库文件已经有问题下次查询时就会冒出来。这种“潜伏型”损坏最让人头疼。5.2 日常使用中怎么保护 yum 缓存避免复现先说结论保护缓存最有效的方式是尽量少做“手动干预”。我在运维实践中逐渐养成的习惯是/var/cache/yum目录只让 yum 自己操作任何脚本、排查命令都只读不写。遇到需要清理场景优先用yum clean all而不是手动rm因为 yum 自己能正确处理 meta 与数据库的一致性问题。另外给/var所在分区预留足够空间。可以用监控工具对磁盘使用率设置告警比如超过 80% 就通知到人。这个阈值看起来简单却能在问题发生前给你留出缓冲时间。如果你用的是云主机根分区扩容也不是难事别让磁盘空间成为潜在故障源。还有一点值得提醒不要在同一个 yum 命令里既更新缓存又下载源码包。比如yumdownloader --source nginx --enablerepobase-source虽然这个命令本身没有错但如果环境刚初始化、缓存还没建立它会边拉元数据边查询。遇到网络抖动或 CPU 争用更容易出现半个缓存。稳妥的做法是先执行一次yum makecache下载和解析完成之后再执行 yumdownloader。5.3 容器和自动化场景下的推荐做法文章开头提到的那台构建机后来我把它改成容器化执行下载任务问题再也没有出现。具体做法很简单下载源码包的步骤放进一次性容器里用完即焚。docker run --rm -v $(pwd):/data -w /data \ centos:7.9.2009 \ bash -c yum install -y yum-utils yumdownloader --source nginx这种方式让 yum 的缓存目录跟着容器生命周期走宿主机上不会残留任何可损坏的长期缓存也就从根本上绕开了no such table: packages这类问题。容器的开销在单次下载任务里可以忽略不计但带来的是环境隔离和可复现性。如果你的自动化环境不适合容器还有一个折中做法把/var/cache/yum挂到临时文件系统tmpfs上。这样每次重启后缓存自动清空虽然首次查询会重新拉元数据但至少不会积累坏文件。回到这次的no such table: packages。核心教训并不是“要会执行 yum clean all”而是遇到一种看不懂的报错时先顺着报错去理解工具的运行机制再动手修复。yum 的缓存数据库机制其实不复杂搞清楚它的工作原理之后这类问题基本一眼就能定位。以后再看到.sqlite相关报错我的第一反应不再是盲目清缓存而是先看一眼缓存目录、搜一下 0 字节文件再决定怎么修。这套流程花不了两分钟却能让问题从“碰运气解决”变成“确定性解决”。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →