存储介质故障指的是读取或写入数据的存储介质出现的物理上的故障。这是DBA不得不重点关注的问题之一。

一个典型的介质故障就是磁盘的磁盘头损坏,这会导致磁盘中存储的所有文件丢失。与数据库关联的文件例如数据文件、wal日志文件和控制文件都会因为磁盘崩溃而损坏。

这篇文章我们将重点介绍在PostgreSQL中遇到磁盘故障后,我们该如何在故障后找回数据(并不是还原备份)。

1、PostgreSQL checksum概述

  • 第1章:什么是checksum?
  • 第2章:PostgreSQL checksum:实际实现
  • 第3章:如何解决PostgreSQL损坏的页面问题?

在PostgreSQL9.3中引入了checksum,此后发生了许多变化。现在,我们在PostgreSQL12中有了一个完善的视图,可以通过pg_checksums的来获取关于checksum的详细信息。

1.1、什么是checksum?

启用checksum后,会将一个小的整数校验和写入Postgres存储在硬盘驱动器上的每个“page”的数据中。读取该块后,将重新计算校验和值并将其与存储的值进行比较。

这可以检测到数据损坏,而这些数据损坏(没有checksum)可能长时间潜伏在数据库中。

因此我们可以知道:使用checksum可以检验数据块的损坏。

1.2、checksum如何工作?

PostgreSQL主要在进出缓冲区缓存的过程中维护页面有效性。

从这里我们可以理解,PostgreSQL页面在离开或进入共享缓冲区之前必须经过OS Cache。因此,页面有效性发生在离开共享缓冲区之前和进入共享缓冲区之前。

当PostgreSQL尝试将页面复制到其缓冲区高速缓存中时,它将(如果可能)检测到错误,并且将不允许页面使用无效的8k页面进入共享缓冲区,并弹出报错需要该页面的所有查询用于处理错误消息:

ERROR: invalid page in block 0 of relation base/13455/16395

如果您已经有一个块,而该块在磁盘中具有无效数据,而其page在缓冲区中,则在下一个检查点期间,当其page被调出时,它将更新无效的校验和详细信息,但是在实时环境中很少这样做。

如果无效数据是PostgreSQL数据库缓冲区高速缓存的一部分,则PostgreSQL将会假设没有错误,并尝试处理页面上的数据。结果不可预测;有时您会得到一个错误,有时您可能会得到错误的数据。

1.3、PostgreSQL如何检查页面有效性?

在典型的页面中,如果启用了数据校验和,则信息将存储在2个字节的字段中,该字段包含页面标头之后的标志位。

随后是三个2字节整数字段(pd_lower,pd_upper和pd_special)。它们包含从页面开始到未分配空间的开头,到未分配空间的结尾以及到特殊空间的开头的字节偏移。

checksum的值通常以零开始,并且每次读取该块时,都会重新计算校验和值并将其与存储的值进行比较。这将检测到数据损坏。

当块位于共享缓冲区中时,不会为它们维护checksum。因此,如果使用pageinspect查看PostgreSQL页面缓存中的缓冲区,并且看到校验和值,请注意,当您在已存在于缓冲区中的页面上进行检查时,您可能无法获得实际的checksum值。当页面从缓冲区高速缓存中写出到操作系统页面高速缓存中时,将计算checksum值并将其标记在页面上。

2、checksum使用实例

这里创建一张名为check_corruption的表,然后进行以下操作。

表的大小是8 kB。
有5条记录。
使用的版本是PostgreSQL v12。

postgres=# select * from check_corruption;
aid | bid | abalance | filler
-----+-----+----------+--------------------------------------------------------------------------------------
1 | 1 | 0 | This is checksum example, checksum is for computing block corruption
2 | 1 | 0 | This is checksum example, checksum is for computing block corruption
3 | 1 | 0 | This is checksum example, checksum is for computing block corruption
4 | 1 | 0 | This is checksum example, checksum is for computing block corruption
5 | 1 | 0 | This is checksum example, checksum is for computing block corruption
(5 rows)postgres=# SELECT * FROM page_header(get_raw_page('check_corruption',0));lsn    | checksum | flags | lower | upper | special | pagesize | version | prune_xid
-----------+----------+-------+-------+-------+---------+----------+---------+-----------0/17EFCA0 |        0 |     0 |    44 |  7552 |    8192 |     8192 |       4 |         0
(1 row)postgres=# \dt+ check_corruption
List of relations
Schema | Name | Type | Owner | Size | Description
--------+------------------+-------+----------+------------+-------------
public | check_corruption | table | postgres | 8192 bytes |
(1 row)postgres=# select pg_relation_filepath('check_corruption');pg_relation_filepath
----------------------base/13455/16490
(1 row)

