本文转自Greenplum中文社区官方网站:greenplum.cn;

最实时最权威的Greenplum技术文档、博客,以及热点新闻,请关注greenplum.cn;

Postgres内部提供了很多种索引的方式,满足不同的索引需求。

传统索引方式(B-Tree)

说到传统,B-Tree索引当仁不让,它也是postgres里的默认索引方式。B-Tree的中文名字是二叉平衡树,每个叶子结点的深度都是一样的,特点是允许在O(logn)的时间内对所存储的内容进行搜索、顺序访问、插入及删除。

B-Tree索引对于唯一值的索引来说是相当之理想的(比如id序号这类数据),它存储的是一对对的键值和指针,指针指向被索引数据所在的heap上的位置。

这里说下什么是heap。Heap(或者叫heap文件,用来区分同名不同义的数据结构heap)存储着postgres表里的所有数据(外部表除外)。heap里边的数据是无序的,这让数据库在向里面添加数据的时候不必考虑排序的问题。

目前postgres支持的索引中,只有B-Tree索引能够提供有序的查询结果(也就是支持Order by和Limit),支持高并发场景(并行scan),支持merge join和包含index scan的nested loop操作。

B-Tree是非常基本且古老的索引,但不代表它功能单一。

有时我们需要更多的功能,比如:表达式

看下这个例子:

CREATE TABLE customer (name) AS SELECT 'cust' || i

FROM generate_series(1, 1000) AS g(i);

CREATE INDEX i_customer_name ON customer (name);

/*看一下直接对B-Tree索引列做fitler的plan*/

EXPLAIN SELECT * FROM customer WHERE name = 'cust999';

QUERY PLAN

------------------------------------------------

Index Only Scan using i_customer_name on customer ...

Index Cond: (name = 'cust999'::text)

/* 没问题!使用了Index!另外,Index Only Scan的意思是,所有想要的column都在index里已经有了*/

/* 如果对B-Tree索引列在查询时增加一个表达式操作呢?*/

EXPLAIN SELECT * FROM customer WHERE lower(name) = 'cust999';

QUERY PLAN

-------------------------------------------------

Seq Scan on customer (cost=0.00..20.00 rows=5 width=7)

Filter: (lower(name) = 'cust999'::text)

/* 又变回Seq Scan了!? */

Postgres提供了表达式索引,你可以用表的一列或者多列组成的表达式,创建一个索引。

CREATE INDEX i_customer_lower ON customer (lower(name));

EXPLAIN SELECT * FROM customer WHERE lower(name) = 'cust999';

QUERY PLAN

-------------------------------------------------

Bitmap Heap Scan on customer (cost=4.32..9.66 rows=5 width=7)

Recheck Cond: (lower(name) = 'cust999'::text)

-> Bitmap Index Scan on i_customer_lower ...

Index Cond: (lower(name) = 'cust999'::text)

等一下,这个Bitmap Index Scan又是什么意思?

Postgres做涉及到索引的扫描的时候,会先在索引上扫描一遍,得出符合条件的row/block的list,再拿着这个list到表上做真正的取数据操作。但如果一张表上的查询涉及多个索引呢?每个索引都这么来一次岂不是很浪费时间?

Bitmap Index Scan就能很好解决这个问题。多个索引都扫描完毕后,符合每个索引过滤条件的row/block会用同一个bitmap来记录,然后只需要用这个bitmap一次性去heap表上取出数据就好。值得一提的是,Postgres优化器会自动做出是否要使用Bitmap Index Scan的判断,不需要任何人工干预。

下图是bitmap index的示意图:

部分索引

当一个表很大的时候,为每一行都建索引是很耗存储空间的。如果常用的数据只有一小部分的话,完全可以只为这部分数据建立索引,省时省力省空间。

  • 省时:当做Insert和Update操作的时候,额外开销更小!

  • 省力:热数据索引扫描更快!

  • 省空间:索引再也不用耗费大量磁盘了!

让我们看下怎么建立一个部分索引

ALTER TABLE customer ADD COLUMN state CHAR(2);

UPDATE customer SET state = 'AZ' WHERE name LIKE 'cust9__';

CREATE INDEX i_customer_state_az ON customer (state) WHERE state = 'AZ';

/* 一个WHERE语句,只对state = AZ的行进行索引 */

EXPLAIN SELECT * FROM customer WHERE state = 'PA';

