Postgresql INDEX HOT 原理與更好的 “玩轉” INDEX

隨着問問題的同學越來越多,公衆號內部私信回答問題已經很困難了,所以建立了一個羣,關於各種數據庫的問題都可以,目前主要是 POSTGRESQL, MYSQL ,MONGODB ,POLARDB ,REDIS,SQL SERVER 等,期待你的加入,加羣請添加微信 liuaustin3.  (羣裏有各方面的工作人員和專家)

好長時間不進行研究了,最近被突發的問題想到了 INDEX 的問題,隨機想到數據和 INDEX 存儲在一起會怎樣,我們將索引和數據進行分離後,會不會對數據庫的性能有優化的可能。

所以讓我想到了 HOT  heap only tuples 這個事情,但是在是記不清了,所以就的翻翻舊賬了。

首先 HOT ,heap only tuples 是 Postgres 用戶用於減少基於 UPDATE 後的大量的 IO 所做的工作,主要的問題就是在 MVCC 導致的 UPDATE 等於 INSERT + 廢棄行,以及新插入的行都需要對當前的索引負責。

相對於表本身需要 VACUUM 和 AUTOVACUUM 的情況下,我們的其實需要更多的 I/O 工作在針對這些操作針對索引的問題的 IO 消耗,因爲索引需要修改指針到新的行。

Postgres 爲了降低指針重新指向的問題,提出在一個行 UPDATE 後,就在原有的位置上插入他的新的版本的行,通過這樣的方式讓索引知道新的行就在老得行的下一個位置,避免大量的更新索引的操作,使用這樣的方式就可以在索引上直接指向原來的位置的下一個位置。

而要完成這個事情,需要一個特殊的條件就是,更新的列不能是當前的索引列。

下面是經典的兩個圖 ,1 如果沒有 HOT 的情況下   2 使用 HOT 的情況

所以結論是 POSTGRESQL 在頻繁的 UPDATE 當中,如果更新的字段是索引的情況下,將引發大量的索引更新,引起 IO 的消耗的情況。

在 POSTGRESQL 有這樣的問題的情況下,我們需要針對 POSTGRESQL 的索引更加的小心和謹慎。

所以我們需要注意以下的問題

1  unused indexes   無用的索引

2  bloated indexes   膨脹的索引

3  Duplicate and invalid index   重複的索引

爲什麼會產生以上的這些問題呢

1   添加索引是在業務確認之前添加的,也就是添加索引並不是完全確認了業務的情況下進行的。

2   添加的索引針對的業務下線了

3   服務器的資源提升了,增加了,暫時不使用索引可以達到更好的

4   業務發展,後期添加的索引替代了早期的索引

5   操作失誤,建立了同樣的索引

那麼針對以上的問題,我們需要

1  找到無用的索引

SELECT s.schemaname,

s.relname AS tablename,

s.indexrelname AS indexname,

pg_relation_size(s.indexrelid) AS index_size

FROM pg_catalog.pg_stat_user_indexes s

JOIN pg_catalog.pg_index i ON s.indexrelid = i.indexrelid

WHERE s.idx_scan = 0      

AND 0 <>ALL (i.indkey)  

AND NOT i.indisunique   

AND NOT EXISTS         

(SELECT 1 FROM pg_catalog.pg_constraint c

WHERE c.conindid = s.indexrelid)

ORDER BY pg_relation_size(s.indexrelid) DESC;

這裏的無用的索引的問題,在通過語句找出相關得信息,只能作爲一個借鑑的值,而不是一個可以完全借鑑的值。

得到這些信息,只能是還需要更多的分析,才能將這些索引清理掉。

2  索引的碎片率的問題,導致索引的性能的問題 ,基於 POSTGRESQL MVCC 以及相關的問題,導致表膨脹,這樣的情況下,也會導致索引碎片的問題,所以發現並重建索引是一個需要注意的問題。

create extension pgstattuple;

SELECT i.indexrelid::regclass,

       s.leaf_fragmentation

FROM pg_index AS i

   JOIN pg_class AS t ON i.indexrelid = t.oid

   JOIN pg_opclass AS opc ON i.indclass[0] = opc.oid

   JOIN pg_am ON opc.opcmethod = pg_am.oid

   CROSS JOIN LATERAL pgstatindex(i.indexrelid) AS s