2.1、检查是否启用checksum

[postgres@stagdb ~]$ pg_controldata -D /u01/pgsql/data | grep checksum
Data page checksum version: 0

可以看到checksum被禁用了。

2.1.1、让我们在启用页面checksum
语法:

pg_checksums -D /u01/pgsql/data –enable –progress –verbose[postgres@stagdb ~]$ pg_checksums -D /u01/pgsql/data --enable --progress --verbose
pg_checksums: checksums enabled in file "/u01/pgsql/data/global/2847"
pg_checksums: checksums enabled in file "/u01/pgsql/data/global/1260_fsm"
pg_checksums: checksums enabled in file "/u01/pgsql/data/global/4175"..
..23/23 MB (100%) computed
Checksum operation completed
Files scanned: 969
Blocks scanned: 3006
pg_checksums: syncing data directorypg_checksums: updating control file
Data checksum version: 1
Checksums enabled in cluster

再检查是否启用checksum:
[postgres@stagdb ~]$ pg_controldata -D /u01/pgsql/data | grep checksum
Data page checksum version: 1

我们可以使用–disable选项禁用校验和:
[postgres@stagdb ~]$
[postgres@stagdb ~]$ pg_checksums -D /u01/pgsql/data --disable
pg_checksums: syncing data directory
pg_checksums: updating control file
Checksums disabled in cluster

我们首先检查当前数据目录中是否有错误,然后再处理数据。

2.1.2、要检查PostgreSQL页面错误,我们使用以下命令
pg_checksums -D /u01/pgsql/data –check

[postgres@stagdb ~]$ pg_checksums -D /u01/pgsql/data –check
Checksum operation completed
Files scanned: 969
Blocks scanned: 3006
Bad checksums: 0
Data checksum version: 1
[postgres@stagdb ~]$

警告:不要在生产环境进行以下的操作去研究!

由于表check_corruption数据文件为16490,因此我将使用操作系统的dd命令来破坏该文件。

dd bs=8192 count=1 seek=1 of=16490 if=16490

[postgres@stagdb 13455]$ dd bs=8192 count=1 seek=1 of=16490 if=16490

再次查看该表数据:

postgres=# select * from check_corruption;
aid | bid | abalance | filler
-----+-----+----------+--------------------------------------------------------------------------------------
1 | 1 | 0 | This is checksum example, checksum is for computing block corruption
2 | 1 | 0 | This is checksum example, checksum is for computing block corruption
3 | 1 | 0 | This is checksum example, checksum is for computing block corruption
4 | 1 | 0 | This is checksum example, checksum is for computing block corruption
5 | 1 | 0 | This is checksum example, checksum is for computing block corruption
(5 rows)

我们得到了上面的结果,这是为什么呢?

我从共享缓冲区得到了结果。让我重新启动集群并获取相同的集群。

/usr/local/pgsql/bin/pg_ctl restart -D /u01/pgsql/data
postgres=# select * from check_corruption;
aid | bid | abalance | filler
-----+-----+----------+--------------------------------------------------------------------------------------
1 | 1 | 0 | This is checksum example, checksum is for computing block corruption
2 | 1 | 0 | This is checksum example, checksum is for computing block corruption
3 | 1 | 0 | This is checksum example, checksum is for computing block corruption
4 | 1 | 0 | This is checksum example, checksum is for computing block corruption
5 | 1 | 0 | This is checksum example, checksum is for computing block corruption
(5 rows)

又是一样的值,为什么呢?

如前所述,在重新启动期间,PostgreSQL用共享缓冲区的值替换了错误checksum值。

我们如何触发checksum警告?

我们需要从共享缓冲区中删除该行。在此测试方案中,最快的方法是重新启动数据库,然后确保在进行磁盘上修改之前,我们甚至没有查看(如SELECT)表。完成后,checksum值将失败,并且我们将按预期接收checksum错误:

停止数据库服务,损坏磁盘数据,然后重启:
/usr/local/pgsql/bin/pg_ctl stop -D /u01/pgsql/data

dd bs=8192 count=1 seek=1 of=16490 if=16490

/usr/local/pgsql/bin/pg_ctl start -D /u01/pgsql/data

下一次查看时,我得到了下列报错:

postgres=# select * from check_corruption;
2020-02-06 19:06:17.433 IST [25218] WARNING: page verification failed, calculated checksum 39428 but expected 39427
WARNING: page verification failed, calculated checksum 39428 but expected 39427
2020-02-06 19:06:17.434 IST [25218] ERROR: invalid page in block 1 of relation base/13455/16490
2020-02-06 19:06:17.434 IST [25218] STATEMENT: select * from check_corruption;
ERROR: invalid page in block 1 of relation base/13455/16490

2.1.3、让我们更深入地研究问题并确认该数据块已损坏
您可以通过两种方法来查找问题,其中包括Linux命令,例如
dd
od
hexdump

使用dd命令:

dd if=16490 bs=8192 count=1 skip=1 | od -A d -t x1z -w16 | head -1

[postgres@stagdb 13455]$ dd if=16490 bs=8192 count=1 skip=1 | od -A d -t x1z -w16 | head -2
1+0 records in
1+0 records out
8192 bytes (8.2 kB) copied, 4.5e-05 seconds, 182 MB/s
0000000 00 00 00 00 a0 fc 7e 01 03 9a 00 00 2c 00 80 1d >……~……,…<

00 00 00 00 a0 fc 7e 01前8个字节为pd_lsn,后两个字节为03 9a为checksum值。

Using hexdump : hexdump -C 16490 | head -1

[postgres@stagdb 13455]$ hexdump -C 16490 | head -1
00000000 00 00 00 00 a0 fc 7e 01 03 9a 00 00 2c 00 80 1d |……~……,…|

hexdump和dd返回相同的结果。

让我们了解一下我们的PostgreSQL自己的pg_checksums怎么说?

pg_checksums -D /u01/pgsql/data –check

[postgres@stagdb 13455]$ pg_checksums -D /u01/pgsql/data –check
pg_checksums: error: checksum verification failed in file “/u01/pgsql/data/base/13455/16490”, block 1: calculated checksum 9A04 but block contains 9A03
Checksum operation completed
Files scanned: 968
Blocks scanned: 3013
Bad checksums: 1
Data checksum version: 1

在这里,根据pg_checksums,校验和9A03与hexdump的校验和9A03相匹配。

将十六进制9A03转换为十进制,我得到了39427。

这刚好和前面的报错相匹配:

2020-02-06 19:06:17.433 IST [25218] WARNING: page verification failed, calculated checksum 39428 but expected 39427

3、如何解决PostgreSQL损坏页面的问题?

使用以下函数查找页面损坏的确切位置。

CREATE OR REPLACE FUNCTION
find_bad_row(tableName TEXT)
RETURNS tid
as $find_bad_row$
DECLARE
result tid;
curs REFCURSOR;
row1 RECORD;
row2 RECORD;
tabName TEXT;
count BIGINT := 0;
BEGIN
SELECT reverse(split_part(reverse($1), '.', 1)) INTO tabName;
OPEN curs FOR EXECUTE 'SELECT ctid FROM ' || tableName;
count := 1;
FETCH curs INTO row1;
WHILE row1.ctid IS NOT NULL LOOP
result = row1.ctid;
count := count + 1;
FETCH curs INTO row1;
EXECUTE 'SELECT (each(hstore(' || tabName || '))).* FROM '
|| tableName || ' WHERE ctid = $1' INTO row2
USING row1.ctid;
IF count % 100000 = 0 THEN
RAISE NOTICE 'rows processed: %', count;
END IF;
END LOOP;
CLOSE curs;
RETURN row1.ctid;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'LAST CTID: %', result;
RAISE NOTICE '%: %', SQLSTATE, SQLERRM;
RETURN result;
END
$find_bad_row$
LANGUAGE plpgsql;

现在,使用find_bad_row()函数,您可以找到损坏位置的ctid。

使用该函数前需要安装hstore扩展。

