从 PostgreSQL 中挽救损坏的表

~/tmp-dir.dab4fd85-8b47-4d9a-b15c-18312ef61075 pg_dump -U postgres -h locathost www_p1 > wow_p1.sql

pg_dump:错误:转储表 “page_views” 的内容失败:PQgetResult() 失败。
pg_dump:详细信息:来自服务器的错误消息:ERROR: relation base/16384/16417 的块 31869 中的页面无效
pg_dump:详细信息:命令为:COPY public.page_views (page_view_id, visited_at, hostname, ip, method, endpoint, user_id, xhr) TO stdout;

警告:千万不要这样做…… 实际上任何时候都不要,尤其是在磁盘有故障的服务器上。这里是在磁盘正常但 Postgres 块损坏的服务器上进行的操作。

在我的专业工作和家庭实验室中,我花了大量时间尝试学习和实施 “正确” 或 “可靠” 的解决方案 —— 高可用部署、自动化和经过测试的备份、基础设施即代码等等。

但这次不是。

这是一种非常粗暴、毫无顾忌、绝对疯狂的做法,如果你在任何重要的环境中工作,你应该阅读本文并聘请专业人员。

由于一些不重要的原因,我一直在处理家庭实验室中 Postgres 服务器上的数据损坏问题。服务器有几次非正常关闭,导致磁盘数据损坏。因为没有什么比临时解决方案更持久的了,所以这台服务器没有备份。

对于大多数数据,我能够使用 pg_dump 转储模式和数据,并将其重新导入到新的 Postgres 服务器中(是的,新服务器现在已经配置了备份)。

pg_dump -U postgres -h localhost my_database > my_database.sql

但是,对于有损坏表的数据库,pg_dump 会因这个令人不安的错误而失败:

pg_dump -U postgres -h localhost www_p1 > www_p1.sql
pg_dump: error: 转储表“page_views”的内容失败:PQgetResult() 失败。
pg_dump: detail: 来自服务器的错误消息:ERROR: relation base/16384/16417 的块 31869 中的页面无效
pg_dump: detail: 命令为:COPY public.page_views (page_view_id, visited_at, hostname, ip, method, endpoint, user_id, xhr) TO stdout;

(…… 是的,那是我的个人网站的数据库。👀)令我有些惊讶的是,我找不到很多关于如何 “尽力” 从损坏的 Postgres 表中恢复数据的详细信息或策略,所以就有了这篇文章。

幸运的是,由于损坏是由 Postgres 非正常退出而不是物理磁盘故障引起的,它只影响了当时频繁写入的表。在这种情况下,就是 sessions 表和 page_views 表。sessions 表完全可以丢弃 —— 我在新服务器上重新创建了一个空表,然后就不管它了。

如果我丢失了 page_views 表,也不是世界末日,但表中记录了大约 650 万条历史页面浏览量,丢失它们还是挺可惜的。所以…… 让我们做些冒险的事情。

我的目标不是恢复整个表。如果是这个目标,我就会停下来聘请专业人员了。相反,我的目标是尽可能多地恢复表中的行。

pg_dump 失败的一个原因是它试图使用游标读取数据,当 Postgres 的基本假设被违反时(例如磁盘块中的坏数据、无效索引),游标读取会失败。

我的策略是在损坏的服务器上创建一个具有相同模式的第二个表,然后逐个遍历 page_views 表中的每一行,并将它们插入到干净的表中,跳过磁盘块中有坏数据的行。要感谢这个 Stack Overflow 答案给了我这个策略的大致启发。

CREATE OR REPLACE PROCEDURE pg_recover_proc()
LANGUAGE plpgsql AS $$
DECLAREcnt BIGINT := 0;
BEGIN-- 从 page_views 表中获取最大的 page_view_idcnt := (SELECT MAX(page_view_id) FROM page_views);-- 按 page_view_id 降序遍历 page_views 表LOOPBEGIN-- 将当前 page_view_id 的行插入到 page_views_recovery 表中INSERT INTO page_views_recoverySELECT * FROM page_views WHERE page_view_id = cnt and entrypoint is not null;-- 递减计数器cnt := cnt - 1;-- 当 cnt < 1 时退出循环EXIT WHEN cnt < 1;EXCEPTIONWHEN OTHERS THEN-- 处理异常(例如数据损坏)IF POSITION('block' in SQLERRM) > 0 OR POSITION('status of transaction' in SQLERRM) > 0 OR POSITION('memory alloc' in SQLERRM) > 0 OR POSITION('data is corrupt' in SQLERRM) > 0 OR POSITION('MultiXactId' in SQLERRM) > 0 THENRAISE WARNING 'PGR_SKIP: %', cnt;cnt := cnt - 1;CONTINUE;ELSERAISE;END IF;END;IF MOD(cnt, 500) = 0 THENRAISE WARNING 'PGR_COMMIT: %', cnt;COMMIT;END IF;END LOOP;
END;
$$;

这里有一些巧妙但又很糟糕的做法。在现代版本的 Postgres 中,存储过程可以通过重复调用 COMMIT 来定期提交正在进行的顶级事务。我在这里(滥用)这个功能,以便在过程运行过程中,如果失败了,已经恢复的行能够被刷新到新表中。