WHERE t.relkind = 'i'

  AND pg_am.amname = 'btree' and s.leaf_fragmentation > 0 and s.leaf_fragmentation <> 'NaN';

通過上面的語句去查詢你索引的碎片率,通過這個來決定你的是否要進行索引的重建的工作。

3  重複索引的問題

基於上面的問題,索引不使用另外一種可能是有同類的索引,所以在發現索引不被使用的情況下,可以先看看是否有重複的索引的原因引起的,重複索引的害處可謂是 “罄竹難書”

1  衆所周知的重複索引,引起插入效率低

2  重複索引導致的數據量加大的問題

3  進行 VACUUM AUTOVACUUM 多餘的重複索引導致的操作時間和資源消耗過大的問題。

所以重複索引的問題一定要將多餘的索引清理出去

SELECT   indrelid::regclass table_name,

         att.attname column_name,

         amname index_method

FROM     pg_index i,

         pg_class c,

         pg_opclass o,

         pg_am a,

         pg_attribute att

WHERE    o.oid = ALL (indclass) 

AND      att.attnum = ANY(i.indkey)

AND      a.oid = o.opcmethod

AND      att.attrelid = c.oid

AND      c.oid = i.indrelid

GROUP BY table_name, 

         att.attname,

         indclass,

         amname, indkey

HAVING count(*) > 1;

SELECT    indrelid::regclass AS TableName    ,array_agg(indexrelid::regclass) AS Indexes 

FROM pg_index 

GROUP BY    indrelid    ,indkey 

HAVING COUNT(*) > 1;

通過上面的語句來查看當前的數據庫中是否有重複的索引。

除此以外,我們還可以針對索引做如下的一些工作

1 在 Postgresql 中創建針對索引的表空間,數據和索引進行分離,而不要將索引和數據創建在一個數據文件內。

postgres=# create tablespace index_storage location '/pgdata/index';

CREATE TABLESPACE

postgres=# create index idx_user_name on user_ini(user_name) tablespace index_storage;

CREATE INDEX

postgres=# 

2  針對當前的索引進行查詢和分析

1 針對當前有多少索引進行信息的獲取

SELECT CONCAT(n.nspname,'.', c.relname) AS table,i.relname AS index_name,x.indisunique as is_unique FROM pg_class c

INNER JOIN pg_index x ON c.oid = x.indrelid

INNER JOIN pg_class i ON i.oid = x.indexrelid LEFT JOIN pg_namespace n ON n.oid = c.relnamespace

WHERE c.relkind = ANY (ARRAY['r', 't']) and c.relname not like 'pg%';

2  這對當前的索引的大小進行分析

SELECT 

    relname AS objectname,

    relkind AS objecttype,

    reltuples :: bigint AS "rows",

    pg_size_pretty(relpages::bigint81024) AS size

FROM pg_catalog.pg_class where relkind = 'i'

ORDER BY relpages DESC;

3 針對索引使用的次數進行統計,如每天索引被使用多少次,如果索引組最近一段時間使用的頻次明顯比之前要少,或者根本就不使用了,就需要分析有沒有可能是因爲索引損壞造成的問題。

SELECT s.relname AS table_name,

       indexrelname AS index_name,

       i.indisunique,

       idx_scan AS index_scans

FROM   pg_catalog.pg_stat_user_indexes s,

       pg_index i

WHERE  i.indexrelid = s.indexrelid;

另外,在索引的工作中,還有一些問題基於索引的損壞導致的問題,會發現如下的一些問題

1  本來有索引但是在查詢中不走索引而是走全表掃描

2  通過 pg_stat_user_tables 表中的 seq_scan  和 idx_scan  兩個字段的數值的對比來發現問題,如 seq_scan 瘋狂的增加數字,而 idx_scan 裏面不增長或增長很慢,(1 是否有對應的索引  2 索引是否損壞)

3  在查詢中出現錯誤的數據,如查詢範圍的明顯標定的很清楚,但是查詢的數據突破了這個範圍,也就是查詢的值不對。

以上的方式也可能是其他問題造成的,如數據庫表的 analyze 操作不及時,導致統計分析的數據出現偏差造成的。

基於以上的一些內容,索引的維護和信息的收集,以及問題的發現對於索引的維護是非常重要的。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/6CBdHy5Buxva9vAoaZ5_aQ