postgres=# CREATE EXTENSION hstore;
CREATE EXTENSION
postgres=#
postgres=# select find_bad_row(‘check_corruption’);
2020-02-06 19:44:24.227 IST [25929] WARNING: page verification failed, calculated checksum 39428 but expected 39427
2020-02-06 19:44:24.227 IST [25929] CONTEXT: PL/pgSQL function find_bad_row(text) line 21 at FETCH
WARNING: page verification failed, calculated checksum 39428 but expected 39427
NOTICE: LAST CTID: (0,5)
NOTICE: XX001: invalid page in block 1 of relation base/13455/16490
find_bad_row
————–
(0,5)
(1 row)

删除特定的CTID将解决问题。

postgres=# delete from check_corruption where ctid='(0,6)’;
DELETE 1
postgres=#

如果删除ctid不适用于您,则您可以使用替代解决方案,该解决方案是设置zero_damaged_pa​​ges参数。

例如:

postgres=# select * from master;
WARNING: page verification failed, calculated checksum 8770 but expected 8769
ERROR: invalid page in block 1 of relation base/13455/16770
postgres=#

由于块已损坏,我无法从表主访问数据。

解决方法:

postgres=# SET zero_damaged_pages = on;
SET
postgres=# vacuum full master;postgres=# select * from master;
WARNING: page verification failed, calculated checksum 8770 but expected 8769
WARNING: invalid page in block 1 of relation base/13455/16770; zeroing out page
id | name | city
----+---------+-----------
1 | Orson | hyderabad
2 | Colin | chennai
3 | Leonard | newyork

在这里,它清除了损坏的页面并给出了其余结果。

zero_damaged_pages (boolean):
检测到损坏的页眉通常会导致PostgreSQL报告错误,从而中止当前事务。将zero_damaged_pa​​ges设置为on会导致系统改为报告警告,将内存中的损坏页面清零,然后继续处理。此行为将破坏数据,即损坏页面上的所有行。但是,它确实允许您克服错误并从表中可能存在的任何未损坏页面中检索行。如果由于硬件或软件错误而发生损坏,它对于恢复数据很有用。通常,除非您已放弃从表的损坏页面中恢复数据的希望,否则不应该将其设置为on。调零的页面不会强制插入磁盘,因此建议在重新关闭此参数之前重新创建表或索引。默认设置为关闭,并且只能由超级用户更改。

但是,使用此功能时需要注意两点。首先,使用checksum会降低性能,因为它会为每个数据页引入额外的计算(默认为8kB),因此请注意在使用数据时在数据安全性和性能之间进行权衡。

启用checksum时,有许多因素会影响速度变慢,其中包括:

从shared_buffers读取数据的可能性有多高,这取决于设置了shared_buffers的大小以及内部有多少活动数据库。
您的服务器通常运行多快,以及它(以及您的编译器)如何优化校验和计算。
您有多少个数据页(可能受数据类型影响)。
您编写新页面的频率(通过COPY,INSERT或UPDATE)。
您多久读取一次值(通过SELECT)

共享缓冲区使用得越多(有效地使用它们是一个好的总体目标),checksum就越少,并且checksum对数据库性能的影响就越小。平均而言,如果启用checksum,则性能成本将超过2%,而对于插入,则平均差异为6%。对于选择,该值跳升到19%。完整的计算基准测试可在此处找到。

您可以在测试前后使用pg_filedump转储文件的内容,并且可以使用diff命令来分析数据损坏:

  1. pg_filedump -if 16770 > before_corrupt.txt
  2. corrupt the disk block
  3. pg_filedump -if 16770 > before_corrupt.txt
  4. diff or beyond compare both the files.

