首页 > 数据库 >数据库损坏指南(2)--B-Tree Index损坏

数据库损坏指南(2)--B-Tree Index损坏

时间:2023-01-22 13:12:15浏览次数:37  
标签:Index 内部 -- 查询 损坏 索引 CTID 根页

在理解PostgreSQL索引损坏之前,要理解PostgreSQL是如何实现b-tree索引的。

B-tree索引结构

PostgreSQL中,B-tree索引结构是根据Lehman和Yao的高并发B-tree算法实现的。逻辑上,B-tree索引的层次结构由四种类型的页组成:

·元数据页

·根页

·内部页

·叶子页

1.元数据页

这是索引的第一个页,主要包含了元数据信息,比如索引的类型,也被称作0页。可以从元数据页获取到根页的地址。如果这个页损坏了或者不可访问了,就不可以访问索引了。

2.根页和内部页

根页是第一个包含指向内部页和叶子页链接的页。在内部,内部页和根页没有区别,内部页也包含指向其它内部页的指针。唯一的区别是,每个索引只有一个根页,内部页可能却有很多个。

根页和内部页不包含访问表记录的信息。

3.叶子页

叶子页位于索引树的最后一层。他们包含访问表数据的真正信息。存储了索引的值和CTIDs(行版本在其表中的物理位置)。

下图是典型的B-tree索引结构:

如上图所示,根页和内部页具有与内部页或叶子页链接的元组。每个内部页和叶子页的值都应该大于或等于前一页链接的值,并且下一页的值应该大于该页中的关联值。每个根页和内部页中的第一个元组是空白的;它指向包含所有低于其右邻的值的页。

例如,在上图中:

·根页有三个元组(空白、40、80),"内部页1"与值为空白的元组相关联,根页中下一个更高的值为40。因此,这里的"内部页面1"包含小于40的值。

·"内部页2"包含大于或等于40且小于80的值。

 

案例学习

在索引损坏的情况下,它根据数据损坏的位置给出不同的结果。损坏可能存在于任何页面(根页、内部页和叶子页)。但是,只要仔细研究,人们就会明白,损坏可能并不总是会暴露它的存在。有时,它也会误导用户。如果页头或格式损坏,则在执行查询本身时抛出错误。但是,当页面内部的实际数据而不是格式被损坏时,它不能检测到它被损坏,它会返回一些结果。

在这,我故意选择了一个没有任何内部页的索引,以便读者可以理解内部页不一定出现在每个b树索引中。例如,这里使用的索引是一个主键索引。

本节讨论查询由于损坏而返回不正确结果的情况。在这里,我们将深入研究叶子页和内部页的数据损坏。

 

案例1 -数据被损坏并变得无法检索

这是一个典型的损坏案例,Postgres试图查找一条记录,但无法发现它,因为该值不属于当前的父节点。在这种情况下,在一个叶子页中,两个相邻的位相互交换。详情如下。

在这里,我们从表中随机选择一个数字,并在其上准备一个案例。以下是记录及相关细节。下面快照中描述的记录(id = 612)将是我们描述损坏的目标。

我们在corruption_test的id列上有一个索引,正如下面所描述的,带下划线的位(第10位和第11位)的位置在索引中交换。因此,实际值从612变成1124;但是,关联表中的值仍然是相同的。

表名:corruption_test
索引名:test_tbl_pkey
损坏的页:叶子页第2页
表中相关的CTID:(101,6)
索引元组中的实际值: 00000010 01100100 (612)
损坏后索引元组中的值:00000100 01100100 (1124)

下图描述了test_tbl_pkey索引中实际发生的情况。

现在,我们将看到当我们请求检索与这个案例相关的值的数据时,查询如何响应。

​使用原始编号查询表:

如上所述,实际的(未损坏的)值是612。让我们看看使用"id = 612"谓词查询得到的结果。

