PostgreSQL技术内幕(七)索引扫描

索引概述

数据库索引,是将一个表的某些字段的数据进行重新组织的数据库对象。通过使用索引,可以大大加速数据库的一些操作,其背后的思想也很简单朴素:空间换时间。

数据库中的索引,可以类比为一本书的目录,当我们在书中查询某信息的时候,借助目录,可以快速定位到对应的章节,从而避免了从整本书中去翻阅,加速了查找的过程。

索引分类

Postgres 中常见的索引大致有下面的这几种,其中 BTree 索引是使用最广泛的,也是创建索引时默认的选项。

索引类型索引名称说明
btreeB+树索引B+ 树实现的索引类型,具有丰富的索引特性(多值、排序、聚簇等),增、删、改操作性能稳定,应用广泛,是默认的索引类型。
hash哈希索引Hash 索引基于哈希表结构,适用于等值比较查询。Hash 索引的查询速度非常快,但是不支持范围查询和排序操作。
gin通用倒排索引可以用于支持各种文本类型的查询。它适用于文本搜索、数组、范围查询等场景。GIN 索引在查询过程中会对匹配的值进行聚合,因此查询速度较快,但索引的更新和插入速度较慢。
gist通用搜索树索引GiST 索引是一种通用的空间索引,用于支持空间查询和范围查询。GiST 索引可以处理点、线、多边形等复杂的空间数据类型,因此适用于地理信息系统(GIS)等场景。GiST 索引的查询速度较快,但索引的更新和插入速度较慢。

索引扫描的例子

下面通过一个例子来体会索引对表扫描的性能的影响。我们首先创建一个测试表,例如叫 articles,并向其中插入一些测试的数据。

CREATE TABLE articles (  id SERIAL8 NOT NULL PRIMARY KEY,  a text,  b text,  c text);INSERT INTO articles(a, b, c)SELECTmd5(random()::text),md5(random()::text),md5(random()::text)from (  SELECT * FROM generate_series(1,1000000) AS id) AS x;

我们从这个表中查询一条数据,例如查找 a = '65c966eb2be73daf418c126df8dc33b5' 的数据,其查询计划如下:

可以看到这里使用了顺序扫描(Seq Scan),并且代价(Cost)是 22450。如果我们给字段 a 加上一个索引(默认是 BTree),create index on articles (a),然后再执行这个 sql 语句,其查询计划如下:可以看到这里使用到了索引扫描(Index Scan),并且代价是 8,相较于顺序扫描的 22450,查询的代价大大降低了,查询的性能由此得到了大幅的提升。

扫描方法

顺序扫描

当对无索引的字段进行查询,或者判断到查询将返回大多数数据时,查询优化器将会使用顺序扫描方法。还是以之前的 articles 表为例,这里我们查询了 id > 100 的数据,包含了大部分该表中的数据,所以尽管 id 列上有索引,但还是会使用顺序扫描。

索引扫描

如果判断到查询将会命中非常少量的数据时,查询优化器将会选择索引扫描方法,上面的例子已经有对应的展示了。下面是一个扫描索引范围的例子,可以看到命中数据占表数据的少量,选择索引扫描是最高效的。

位图索引扫描

尽管索引扫描的数据量一般较少,但是这个扫描需要随机 IO 操作,因此对比顺序扫描使用的顺序 IO 操作,它的代价并不总是更小。所以在命中适中数据(少量与多数之间),顺序扫描和索引扫描各自都有缺陷。针对这种情况,一般可以采用位图索引扫描,其原理是将需要访问的页面有序化,将随机 IO 转为顺序 IO。

大致操作步骤如下:

  • 使用索引扫描到满足条件的所有 TID

  • 用 TID 列表按照页面的访问顺序构建一个位图

  • 读取数据记录时,同一个页面只需要读取一次

下图描述了 Postgres 中几种表数据扫描的方式,查询优化器会根据计算的代价选择最优的扫描方法。

索引物理存储

postgres 中的索引是一种二级索引,即在物理存储上,索引数据和对应的表数据是分离开的。每个特定的索引对象都存储为了一张独立的关系表,并且都能够在 pg_class 系统表中查询到。