我对与损坏数据相关的错误消息进行了一些粗略的字符串分析,如果是这种情况就跳过当前行。另一个有趣的边界情况:有几次,我遇到了向恢复表中插入数据失败的情况,因为对损坏表的 SELECT 查询返回了 null 值,尽管从技术上讲这是不可能的。我告诉过你我们在这里违反了 Postgres 的一些基本假设。在一个不同的非空列上添加 is not null 有助于避免这种情况。

我最初编写的这个过程是为了持续循环并跳过由磁盘损坏引起的致命错误(错误处理程序中的各种粗糙的 POSITION 检查)。

然而,很快我就遇到了一个新错误:

SQL Error [57P03]FATAL: the database system is in recovery mode

原来,如果你一直故意迫使 Postgres 尝试从损坏的磁盘块中读取数据,最终它的内部数据结构会进入不一致状态,服务器进程会出于安全原因自动重启。

这显然是个问题,因为我们无法捕获这个情况并强制过程继续运行。所以我转而添加 IF 条件来手动跳过导致服务器进程崩溃的主键区域。(我告诉过你这很疯狂。)

每次服务器崩溃时,我都会导出到目前为止恢复的行,以防万一:

pg_dump -U postgres -h localhost --table page_views2 www_p1 > page_views2-1.sql

然后我会跳过一个新的主键区域,删除并重新创建恢复表,然后再试一次。为什么要删除并重新创建它呢?因为我发现当服务器进程崩溃时,它偶尔会向恢复表中写入坏数据,这显然是不行的:

pg_dump: error: 转储表“page_views_recovery”的内容失败:PQgetResult() 失败。
pg_dump: detail: 来自服务器的错误消息:ERROR: 无效的内存分配请求大小 18446744073709551613
pg_dump: detail: 命令为:COPY public.page_views_recovery (page_view_id, visited_at, hostname, ip, method, endpoint, user_id, xhr) TO stdout;

可以预见,手动做这些事情变得非常烦人,所以我做了任何一个优秀的 Linux 极客都会做的事情 —— 为它编写了一个脚本,你可以在这里找到它。要点如下:

./pg-recover.sh postgres localhost www_p1 page_views page_view_id entrypoint

在损坏的表中的 6,628,903 行数据中,我成功恢复了 6,444,118 行。正如人们所说 —— 如果它很愚蠢但却有效,那它仍然是愚蠢的,而你只是幸运罢了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/892566.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue 封装公告滚动

文章目录 需求分析1. 创建公告组件Notice.vue2. 注册全局组件3. 使用 需求 系统中需要有一个公告展示&#xff0c;且这个公告位于页面上方&#xff0c;每个页面都要看到 分析 1. 创建公告组件Notice.vue 第一种 在你的项目的合适组件目录下&#xff08;比如components目录&a…

Win10微调大语言模型ChatGLM2-6B

在《Win10本地部署大语言模型ChatGLM2-6B-CSDN博客》基础上进行&#xff0c;官方文档在这里&#xff0c;参考了这篇文章 首先确保ChatGLM2-6B下的有ptuning AdvertiseGen下载地址1&#xff0c;地址2&#xff0c;文件中数据留几行 模型文件下载地址 &#xff08;注意&#xff1…

HTTP-响应协议

HTTP的响应过程&#xff1f; 浏览器请求数据--》web服务器过程&#xff1a;请求过程 web服务器将响应数据-》到浏览器&#xff1a;响应过程 响应数据有哪些内容&#xff1f; 1.和请求数据类似。 2. 响应体中存储着web服务器返回给浏览器的响应数据。并且注意响应头和响应体之间…

爬虫基础之爬取歌曲宝歌曲批量下载

声明&#xff1a;本案列仅供学习交流使用 任何用于非法用途均与本作者无关 需求分析: 网站:邓紫棋-mp3在线免费下载-歌曲宝-找歌就用歌曲宝-MP3音乐高品质在线免费下载 (gequbao.com) 爬取 歌曲名 歌曲 实现歌手名称下载所有歌曲 本案列所使用的模块 requests (发送…

C++ 鼠标轨迹算法 - 防止游戏检测

一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序&#xff0c;它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言&#xff0c;原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势&#xff1a; 模拟…

2025年中科院分区大类划分公布!新增8155本

2025年中科院分区表变更情况 扩大收录范围 2025年的期刊分区表在原有的自然科学&#xff08;SCIE&#xff09;、社会科学&#xff08;SSCI&#xff09;和人文科学&#xff08;AHCI&#xff09;的基础上&#xff0c;增加了ESCI期刊的收录&#xff0c;并根据这些期刊的数据进行…

【前端动效】HTML + CSS 实现打字机效果

目录 1. 效果展示 2. 思路分析 2.1 难点 2.2 实现思路 3. 代码实现 3.1 html部分 3.2 css部分 3.3 完整代码 4. 总结 1. 效果展示 如图所示&#xff0c;这次带来的是一个有趣的“擦除”效果&#xff0c;也可以叫做打字机效果&#xff0c;其中一段文本从左到右逐渐从…

