postgresql 索引之 hash的使用详解

os: ubuntu 16.04

postgresql: 9.6.8

ip 规划

192.168.56.102 node2 postgresql

help create index

postgres=# \h create index
Command:   CREATE INDEX
Description: define a new index
Syntax:
CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] name ] ON table_name [ USING method ]
  ( { column_name | ( expression ) } [ COLLATE collation ] [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] )
  [ WITH ( storage_parameter = value [, ... ] ) ]
  [ TABLESPACE tablespace_name ]
  [ WHERE predicate ]

[ USING method ]

method

要使用的索引方法的名称。可以选择 btree、hash、 gist、spgist、 gin以及brin。 默认方法是btree。

hash

hash 只能处理简单的等值比较,

postgres=# drop table tmp_t0;
DROP TABLE
postgres=# create table tmp_t0(c0 varchar(100),c1 varchar(100));
CREATE TABLE
postgres=# insert into tmp_t0(c0,c1) select md5(id::varchar),md5((id+id)::varchar) from generate_series(1,100000) as id;
INSERT 0 100000
postgres=# create index idx_tmp_t0_1 on tmp_t0 using hash(c0);
CREATE INDEX
postgres=# \d+ tmp_t0
                     Table "public.tmp_t0"
 Column |     Type     | Collation | Nullable | Default | Storage | Stats target | Description
--------+------------------------+-----------+----------+---------+----------+--------------+-------------
 c0   | character varying(100) |      |     |     | extended |       |
 c1   | character varying(100) |      |     |     | extended |       |
Indexes:
  "idx_tmp_t0_1" hash (c0)
postgres=# explain select * from tmp_t0 where c0 = 'd3d9446802a44259755d38e6d163e820';
                 QUERY PLAN
----------------------------------------------------------------------------
 Index Scan using idx_tmp_t0_1 on tmp_t0 (cost=0.00..8.02 rows=1 width=66)
  Index Cond: ((c0)::text = 'd3d9446802a44259755d38e6d163e820'::text)
(2 rows)

注意事项,官网特别强调:

Hash索引操作目前不被WAL记录,因此存在未写入修改,在数据库崩溃后需要用REINDEX命令重建Hash索引。

同样,在完成初始的基础备份后,对于Hash索引的改变也不会通过流式或基于文件的复制所复制,所以它们会对其后使用它们的查询给出错误的答案。

正因为这些原因,Hash索引已不再被建议使用。

补充:Postgresql hash索引介绍

hash索引的结构

当数据插入索引时,我们会为这个索引键通过哈希函数计算一个值。 PostgreSQL中的哈希函数始终返回“整数”类型,范围为2^32≈40亿。bucket桶的数量最初为2个,然后动态增加以适应数据大小。可以使用位算法从哈希码计算出桶编号。这个bucket将存放TID。

由于可以将与不同索引键匹配的TID放入同一bucket桶中。而且除了TID之外,还可以将键的源值存储在bucket桶中,但这会增加索引大小。为了节省空间,bucket桶只存储索引键的哈希码,而不存储索引键。

当我们通过索引查询时,我们计算索引键的哈希函数并获取bucket桶的编号。现在,仍然需要遍历存储桶的内容,并仅返回所需的哈希码匹配的TID。由于存储的“hash code - TID”对是有序的,因此可以高效地完成此操作。

但是,两个不同的索引键可能会发生以下情况,两个索引键都进入一个bucket桶,而且具有相同的四字节的哈希码。因此,索引访问方法要求索引引擎重新检查表行中的情况来验证每个TID。

映射数据结构到page

Meta page - 0号page,包含索引内部相关信息

Bucket pages - 索引的主要page,存储 “hash code - TID” 对

Overflow pages - 与bucket page的结构相同,在不足一个page时,作为bucket桶使用

Bitmap pages - 跟踪当前干净的overflow page,并可将其重新用于其他bucket桶

注意,哈希索引不能减​​小大小。虽然我们删除了一些索引行,但是分配的页面将不会返回到操作系统,只会在VACUUMING之后重新用于新数据。减小索引大小的唯一选项是使用REINDEX或VACUUM FULL命令从头开始重建索引