PostgreSQL checksum与Data Corruption相关推荐

  1. PostgreSQL checksum

    在计算机系统中,checksum 通常用于校验数据在传输或存取过程中是否发生错误.PostgreSQL 从 9.3 开始支持 checksum,以发现数据因磁盘. I/O 损坏等原因造成的数据异常.本 ...

  2. Verifying Checksum ... Bad Data CRC ERROR: can#39;t get kernel image!

    解决方法一: 有可能是你写地址和读地址范围错了,代码重叠了,还有可能是你的内核大于你分配的内核空间,重新规划下内核空间就OK. 解决方法二: nand flash出现了坏块,换一段地址空间试试.

  3. PostgreSQL 11 preview - Allow on-line enabling and disabling of data checksums

    标签 PostgreSQL , checksum , online modify , pg_verify_checksums , pg_enable_data_checksums , pg_disab ...

  4. PostgreSQL 之 流复制主备库切换

    作者:瀚高PG实验室 (Highgo PG Lab)- 波罗 在主从复制的两个节点中,当主节点数据库服务访问异常的时候后,可以手动通过promote 命令在从节点执行将从节点状态切换到可读写的状态,变 ...

  5. Postgresql中的sync相关参数源码分析

    注:本专栏所有分析以函数为主线,必要数据结构会带入讲解:数据库版本为Postgresql10.16. 注:如有讨论的需要请email to jackgo73@outlook.com 一.问题 Post ...

  6. PostgreSQL 数据库备份与恢复介绍

    防止数据库数据丢失的最重要的方法就是备份.造成数据丢失可能的原因有很多种,例如服务器的硬件损坏,而有的是人为的原因导致的(例如误删数据),还有的就是应用程序的bug导致数据误删.因此关于数据库的备份与 ...

  7. 《PostgreSQL 9.0性能调校》一一2.1 平衡硬件支出

    本节书摘来自异步社区出版社<PostgreSQL 9.0性能调校>一书中的第2章,第2.1节,作者: [美]Gregory Smith,更多章节内容可以访问云栖社区"异步社区&q ...

  8. postgresql基本操作

    连接数据库: psql -h 127.0.0.1 -U postgres -d postgres -p 5432 创建用户: CREATE USER sec WITH PASSWORD 'sec'; ...

  9. 如何在Mac OS X上启动PostgreSQL服务器?

    最终更新: 我忘了运行initdb命令. </ FINAL UPDATE> 通过运行此命令 ps auxwww | grep postgres 我看到postgres没有运行 > p ...

  10. postgresql主从备份_基于PG12.2实现主从异步流复制及主从切换教程(下)

    概述 今天主要介绍如何搭建PG主从流复制及主从切换,仅供参考. PS:上篇的地址在文末链接. PostgreSQL数据库主从异步流复制搭建 环境说明: 1.安装PG数据库(主从库进行) 用脚本进行,略 ...

最新文章

  1. java生成函数excel_java实现在excel中创建及读取公式
  2. zeros--创建零矩阵
  3. c++ string 长度限制_String 有多长?
  4. flutter能开发游戏吗_Steam上架游戏开发软件,不用代码也能制作游戏,而且还是免费的...
  5. 【6】JAVA---地址App小软件(QueryPanel.class)(表现层)
  6. 今天的这个小成绩,需要向阿里云的朋友报告一下!
  7. 元素(HYSBZ-2460)
  8. mysql链接丢失_mysql 连接丢失错误解决(转载)
  9. Linux查看端口被那个进程占用
  10. linux8如何开启多个桌面,CentOS8安装GNOME3桌面并设置开机启动图形界面
  11. softmax与sigmoid函数的理解
  12. js逆向爬虫入门-01.微信公众平台模拟登录逆向
  13. PS带框的对号怎么打
  14. 20221118-数学函数图像在线工具推荐
  15. 第X届智能车常见问题汇总:(二)甩尾?智能车甩尾
  16. web前端知识——iframe标签、CSS
  17. 遇上Android客户端打包党,该怎么办?
  18. 转:SQL Server:获取当前日期是本月的第几周
  19. 命令控制qq自动申请远程控制
  20. python scipy.optimize.minimize多变量多参数优化

热门文章

  1. 【实战】通过命令行调用360杀毒软件接口对指定文件或文件夹杀毒
  2. 100行代码实现HarmonyOS“画图”应用,eTS开发走起
  3. 【c语言】产生一个1到1000的随机整数,用户进行猜测
  4. mac os x 使用教程_如何在Mac OS X计算机上使用扫描仪
  5. PLM -Aras Innovator 安装
  6. oel+oracle,OEL7.6安装Oracle Database 19C(VERSION 19.3.0.0)
  7. oracle+linux+oel+6.9,Oracle 11g(11.2.0.4) install on OEL6.7
  8. 基础知识系列博客——计算机组成原理
  9. 偏见与苛求在科技媒体中依旧根深蒂固
  10. scratch编程 超有趣反应力游戏