现在,如果我们使用之前在表中提取的CTID搜索数据呢?

这足以让你感到惊讶,因为记录实际上存在于表中,但是,当我们直接使用特定值查询它时,它仍然无法检索它。
这是因为在索引损坏后,612被1124取代了。这里,搜索执行索引扫描以轻松获取记录,而索引无法定位所需的记录;因此,它无法显示任何数据。下面描述这种情况。

使用损坏的编号查询表:

根据我们的理解,612在索引中是不存在的,所以不可能从索引中得到正确的值。但是,我们知道1124存在于索引中。想知道查询1124时会发生什么。

让我们深入了解一下,为什么在查询损坏的数据时没有得到任何记录。

在这里,根据索引的有效结构,1124应该在Leaf page 3中,但在那里可能找不到它,因为它既不存在于页中,也不存在于表中。虽然Leaf page 2包含了关于1124的详细信息,但PostgreSQL不会在Leaf page 2中搜索1124,因为它在逻辑上不满足于在该页中探索1124的值;因此,将无法找到该值。​

下图描述了这种情况。

 

 

案例2 -错误的数据指针(CTID)

在这种情况下,PostgreSQL尝试从索引中找到一条记录,它可以找到这条记录,但显示了一条错误的记录。这是因为错误的CTID值存储在索引中。因此,它显示了一个完全不同的记录。

在这里,CTID改变了一个位。我们从表中随机选择一条记录(id = 245)作为描述测试用例的主题。下面的快照是记录的详细信息。

表名:corruption_test
索引名:test_tbl_pkey
损坏页:叶子页1
表中相关CTID:(40,5)
CTID的值:245
索引元组中的实际CTID:(40,5) - (00101000,00000101)
损坏后索引元组中的CTID:(56,5) - (00111000,00000101)

如上所述,由于第5位值的变化,它存储了一个不同的CTID。该案例如下所述。

使用可疑编号查询表:

如上所述,这里的可疑值为245。让我们看看使用"id = 245"谓词查询得到的结果。

这是令人震惊和困惑的,因为返回的数据与我们预期的完全不同。无需解释它对日常业务的影响。

在这里,在一个索引中,我们可以观察到CTID存储的245指向不同的值。查询在这里返回不同的值。

仅通过id查询表:

如果在查询中仅选择id列,将显示正确的结果。

得到正确结果的原因是它执行"仅索引扫描",因为索引存在于id列上。因此,不需要访问表数据来显示记录,因为索引包含了所有必需的记录。

 

案例3 -不正确的索引格式

每个数据库对象都有一个预定义的格式。PostgreSQL(或任何其他数据库)以特定的格式读取和写入,这种格式只是字符序列(或字节)。如果一个或多个字符被更改,对象将变得不可读。在这种损坏情况下,查询立即返回一个错误,并且错误文本每次都不相同。

让我们看看错误的各种情况。

损坏索引的元数据页:

如上所述,元数据页是任何索引的核心页;它存储索引的元数据。因此,如果元数据页中存在任何与格式相关的损坏,则索引将无法识别。因此,查询不会返回任何结果。

下面显示了查询特定记录时的结果。

下面的查询显示了查询整个表时的结果。

这里有一个圈套!如果查询执行索引扫描,则不接收数据是可以理解的。但是,当查询不期望使用索引时,由于索引损坏而无法交付时,这是令人不安的。但是,这要归因于计划器。

 

损坏的是非元数据页:

如果非元数据页(根页、内部页或叶子页)中存在与格式相关的损坏,则返回不同的错误。但是,它不会影响执行顺序扫描的查询

 

索引坏块的原因

大多数问题是由硬件故障或硬件问题引起的,以下是最可能的原因。

·RAID盘或RAID控制器故障
·磁盘或RAM有问题
·硬盘无掉电保护
·CPU过载
·PostgreSQL/OS的bug

另外,还有一些用户造成的问题。其中一些如下所示。