以 BTree 为例,其大致的结构如下:B+ 树的大致特点:

  • 树层级更少:每个内部节点不再存储数据,因此能存储的键值会更多,于是导致树的层级更少且查询数据也更快(减少了随机IO)。

  • 查询速度更稳定:因为所有数据都存在叶子节点上,因此每次查找的次数(树的高度次随机IO操作)都相同,查询速度也要更稳定。

  • 遍历查询更方便:B+Tree的叶子节点数据构成了一个有序链表,在遍历查询时,首先定位第一个键值的位置,然后沿着链表即可访问到全部数据。

BTree 中的每一个节点在物理结构上存储为一个 page,page 的结构和 heap 表的类似,如下:

以 BTree 为例,索引中的内容可以理解为一个由键值到数据元组 TID 的映射,其中 TID 由一个块号和偏移组成。

索引创建

当用户使用 create index on table (col) 语句后,将会经过语法解析、权限检查等阶段,然后建立索引关系,更新系统元数据,最后使用表中的数据构建一个完整的B-Tree 索引。

主要的函数调用路径如下:

ProcessUtility() Utility语句的处理入口DefineIndex() 定义一个索引(异常判断,准备index_create()的输入参数)index_create() 创建一个索引(建立关系文件并更新系统表数据)index_build() 构建索引的外层接口bt_build() B-Tree的索引构建逻辑
ProcessUtility数据库Utility语句的统一处理入口,对于创建索引,转发给DefineIndex函数来继续处理
DefineIndex主要功能是执行各类权限和异常情况判断,并初始化index_create函数所需要的各个参数
index_create主要功能是建立索引关系和系统表记录
index_build创建索引的外围接口,主要调用 index 对应的 ambuild 函数
btbuildBTree 索引的构建逻辑

以 BTree 为例,使用表中的数据来构建 B-Tree 索引总体分为两步,一是将表中的数据排序,二是根据有序的数据元组,遍历自底向上构建整个 BTree。

这里主要是会针对不同的索引类型,调用不同的 ambuild 方法,其中 BTree 对应的方法是 btbuild,下图是索引相关接口的访问关系,不同的索引访问方法通过 IndexAM 进行抽象,供上层执行器调用。

索引扫描

索引扫描在执行器中的三个步骤分别是

  • ExecInitIndexScan

  • ExecIndexScan

  • ExecEndIndexScan

ExecInitIndexScan

主要负责初始化索引扫描的状态结构体 IndexScanState 核心任务是将索引扫描的过滤条件转换为各种类型的扫描键 ScanKey。

  • ScanKey 主要存储了索引列的信息,操作函数以及待比较的函数,ScanKey 描述了一个完整的过滤条件,并用于索引扫描

  • 但如果过滤条件是一个复杂的表达式,引入了 iss_RuntimeKeys 来处理

IndexScanState 的主要字段:

类型字段描述
List*indexqualorig索引过滤条件
ScanKeyDataiss_ScanKeysQual 的右操作符为常量
IndexRuntimeKeyInfoiss_RuntimeKeys如果 Qual 的右操作符不是常量,需要在执行的过程中动态计算表达式的值,则将表达式信息存到 IndexRuntimeKey 中

Init 阶段主要关注的是 ExecIndexBuildScanKeys 方法,此方法的作用是将扫描过滤条件转化为各种类型的扫描键 ScanKey。

索引的过滤条件分为了以下五种情况:

  • 常数或普通运算,直接存入 ScanKey

  • 非常数的值表达式运算,此时执行器节点无法在初始阶段得到表达式的结果,需要暂时存入 iss_RuntimeKeys

  • RowCompareExpr,比如过滤条件是“(indexkey1,indexkey2)> (1,2)”,表示多个过滤条件的组合,遍历所有的子过滤条件,分别存入 iss_ScanKeys 或者 iss_RuntimeKeys

  • ScalarArrayOpExpr,比如过滤条件是“indexkey1 = ANY(1,10,20)”,如果索引支持处理基于数组的搜索,分别将常数存入 ScanKey 或者 RuntimeKey,如果不支持数组搜索,例如 Hash、GIN、Gist 索引,则将过滤条件存入 arrayKeys

  • NullTest,索引键是否为 NULL,例如_"indexkey IS NULL/IS NOT NULL",设置 ScanKey 对应的值即可_