接下来看下hash索引如何创建

demo=# create index on flights using hash(flight_no);
demo=# explain (costs off) select * from flights where flight_no = 'PG0001';
           QUERY PLAN
----------------------------------------------------
 Bitmap Heap Scan on flights
  Recheck Cond: (flight_no = 'PG0001'::bpchar)
  -> Bitmap Index Scan on flights_flight_no_idx
     Index Cond: (flight_no = 'PG0001'::bpchar)
(4 rows)

注意:10版本之前hash索引不记录到wal中,所以hash索引不能做recovery,当然也就不能复制了,但是从10版本以后hash所用得到了增强,可以记录到wal中,创建的时候也不会再有警告。

查看hash访问方法相关的操作函数

demo=# select  opf.opfname as opfamily_name,
     amproc.amproc::regproc AS opfamily_procedure
from   pg_am am,
     pg_opfamily opf,
     pg_amproc amproc
where  opf.opfmethod = am.oid
and   amproc.amprocfamily = opf.oid
and   am.amname = 'hash'
order by opfamily_name,
     opfamily_procedure;

   opfamily_name  |  opfamily_procedure
--------------------+-------------------------
 abstime_ops    | hashint4extended
 abstime_ops    | hashint4
 aclitem_ops    | hash_aclitem
 aclitem_ops    | hash_aclitem_extended
 array_ops     | hash_array
 array_ops     | hash_array_extended
 bool_ops      | hashcharextended
 bool_ops      | hashchar
 bpchar_ops     | hashbpcharextended
 bpchar_ops     | hashbpchar
 bpchar_pattern_ops | hashbpcharextended
 bpchar_pattern_ops | hashbpchar
 bytea_ops     | hashvarlena
 bytea_ops     | hashvarlenaextended
 char_ops      | hashcharextended
 char_ops      | hashchar
 cid_ops      | hashint4extended
 cid_ops      | hashint4
 date_ops      | hashint4extended
 date_ops      | hashint4
 enum_ops      | hashenumextended
 enum_ops      | hashenum
 float_ops     | hashfloat4extended
 float_ops     | hashfloat8extended
 float_ops     | hashfloat4
 float_ops     | hashfloat8
 ...

可以用这些函数计算相关类型的哈希码

hank=# select hashtext('zhang');
 hashtext
-------------
 -1172392837
(1 row)
hank=# select hashint4(10);
 hashint4
-------------
 -1547814713
(1 row)

hash索引相关的属性

hank=# select a.amname, p.name, pg_indexam_has_property(a.oid,p.name)
hank-# from pg_am a,
hank-#   unnest(array['can_order','can_unique','can_multi_col','can_exclude']) p(name)
hank-# where a.amname = 'hash'
hank-# order by a.amname;
 amname |   name   | pg_indexam_has_property
--------+---------------+-------------------------
 hash  | can_order   | f
 hash  | can_unique  | f
 hash  | can_multi_col | f
 hash  | can_exclude  | t