·使用signal 9杀掉连接
·突然停止PostgreSQL
·拷贝文件进行备份,没有用pg_start_backup
·执行pg_resetwal

坏块不是经常发生的事情;然而,在出现硬件故障或bug的情况下,这就变成了例行公事。

 

索引坏块的检测

当索引中的数据损坏时,很难检测损坏(尽管也有例外)。

当b-树索引的格式损坏时,我们可以使用amcheck和pageinspect扩展函数来验证它的格式。

我们可以遍历索引的所有页面,并列出损坏的页面。然而,这几乎没有意义,因为我们不能在索引中更改数据。

 

索引坏块的修复

从b树索引中移除坏块不需要彻底的分析;这只是重新创建一个索引的问题。可以通过以下选项来执行。​

·删除并重新创建索引
·使用reindex命令
·使用pg_repack进行重建​

在所有这些选项中,pg_repack是最可行的选项,因为它并行创建索引,因此运行的操作不会受到影响。

我们可以使用校验和特性将损坏记录到数据库中。在执行initdb时,我们可以使用-k选项启用校验和,以后需要保持data_checksum参数启用。pg_stat_database视图将记录每个损坏。如果我们已经知道损坏存在的位置,就有可能在它传播给用户之前采取必要的行动。

标签:Index,内部,--,查询,损坏,索引,CTID,根页
From: https://www.cnblogs.com/abclife/p/17061325.html

相关文章

  • ffmpeg视频抽帧
    视频文件是常见的一类数据,相关的任务包括视频物体检测、视频物体追踪、视频分类、视频检索和视频摘要抽取等。视频数据与图像数据非常类似,都是由像素点组成的数据。在非音......
  • SQL 算术函数
    1.前言解释:SQL内置的算术函数主要用于对数值类型的字段进行算数运算。本小节,我们将一起学习SQL函数中的算术函数。有时候,我们需要对数据表中的数值字段进行算术运算,......
  • 1、IT运营:开篇 - IT运营系列文章
          笔者在实际的工作中有面试一家公司的运营经理,同时,在博客中也有相关的运营博文进行过记录。前面也开始写相关系列的文章,而此系列主要是根据笔者的学习及总结的......
  • Minimum Cost to Split an Array
    MinimumCosttoSplitanArrayYouaregivenanintegerarray nums andaninteger$k$.Splitthearrayintosomenumberofnon-emptysubarrays.Thecost of......
  • 用Python下载第一次陷入小说
    #-*-coding:utf-8-*-importrequestsimportreimportos#判断有没有该文件夹,没有加创建一个ifnotos.path.exists('青春无悔'):os.mkdir('青春无悔')ur......
  • js 操作视频帧
    MDNhttps://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Manipulating_video_using_canvasfunctiongetVideoBase64(url){ returnnewPromise(function(res......
  • Statue状态定义
    Statue状态定义#include<stdio.h>/*状态码*/#defineTRUE1//真/是#defineFALSE0//假/否#defineOK1//通过/成功......
  • Python运行时CPU占用不同的问题
    提问: 同一个Python文件,双击运行和在IDLE里Run所占用的CPU资源相差很多    如上图,一个是双击运行的,另一个是IDLE里运行的,CPU资源占用的区别很明显解答: 它们的......
  • IOI
    IOI2022鲶鱼塘(2)记第\(i\)列的堤为\([0,l_{i}),\)贡献区间为\([l_{i},r_{i}]\),则限制即\(l_{i}>r_{i}\)或\(r_{i}<\max(l_{i-1},l_{i+1})\)若\(l_{i}>r_{i}\)或\(r_{i......
  • JavaScript学习笔记—数组
    1.描述数组也是一种复合数据类型,在数组中可以存储多个不同类型的数据数组中存储的是有序的数据,数组中的每个数据都有一个唯一的索引,可以通过索引来操作获取数据数组中......