ExecIndexScan

负责基于索引读取元组,并返回给执行器上层节点。函数 IndexNext 不断进行索引扫描,读取元组,并将元组封装进 TupleTableSlot 传递给上层节点。

  • 此函数的主要参数是 IndexScanDesc,保存了 scan 过程中的状态信息

  • 通过 xs_heap_continue 判断是否在 HOT 链上,如果是的话不做任何操作

  • 否则调用 index_getnext_tid 返回一个 TID

    • 在 pg_am 表中查找 amgettuple 对应的内层接口函数

    • 调用这个函数(例如 BTree 中的 btgettuple),根据具体的索引实现返回一个 TID
      • 调用 index_fetch_heap 获取实际的元组

ExecEndIndexScan

主要负责清理工作,释放计算 RuntimeKey 的内存上下文,并关闭相关索引表和数据表。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

远程代码执行渗透与防御

远程代码执行渗透与防御 1.简介2.PHP RCE常见函数3.靶场练习4.防御姿势 1.简介 远程代码执行漏洞又叫命令注入漏洞 命令注入是一种攻击,其目标是通过易受攻击的应用程序在主机操作系统上执行任意命令。 当应用程序将不安全的用户提供的数据(表单、cook…

如何在Java中创建临时文件?

在Java程序中,有时需要创建临时文件来暂存数据或者执行某些操作。Java提供了许多方式来创建临时文件。在本教程中,我们将介绍如何使用Java标准库来创建临时文件。 一、使用File.createTempFile()方法 Java标准库中的File类提供了createTempFile()方法来…

条款08: 别让异常逃离析构函数

文章目录 背景知识析构函数 背景知识 下面是一段测试代码&#xff1a; class Test { public:Test(int para){m_num para;};void test_throw(){throw(3);};~Test() {cout<<"delete Test"<<m_num<<endl;//test_throw();};int m_num; }; int main(…

无线耳机哪个音质比较好?四百内音质最好的无线耳机排行

蓝牙耳机常常作为手机的伴生产品而出现在人们的日常生活当中&#xff0c;其使用场景也越来越广泛。而随着蓝牙技术的发展&#xff0c;蓝牙耳机在音质上的表现也越来越好。下面&#xff0c;我来给大家推荐几款四百内音质最好的无线耳机&#xff0c;一起来看看吧。 一、南卡小音舱…

Java多线程基础面试总结(三)

线程的生命周期和状态 Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态&#xff1a; NEW&#xff1a;初始状态&#xff0c;线程被创建出来&#xff0c;但是还没有调用start()方法。RUNABLE&#xff1a;运行中状态&#xff0c;调用了start()…

什么是 AUTOSAR C++14?

总目录链接>> AutoSAR入门和实战系列总目录 总目录链接>> AutoSAR BSW高阶配置系列总目录 文章目录 什么是 AUTOSAR C14&#xff1f;AUTOSAR C14 规则和偏差静态分析工具可以完全支持自动 什么是 AUTOSAR C14&#xff1f; 它是 C 版本 14 (ISO/IEC 14882:2014…

layui框架学习(23:代码文本修饰模块)

Layui中的代码文本修饰模块layui.code主要用于修饰代码区域或文本行&#xff0c;其基本用法是使用预设类layui-code标识包含代码或文本的元素&#xff0c;然后调用layui.code函数渲染样式。Layui官网教程及示例中主要使用pre元素包含带修饰的代码或文本&#xff08;pre元素可定…

Qt在安卓手机输出‘hello,world‘

我也想实现这样的功能。 最开始的参考文章&#xff1a; (2条消息) Qt android 开发环境搭建_逝水流年丶轻染尘的博客-CSDN博客 方案1&#xff1a;(失败) 我之前已经下载过 Qt5.14.2了&#xff0c;所以我想直接添加组件 中间过程参考&#xff1a; (2条消息) Qt更新组件出现&…

Kubernetes 多集群管理工具Rancher 使用介绍

目录 一、Rancher 简介 2.1 Run Kubernetes Everywhere 2.2 满足 IT 需求规范 2.3 赋能 DevOps 开发团队 2.4 Rancher API Server 的功能 2.4.1授权和角色权限控制 2.4.2使用 Kubernetes 的功能 2.4.3 配置云端基础信息 2.4.4 查看集群信息 2.5 编辑下游集群 二、Ra…

在线免费把Markdown格式文件转换为PDF格式

用CSDN的MarkDown编辑器在线转换 CSDN的MarkDown编辑器说实话还是挺好用的。 导出PDF操作步骤&#xff0c;图文配合看&#xff1a; 在MD编辑模式下写好MarkDown文章或者直接把要转换的MarkDown贴进来&#xff1b; 使用预览模式&#xff0c;然后在预览文件上右键选择打印&…

部分面试题记录

Spring相关&#xff1a; 1. spring框架中的单例bean是线程安全的嘛&#xff1f; 1.1 bean单例多例配置&#xff1a; bean可以手动设置单例或者多例&#xff1a; Service Scope("singleton") public class UserServicelmpl implements UserService { }singleton:b…

溯源取证-Linux内存取证 中难度篇

原谅我这么晚才出来文章&#xff0c;因为最近忙着录课&#xff0c;至于为啥没有基础篇&#xff0c;是因为靶场里没看见&#xff0c;哈哈 这个也是研究了好几个晚上才出来的东西&#xff0c;此处场景为linux环境下的rootkit病毒&#xff0c;我们通过这篇文章可以通过内存取证发…

基于广泛数据增强的新型白质束ONE-SHOT分割

文章目录 One-Shot Segmentation of Novel White Matter Tracts via Extensive Data Augmentation摘要方法One-Shot分割的广泛数据增强 实验结果 One-Shot Segmentation of Novel White Matter Tracts via Extensive Data Augmentation 摘要 探索了新WM束的One-Shot分割问题由…

揭秘阿里新大招:大模型只是前菜

技术的代际变革往往对商业格局产生深远影响。眼下&#xff0c;随着产业各界对大模型的投入布局加深&#xff0c;一个业界共识逐渐诞生&#xff1a;大模型正在改变云生态&#xff0c;将为云计算行业带来新的奇点。 在过去的一段时间里&#xff0c;市场研究机构IDC在2022年底的预…

瑞吉外卖项目——读写分离

读写分离 读和写所有压力都由一台数据库承担&#xff0c;压力大数据库服务器磁盘损坏则数据丢失&#xff0c;单点故障 Mysql主从复制 介绍 MySQL主从复制是一个异步的复制过程&#xff0c;底层是基于Nysql数据库自带的二进制日志功能。 就是一台或多台MysQL数据库&#xf…

偶数科技发布实时湖仓数据平台 Skylab 5.0

2023 年 4 月 11 日&#xff0c; 偶数发布了最新的实时湖仓数据平台 Skylab 5.0&#xff0c;平台各个组件进行了全面升级。新版 Skylab 的发布标志着偶数科技具有从数据存储、计算、管理到分析、应用和 AI 能力的完整的数据管理生态闭环&#xff0c;帮助用户实现批流一体、实时…

LeetCode LCP 04. 覆盖【二分图最大匹配,匈牙利算法】困难

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

[Java]Session机制

什么是Session Session是另一种记录客户状态的机制&#xff0c;不同的是Cookie保存在客户端浏览器中&#xff0c;而Session保存在服务器上。客户端浏览器访问服务器的时候&#xff0c;服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需…

springboot整合redis

一、总体概述 1、redis配置文件 redis.conf配置文件&#xff0c;改完后确保生效&#xff0c;记得重启&#xff0c;记得重启 默认daemonize no 改为 daemonize yes 默认protected-mode yes 改为 protected-mode no 默认bind 127.0.0.1 改为 直接注释掉(默认bind 127.0.0.1只能…

ApplicationContextAware接口

一、ApplicationContextAware接口的基本介绍 public interface ApplicationContextAware extends Aware {void setApplicationContext(ApplicationContext applicationContext) throws BeansException;}在Spring/SpringMVC中&#xff0c;我们拿到IOC容器无非有三种方式&#x…