(4 rows)
hank=# select p.name, pg_index_has_property('hank.idx_test_name'::regclass,p.name)
hank-# from unnest(array[
hank(#    'clusterable','index_scan','bitmap_scan','backward_scan'
hank(#   ]) p(name);
   name   | pg_index_has_property
---------------+-----------------------
 clusterable  | f
 index_scan  | t
 bitmap_scan  | t
 backward_scan | t
(4 rows)
hank=# select p.name,
hank-#   pg_index_column_has_property('hank.idx_test_name'::regclass,1,p.name)
hank-# from unnest(array[
hank(#    'asc','desc','nulls_first','nulls_last','orderable','distance_orderable',
hank(#    'returnable','search_array','search_nulls'
hank(#   ]) p(name);
    name    | pg_index_column_has_property
--------------------+------------------------------
 asc        | f
 desc        | f
 nulls_first    | f
 nulls_last     | f
 orderable     | f
 distance_orderable | f
 returnable     | f
 search_array    | f
 search_nulls    | f
(9 rows)

由于hash函数没有特定的排序规则,所以一般的hash索引只支持等值查询,可以通过下面数据字典看到,所有操作都是“=”,而且hash索引也不会处理null值,所以不会标记null值,还有就是hash索引不存储索引键,只存储hash码,所以不会 index-only扫描,也不支持多列创建hash索引

hank=# select  opf.opfname AS opfamily_name,
hank-#     amop.amopopr::regoperator AS opfamily_operator
hank-# from   pg_am am,
hank-#     pg_opfamily opf,
hank-#     pg_amop amop
hank-# where  opf.opfmethod = am.oid
hank-# and   amop.amopfamily = opf.oid
hank-# and   am.amname = 'hash'
hank-# order by opfamily_name,
hank-#     opfamily_operator;
  opfamily_name  |           opfamily_operator
--------------------+------------------------------------------------------------
 abstime_ops    | =(abstime,abstime)
 aclitem_ops    | =(aclitem,aclitem)
 array_ops     | =(anyarray,anyarray)
 bool_ops      | =(boolean,boolean)
 bpchar_ops     | =(character,character)
 bpchar_pattern_ops | =(character,character)
 bytea_ops     | =(bytea,bytea)
 char_ops      | =("char","char")
 cid_ops      | =(cid,cid)
 date_ops      | =(date,date)
 enum_ops      | =(anyenum,anyenum)
 float_ops     | =(real,real)
 float_ops     | =(double precision,double precision)
 float_ops     | =(real,double precision)
 float_ops     | =(double precision,real)
 hash_hstore_ops  | =(hstore,hstore)
 integer_ops    | =(integer,bigint)
 integer_ops    | =(smallint,smallint)
 integer_ops    | =(integer,integer)
 integer_ops    | =(bigint,bigint)
 integer_ops    | =(bigint,integer)
 integer_ops    | =(smallint,integer)
 integer_ops    | =(integer,smallint)
 integer_ops    | =(smallint,bigint)
 integer_ops    | =(bigint,smallint)
 interval_ops    | =(interval,interval)
 jsonb_ops     | =(jsonb,jsonb)
 macaddr8_ops    | =(macaddr8,macaddr8)
 macaddr_ops    | =(macaddr,macaddr)
 name_ops      | =(name,name)
 network_ops    | =(inet,inet)
 numeric_ops    | =(numeric,numeric)
 oid_ops      | =(oid,oid)
 oidvector_ops   | =(oidvector,oidvector)
 pg_lsn_ops     | =(pg_lsn,pg_lsn)
 range_ops     | =(anyrange,anyrange)
 reltime_ops    | =(reltime,reltime)
 text_ops      | =(text,text)
 text_pattern_ops  | =(text,text)
 time_ops      | =(time without time zone,time without time zone)
 timestamp_ops   | =(timestamp without time zone,timestamp without time zone)
 timestamptz_ops  | =(timestamp with time zone,timestamp with time zone)
 timetz_ops     | =(time with time zone,time with time zone)
 uuid_ops      | =(uuid,uuid)
 xid_ops      | =(xid,xid)

从10版本开始,可以通过pageinspect插件查看hash索引的内部情况

安装插件

create extension pageinspect;

查看0号page

hank=# select hash_page_type(get_raw_page('hank.idx_test_name',0));
 hash_page_type
----------------
 metapage
(1 row)

查看索引中的行数和已用的最大存储桶数

hank=# select ntuples, maxbucket
hank-# from hash_metapage_info(get_raw_page('hank.idx_test_name',0));
 ntuples | maxbucket
---------+-----------
  1000 |     3
(1 row)

可以看到1号page是bucket,查看此bucket page的活动元组和死元组的数量,

也就是膨胀度,以便维护索引

hank=# select hash_page_type(get_raw_page('hank.idx_test_name',1));
 hash_page_type
----------------
 bucket
(1 row)
hank=# select live_items, dead_items
hank-# from hash_page_stats(get_raw_page('hank.idx_test_name',1));
 live_items | dead_items
------------+------------
    407 |     0
(1 row) 

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。

(0)

相关推荐

  • PostgreSql 的hash_code函数的用法说明

    PostgreSql 实现的hash_code 函数与java hash_code方法一致 CREATE FUNCTION hash_code(text) RETURNS integer LANGUAGE plpgsql AS $$ DECLARE i integer := 0; DECLARE h bigint := 0; BEGIN FOR i IN 1..length($1) LOOP h = (h * 31 + ascii(substring($1, i, 1))) & 42949672

  • Postgresql 查询表引用或被引用的外键操作

    今天更新两个SQL.是用来查询PG中,主表被子表引用的外键,或子表引用了哪个主表的主键. 废话不多说,直接上实验! CentOS 7 + PG 10 创建两个实验表,test01为主表,test02为子表,test02引用test01中的id列. test=# create table test01( test(# id int primary key, test(# col1 varchar(20) test(# ); CREATE TABLE test=# create table test

  • PostgreSql 重建索引的操作

    PostgreSql数据库的重建索引时通过REINDEX命令来实现的,如reindexindex_name: 其语法是: REINDEX { INDEX | TABLE | DATABASE | SYSTEM } name [ FORCE ]; 下面解释下说明情况下需要: 1.当由于软件bug或者硬件原因导致的索引不再可用,索引的数据不再可用: 2.当索引包含许多空的或者近似于空的页,这个在b-tree索引会发生.Reindex会腾出空间释放哪些无用的页(页就是存放数据的一个单位,类似于bloc

  • PostgreSQL Sequence序列的使用详解

    PostgreSQL是一种关系型数据库,和Oracle.MySQL一样被广泛使用.平时工作主要使用的是PostgreSQL,所以有必要对其相关知识做一下总结和掌握,先总结下序列. 一. Sequence序列 Sequence是一种自动增加的数字序列,一般作为行或者表的唯一标识,用作代理主键. 1.Sequence的创建 例子:创建一个seq_commodity,最小值为1,最大值为9223372036854775807,从1开始,增量的步长为1,缓存为1的循环排序Sequence. SQL语句如

  • PostgreSQL之INDEX 索引详解

    之前总结了PostgreSQL的序列相关知识,今天总结下索引. 我们都知道,数据库索引最主要的作用是可以提高检索数据的速度,但是索引也不是越多越好.因为索引会增加数据库的存储空间,查询数据是要花较多的时间. 1.创建索引 SQL语句如下: CREATE INDEX idx_commodity ON commodity //表名 USING btree //用B树实现 (commodity_id); //作用的具体列 2.删除索引 DROP index idx_commodity; 3.增加索引的

  • postgresql 索引之 hash的使用详解

    os: ubuntu 16.04 postgresql: 9.6.8 ip 规划 192.168.56.102 node2 postgresql help create index postgres=# \h create index Command: CREATE INDEX Description: define a new index Syntax: CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] name ] ON

  • PostgreSQL使用MySQL外表的步骤详解(mysql_fdw)

    浅谈 postgres不知不觉已经升到了版本13,记得两年前还是版本10,当然这中间一直期望着哪天能在项目中使用postgresql,现在已实现哈-: 顺带说一下:使用postgresql的原因是它的生态完整,还有一个很重要的点儿是速度快这个在第10版的时 这么说也许还为时过早, 但是在13这一版本下一点儿也不为过,真的太快了,我简单的用500w的数据做聚合,在不建立索引(主键除外)的情况下 执行一个聚合操作,postgres 的速度是mysql的8倍,真的太快了-:好了,这一章节我就聊一聊我实

  • 基于php数组中的索引数组和关联数组详解

    php中的索引数组是指以数字为键的数组.并且这个键值 是自增的 关联数组指的是一个键值对应一个值,并且这个键值是不规律的,通常都是我们自己指定的. 他们两还有不同的地方,索引数组转为json后是数组.而关联数组转为json后是对象.通常我们给app端写接口都是用索引数组转成json传过去.客户端那边对数组更为友好一点. 需要注意点: $arr = [0=>1,2=>3a]; 上述数组$arr转为json会是对象形式的. $arr = ['a','b']; 这里的$arr转为json后是数组的形

  • Vue中Router路由两种模式hash与history详解

    hash 模式 (默认) 工作原理: 监听网页的hash值变化 -> onhashchange事件, 获取location.hash 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载. 会给用户好像跳转了网页一样的感觉, 但是实际上没有跳转 主要用在单页面应用(SPA) // 模拟原理 // 监听页面hash值变化 window.onhashchange = function(){ // 获取当前url的哈希值 const _hash = locat

  • MySQL索引优化之适合构建索引的几种情况详解

    目录 结论 建立索引的场景 小结 结论 在where后面的过滤字段上建立索引(select/update/delete后面的where都是适用的),使用索引加快过滤效率,不用进行全表扫描 在具有唯一要求的字段上添加唯一索引,加快查询效率,查到即可直接返回 group by或者order by后面的字段添加索引,由于索引是排好序的,所以建立索引就等同于在查询之前已经是排好序了(这里需要注意建立的联合索引建立中字段的顺序,可以结合具体案例场景7进行学习) 在DISTINCT(去重字段)后面的字段添加

  • Redis 哈希Hash底层数据结构详解

    目录 1. Redis 底层数据结构 2. hashtable 3. redisDb 与 redisObject 4. ziplist 5. linkedlist 6. quicklist 1. Redis 底层数据结构 Redis数据库就像是一个哈希表,首先对key进行哈希运算得到哈希值再取模得到一个下标,每个元素是一个节点,节点之间形成链表.这感觉有点像Java中的HashMap. 不同的数据类型的实现方式是不一样的,可以通过object encoding命令查看底层真正的数据存储结构 同一

  • Java postgresql数组字段类型处理方法详解

    在实际开发中遇到postgresql中定义的数组字段,下面解决两个问题,如何定义数组字段的默认值为空格数组,以及如何再java实体类中直接使用数组对象接受数据或把数据存入数据库. 1.在postgresql中定义数组对象及默认值 以字符串你数组为例: 比如一个字段用于存储多张图片的url,可以使用一下sql定义 pictures _varchar NOT NUll default ARRAY[]::character varying[] 2.实体类存入数组到数据库并接受数据库的数组数据 直接定义

  • PostgreSQL中的日期/时间函数详解

    目录 零.前言 一.获取当前时间 二.时间的加减 三.格式化函数 3.1时间转字符串 3.2字符串转日期 3.3字符串转时间 3.4Unix时间戳转时间 四.一些重要函数 4.1时间间隔 4.2时间截取 五.时间的转换 六.收! 零.前言 公司里有一台阿里云RDS数据库用了PPAS(Postgres PlusTM Advanced Server),在处理日期/时间时遇到一些问题,花了点时间整理如下. 一.获取当前时间 select now() select current_timestamp s

  • MySQL中的全表扫描和索引树扫描 的实例详解

    目录 引言 实例 引言 在学习mysql时,我们经常会使用explain来查看sql查询的索引等优化手段的使用情况.在使用explain时,我们可以观察到,explain的输出有一个很关键的列,它就是type属性,type表示的是扫描方式,代表 MySQL 使用了哪种索引类型,不同的索引类型的查询效率是不一样的. 在type这一列,有如下一些可能的选项: system:系统表,少量数据,往往不需要进行磁盘IOconst:常量连接eq_ref:主键索引(primary key)或者非空唯一索引(u

  • MySQL索引最左匹配原则实例详解

    目录 简介 准备 理论详解 聚集索引和非聚集索引 回表查询 索引覆盖 最左匹配原则 详细规则 补充:为什么要使用联合索引 总结 简介 这篇文章的初衷是很多文章都告诉你最左匹配原则,却没有告诉你,实际场景下它到底是如何工作的,本文就是为了阐述清这个问题. 准备 为了方面后续的说明,我们首先建立一个如下的表(MySQL5.7),表中共有5个字段(a.b.c.d.e),其中a为主键,有一个由b,c,d组成的联合索引,存储引擎为InnoDB,插入三条测试数据.强烈建议自己在MySQL中尝试本文的所有语句

随机推荐