提升租赁效率的租赁小程序全解析

内容概要 在如今快节奏的生活中&#xff0c;租赁小程序俨然成为了提升租赁效率的一把利器。无论是个人还是企业&#xff0c;都会因其便捷的功能而受益。简单来说&#xff0c;租赁小程序能让繁琐的租赁流程变得轻松、高效。在这里&#xff0c;我们将带您畅游租赁小程序的海洋&a…

Docker--Docker Compose(容器编排)

什么是 Docker Compose Docker Compose是Docker官方的开源项目&#xff0c;是一个用于定义和运行多容器Docker应用程序的工具。 服务&#xff08;Service&#xff09;&#xff1a;在Docker Compose中&#xff0c;一个服务实际上可以包括若干运行相同镜像的容器实例&#xff0…

搭建docker私有化仓库Harbor

Docker私有仓库概述 Docker私有仓库介绍 Docker私有仓库是个人、组织或企业内部用于存储和管理Docker镜像的存储库。Docker默认会有一个公共的仓库Docker Hub,而与Docker Hub不同,私有仓库是受限访问的,只有授权用户才能够上传、下载和管理其中的镜像。这种私有仓库可以部…

本地视频进度加入笔记+根据进度快速锁定视频位置

本地视频进度记录快速回溯 引言 在学习的过程中, 如果我们想快速记录当前看视频的位置, 后续回溯查找就会非常方便了。 实现效果 进度记录 通过按下快捷键ctrlaltu&#xff0c; 快速记录当前视频的进度信息,然后复制到typora软件内 快速回溯 在typora软件内, 选中视频索引…

网络传输层TCP协议

传输层TCP协议 1. TCP协议介绍 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是一个要对数据的传输进行详细控制的传输层协议。 TCP 与 UDP 的不同&#xff0c;在于TCP是有连接、可靠、面向字节流的。具体来说&#xff0c;TCP设置了一大…

《自动驾驶与机器人中的SLAM技术》ch7:基于 ESKF 的松耦合 LIO 系统

目录 基于 ESKF 的松耦合 LIO 系统 1 坐标系说明 2 松耦合 LIO 系统的运动和观测方程 3 松耦合 LIO 系统的数据准备 3.1 CloudConvert 类 3.2 MessageSync 类 4 松耦合 LIO 系统的主要流程 4.1 IMU 静止初始化 4.2 ESKF 之 运动过程——使用 IMU 预测 4.3 使用 IMU 预测位姿进…

基于大语言模型的组合优化

摘要&#xff1a;组合优化&#xff08;Combinatorial Optimization, CO&#xff09;对于提高工程应用的效率和性能至关重要。随着问题规模的增大和依赖关系的复杂化&#xff0c;找到最优解变得极具挑战性。在处理现实世界的工程问题时&#xff0c;基于纯数学推理的算法存在局限…

【数据库】Unity 使用 Sqlite 数据库

1.找到需要三个 DLL Mono.Data.Sqlite.dllSystem.Data.dllsqlite3.dll 上面两个dll可在本地unity安装目录找到&#xff1a; C:\Program Files\Unity\Hub\Editor\2022.3.xxf1c1\Editor\Data\MonoBleedingEdge\lib\mono\unityjit-win32 下面dll可在sqlite官网下载到&#xff…

冒泡排序基础与实现

目录 1. 原理图 ​编辑 2. 什么是冒泡排序 3. 工作原理 3.1 具体步骤 3.2 时间复杂度 3.3 空间复杂度 4. 代码实现 5. 总结 1. 原理图 2. 什么是冒泡排序 冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单的排序算法&#xff0c;它通过重复地遍历要排序的列表&am…

忘记了PDF文件的密码,怎么办?

PDF文件可以加密&#xff0c;大家都不陌生&#xff0c;并且大家应该也都知道PDF文件有两种密码&#xff0c;一个打开密码、一个限制编辑密码&#xff0c;因为PDF文件设置了密码&#xff0c;那么打开、编辑PDF文件就会受到限制。忘记了PDF密码该如何解密&#xff1f; PDF和offi…

【论文笔记】Sign Language Video Retrieval with Free-Form Textual Queries

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: Sign Language Video Retr…

openEuler22.03系统使用Kolla-ansible搭建OpenStack

Kolla-ansible 是一个利用 Ansible 自动化工具来搭建 OpenStack 云平台的开源项目&#xff0c;它通过容器化的方式部署 OpenStack 服务&#xff0c;能够简化安装过程、提高部署效率并增强系统的可维护性。 前置环境准备&#xff1a; 系统:openEuler-22.03-LTS-SP4 配置&…

记录一下vue2项目优化,虚拟列表vue-virtual-scroll-list处理10万条数据

文章目录 封装BrandPickerVirtual.vue组件页面使用组件属性 select下拉接口一次性返回10万条数据&#xff0c;页面卡死&#xff0c;如何优化&#xff1f;&#xff1f;这里使用 分页 虚拟列表&#xff08;vue-virtual-scroll-list&#xff09;&#xff0c;去模拟一个下拉的内容…