在理解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