QUERY PLAN

-------------------------------------------------

Seq Scan on customer (cost=0.00..17.50 rows=5 width=19)

Filter: (state = 'PA'::bpchar)

/* 当要查询的数据不在部分索引的范围内时,仍然使用Seq Scan */

EXPLAIN SELECT * FROM customer WHERE state = 'AZ';

QUERY PLAN

-------------------------------------------------

Bitmap Heap Scan on customer (cost=4.18..9.51 rows=5 width=19)

Recheck Cond: (state = 'AZ'::bpchar)

-> Bitmap Index Scan on i_customer_state_az ...

Index Cond: (state = 'AZ'::bpchar)

/* 当要查询的数据和部分索引一致时,索引就可以被用上了! */

Postgres这种带条件部分的索引,在mysql里仍不支持[https://dba.stackexchange.com/questions/43/how-to-create-a-conditional-index-in-mysql]。如果想在mysql中创建一个类似上面例子的索引,你需要依据原表创建一个辅助表,并增加3个triggers(update/insert/delete),当对原表有满足“条件”的数据变动时,相应操作也会由trigger在辅助表上触发。然后,对辅助表创建索引。相比postgres,mysql的方式不仅耗时耗空间,对用户来说操作也非常不友好。

非传统索引方式

除了被作为默认索引的B-Tree,Postgres还以插件的方式提供了好几种“新型索引”,用于满足各式各样的应用场景。下面我们逐个认识一下。

BRIN (Block-Range Index)

名字里有个range,顾名思义,这种索引存储的是heap表内每个block中数据的最大值和最小值。

BRIN索引有什么作用和特点呢?

  • 首先,如果一整个block里的数据都非常大或者非常小,不在我们查询的条件范围内,就可以将整个block排除出扫描范围。这种索引对那些分布是有序的数据非常有用,比如insert-only table的id或timestamp列。

  • 其次,因为只存储最大值和最小值,BRIN带来的空间消耗极小。另外,更新数据的时候,索引带来的额外操作只有比较操作,也是相当高效的。

  • BRIN对于含有多个列的索引是比较合适的选择。

但是,因为索引里存储的信息非常少,查找数据的时候消耗的时间也会很长,当查询的数据范围和一个block的范围值有重叠时,就需要对这个block进行scan,如果有重叠的block很多,其实跟全表扫描的差别也不大了。

GIN (Generalized Inverted Index)

  • 尤其适用于有很多key或value的数据的索引,比如说文档、JSON、整型数组等等。

  • 尤其适用于重复数据很多的场景,这点和B-Tree正好相反。

  • 支持一条索引里存在多个key。而B-Tree每条索引里只能有一个key。比如新插入一条数据“foo bar”,GIN会拆分出两个key:“foo” 和 “bar”。

GIN索引内部数据结构整体上类似于B-Tree。不同的是,叶子结点上存储的不是一个TID,而是很多TID的集合,这是GIN为了存储重复数据做的优化。GIN的叶子结点的数据有三种可能:

1. 当只有一个TID的时候,和B-Tree一样。

2. 当有很多TID的时候,那么存储的是一个列表(posting list)。

3. 当有非常非常多的TID的时候,叶子结点存储的是一个指针,这个指针指向另一棵树(Posting Tree)的根结点。而Posting Tree里面存储了所有符合这一个entry的TIDs(以page为存储单元)。

GIN树结构示意图:

GIN索引的Fast Updates特性:

GIN额外维护着一个列表,当有新的数据进入索引,不会直接进入索引树,而是会先暂存在这个列表里。当然,每个对索引的搜索也都会额外的对这个列表进行扫描。

当做vacuum操作的时候,会把这个列表里的数据插入到索引树上。不过,如果这个列表已经太大了,postgres不会傻傻等待用户进行vacuum,会果断地将它里面的内容插入GIN索引树。

但有意思的是,虽然它叫Fast Updates,但这个特性会让搜索变慢,因为每次搜索都要增加一次额外的列表扫描操作,所以很多用户会把这个特性关掉。

GIN索引的gin_fuzzy_search_limit

GIN索引适用于有大量重复关键字的全文索引,但是,当一个关键字出现次数太多时,返回结果也会很大,太大的结果集对用户是没有实际意义的,而且读取并排序这么多的数据会耗费很长时间。

GIN提供了一个配置项gin_fuzzy_search_limit来控制这种情况下返回结果集的上限。如果这个值非0,那么查询只会返回不超过设置值的一个子结果集,而且是随机的。根据经验,这个值设定为5000-20000比较好。

GIST (Generalized Search Tree)

GIST的数据结构仍然是树,但不再是B-Tree了。

支持地理坐标、range类型(比如ip范围)、hstore(key/value对)、整型数组、pg_trgm。

GIST和range

问题(见上图示意):假设表中有一列是range类型的数据,给出一个range范围,找出表中所有和它有重叠部分的数据。

如果是B-Tree,可以按照range的最大值或最小值进行排序,但仍然避免不了每次查询都要做扫描。GIST的做法,是将范围差不多的数据聚集在一起,形成一个个的“clusters”,每个cluster的最大值和最小值都在根结点上记录,这样就可以按照每个cluster的整体范围,排除掉那些根本不可能有重叠的cluster,大大降低了扫描成本。

GIST树的结构:

索引cluster存储在page上,根结点保存了每个page内部的最小值和最大值。给出查询范围后,只需要先扫描根结点,找出所有可能有范围重叠的page,再对这些page进行相应的扫描即可。

GIST对存储的key的顺序没有严格要求,而且每个key都可以在整棵树上任何一个page上存储,只要能把根结点的相应page的最大值最小值相应更新好就ok。但是,如果使用随机存储的话,最后根结点里每个page的范围就会差不多,性能会变得非常差(几乎等同于全表扫描)。所以,如何选择对key分组的函数就至关重要。另外当一个page增长过大之后,需要用picksplit函数来对page分割,这个函数的好坏对性能也有很大影响。

GIST的用途

  • GIS (geographic information system)

  • 寻找bounding box的顶点

  • 找到最近的n个邻居

  • 全文检索

  • 整型数组

一句话总结:GIST适合上层结点的范围能“包含”下层结点的类型。

SP-GIST (Space-Partitioned Generalized Search Tree)

虽然和GIST名字相似,但却是完全不同的索引类型,不是平衡树,各个叶子节点的深度可以不同。GIST各个cluster之间是可以有范围重叠的,但是SP-GIST不允许重叠

举个例子:

这是一个网址数据索引的例子,父节点是各个子节点的共有前缀,一个完整的键值在SP-GIST的树上只存一次,每一部分分别存储在从根结点到该键值叶子节点的路径上。

另外一个常见的使用场景是索引坐标点:将空间分割成不重叠的块,每个子节点再将父节点的空间细分。但是如果是空间里的形状,就无法使用SP-GIST来索引了,因为形状可能有重叠。

索引小知识

  • 你不能删除索引里的一条数据,只能通过vacuum操作将索引里无用的信息删掉。

  • Index Only Scan并不理会每一条数据是否是可见的,它只会如实把找到的一切符合条件的数据信息返回,可见性的控制是上层程序需要做的事。

什么时候你应该建立索引?

当seq_scan耗时很长的时候!

Explain一下你常用的查询,如果seq_scan的cost很高,可以考虑建立索引。但是相应的,如果一个索引的idx scan很少被用到,就要考虑这个索引是不是还应该继续存在了。毕竟,维护一个索引是耗时耗空间的事情。

如何选择使用哪种索引?

当然,数据类型是第一个考虑要素。在此之外,还可以从以下几点考虑:

  • 索引建立的时间

  • 索引存储空间

  • 数据更新时索引带来的额外开销

  • 访问速度

postgres 把一个表的值转成另一个表的字段名_Postgres索引那些事相关推荐

  1. postgres 把一个表的值转成另一个表的字段名_用LUT来做一个可动态配置的卷积核...

    引言 由于卷积核数据在计算过程中保持不变,更新较慢.这样就可以利用LUT来存储权重并同时进行乘法运算.LUT乘法器的实现很早就已经研究过,本论文正是在此基础上,提出了用于实现可配置的卷积实现方法.基于 ...

  2. postgres 把一个表的值转成另一个表的字段名_Phoenix系列创建Phoenix映射表

    目前,在公司小部分的业务场景中有用到 Phoenix,但也仅限基于 Phoenix 的二级索引机制来进行查询上的优化.虽然使用的频次不大,但偶尔用到时,有些语句的使用方式和注意事项总记不太熟,每次都需 ...

  3. postgres 把一个表的值转成另一个表的字段名_希望一个数据同步,包治百病

    小姐姐味道[ID:xjjdog] 作者:十年架构,日百亿流量经验,与你分享. 哎!这是一个脏活,而且是个高风险的活. 大多数情况下,应用架构设计不好,引入什么新存储,引入什么DDD,治标不治本,都是扯 ...

  4. 如何把一个数组的值赋给另一个数组

    java中的数组 一维数组中把一个数组的值赋给另外一个数组 public class test {public static void main(String[] args) {int[] array ...

  5. mysql join 去重_MySQL Update inner join数据库去重,以及根据一张表的值更新另一张表...

    1 问题来源 这几天在项目中遇到一个问题:由于前期设计不合理,导致后期用户录入数据时,基础数据表中有重复多余数据.如下: 出现两个 2G网络测试手机 这样类似的基础数据,直接后果就是用户在使用这个基础 ...

  6. 外汇天眼:Apple与MetaQuotes之争!谁是下一个Apple?谁会成下一个MT4/5?

    手机巨头Apple 从其应用商店App Store中下架领先的交易软件MetaTrader4(MT4)和 MetaTrader5(MT5)引发了外汇市场热议. 虽然截止发稿,Apple以及交易软件MT ...

  7. SQL Server修改表名,字段名,索引名

    说实话,感觉SQL Server的资料真的好难找(也有可能是很多人在吐槽的CSDN的搜索功能不够强--),我想找个修改表名的方法,结果找了好久,才找到一个可行的,留个纪念,希望也能够帮到你(多个人转发 ...

  8. mysql怎么把值更新成space,MySQL表的碎片整理和空间回收小结

    MySQL表碎片化(Table Fragmentation)的原因 关于MySQL中表碎片化(Table Fragmentation)产生的原因,简单总结一下,MySQL Engine不同,碎片化的原 ...

  9. 注册表把html设置成桌面,[注册表] 将Windows 10默认应用程序设置页面添加到桌面右键菜单中...

    从Windows 10初始版本开始微软就已调整默认程序配置策略,即强制用户设置关联的默认程序不允许软件设置. 这也是很多用户经常在通知栏里看到某某文件格式被重置的原因,因为软件安装后设置的会被微软重置 ...

最新文章

  1. warnings.filterwarnings(ignore)
  2. scrapy爬虫程序xpath中文编码报错
  3. [转]iOS为UILabel添加长按复制功能
  4. python PIL图像处理-框选
  5. python 查看当前目录_「Python」打包分发工具setuptools学习
  6. C++ —— C++引用
  7. PID控制器改进笔记之一:改进PID控制器之参数动态调整
  8. Web前端开发工程师必读de设计博客
  9. sobel prewitt算法 模板加权模糊的解释 + 两类边缘下的二阶导数值
  10. 【华为云技术分享】《跟唐老师学习云网络》—router路咋走啊
  11. 为了做服务注册迁移,我提前准备了这些东西,来看看对你有没有用!
  12. Sentinel流控规则_线程数失败_分布式系统集群限流_线程数隔离_削峰填谷_流量控制_速率控制_服务熔断_服务降级---微服务升级_SpringCloud Alibaba工作笔记0034
  13. Linux的一些基础命令
  14. 目标检测中文类别--在图片中添加汉字
  15. 国产数据库普及风暴有奖征文获奖名单揭晓
  16. 阿里云、腾讯云----域名DDNS云解析到动态IP
  17. PHP实现的敏感词过滤方法
  18. 几个比较好看的几个颜色
  19. 王者荣耀头像大小怎么调?调整图片尺寸大小工具分享
  20. JS Array.slice 截取数组的实现方法

热门文章

  1. linux下批量新增数据,linux下批量插入数据到mysql
  2. 管状合金电阻和片状合金电阻的区别_合金采样电阻的特点及作用
  3. linux安装界面意思,为linux安装图形化界面
  4. android 工厂测试内存,Android性能测试之内存
  5. java多线程详解 六_java多线程学习-java.util.concurrent详解(六) Exchanger
  6. oracle插入CLOB类型超过4000个字符报ORA-01704错的解决方法
  7. www,android18x.com,Android 11 LineageOS 18.1系统
  8. 安卓团课快进_青年大学习网上主题团课第十季第七期答案
  9. 带你学 Redis: 基本命令 String 操作(三)
  10. Job 存储和持久化 (第二部分)