本章介紹與索引掃描相關的兩個功能, 即Heap Only Tuple和Index-Only Scans。
7.1. Heap Only Tuple (HOT)
HOT是在8.3版中引入的, 當更新的行存儲在舊行所在的page時,可以有效地使用索引和表的頁。HOT有效的減少了vacuum操作。
源碼目錄README.HOT描述了HOT的細節。 本章簡要介紹了HOT。首先, 7.1.1章節介紹瞭如何在沒有HOT的情況下更新行。接下來,7.1.2部分介紹了HOT是如何執行的。
7.1.1. 更新沒有HOT的行
假設表 “tbl” 有兩列:“id"和"data”;"id"是"tbl"的主鍵。
testdb=# \d tbl
Table "public.tbl"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | not null |
data | text | | |
Indexes:
"tbl_pkey" PRIMARY KEY, btree (id)
表"tbl"有1000個tuples;id爲"1000"的最後一個tuples存儲在表的第5個page。最後一個tuples指向相應的索引元組,其中的鍵爲"1000",其tid爲"(5,1)"。請參閱圖 7.1(a)。
圖7.1. 更新沒有HOT的行
我們考慮如何在沒有HOT的情況下更新最後一個tuples
testdb=# UPDATE tbl SET data = 'B' WHERE id = 1000;
在這種情況下, PostgreSQL不僅會在表page中插入新的tuples, 還會在索引page中插入新的索引tuples。請參閱圖 7.1(b)。
索引tuples的插入會佔用索引頁的空間, 並且索引tuples的插入和vacuum成本都很高。HOT減少了這些問題的影響。
7.1.2. HOT如何執行
HOT行更新的時候,如果更新的行存儲在舊行所在的page時,Postgresql不需要插入相應的index tuples,並且分別設置HEAP_HOT_UPDATED bit和HEAP_ONLY_TUPLE bit爲舊tuples和新tuples的t_informask2,參照圖7.2和7.3
圖 7.2. 更新HOT行
例如, 在這種情況下, “Tuple_1” 和 "Tuple_2"分別設置HEAP_hot_upeded和HEAP_ONLY_TUPLE的bit位。
此外, 無論修剪情況如何,都會使用HEAP_hot_update和HEAP_ONLY_TUPLE bit位執行碎片整理。如下圖所示
圖 7.3. HEAP_HOT_UPDATED和HEAP_ONLY_TUPLE bits
如下, 本文描述了PostgreSQL如何使用HOT更新後的tuples索引掃描訪問更新的tuples。請參閱圖 7.4(a)。
圖 7.4. 修剪行指針
(1) 找出指向目標tuples的索引tuples
(2) 訪問從索引tuples中獲取的行指針 “[1]”
(3) 讀"Tuple_1"
(4) 通過"Tuple_1"的ctid讀"Tuple_2"
在這種情況下, PostgreSQL讀取兩個元組, “Tuple_1” 和 “Tuple_2”, 並通過第5章中描述的併發控制機制來確定哪個是可見的。
但是, 如果刪除表頁中的dead tuples, 則會出現問題。例如, 在圖7.4(a)中, 如果刪除"Tuple_1", 因爲它是dead tuple, 則無法從索引中訪問"Tuple_2"。
爲了解決此問題, 在適當的時候, PostgreSQL將指向舊tuple的行指針重定向到指向新tuple的行指針。在 PostgreSQL中,此處理稱爲修剪。圖 7.4(b)描述了PostgreSQL 在修剪後如何訪問更新的tuples。
(1) 查找index tuple
(2) 訪問從索引tuples中指向的行指針 “[1]”
(3) 通過重定向的行指針訪問指向"Tuple_2"的行指針"[2]"
(4) 從指向point"[2]“的point讀取"Tuple_2”
如果可能發生, 在執行SQL命令 (如 SELECT,UPDATE,INSERT和DELETE) 時,將執行修剪處理。本章沒有介紹確切的執行時間, 因爲它非常複雜。詳細信息。
如果可能, 在修剪過程中,PostgreSQL會在適當的時間刪除dead tuples。在PostgreSQL官方文檔中, 此處理稱爲碎片整理。圖7.5描述了由HOT引發的碎片整理。
圖 7.5. dead tuples碎片整理
請注意, 碎片整理的成本低於正常的VACUM處理成本,因爲碎片整理不涉及刪除索引元組。
因此, 使用HOT可以減少索引也和表頁的消耗;這也減少了VACUUM處理的tuples。因此, HOT對性能有很好的影響, 因爲它最終減少了在update和vacuum的時候index tuples的插入次數。
沒有HOT的示例
爲了清楚地瞭解 HOT的運行情況, 這個示例描述了沒有HOT的情況
當更新後的tuples沒有存儲在old tuples的同一頁中時, 指向該tuple的索引tuple也會插入到索引頁中。請參閱圖 7.6(a)
當索引tuple的鍵值更新時, 將在索引頁中插入新的索引tuple。請參閱圖 7.6(b)
圖7.6.沒有HOT的示例
與HOT相關的統計數據
pg_stat_all_tables爲每個表提供了統計值,也可以查看https://github.com/s-hironobu/pg_stats插件
7.2. Index-Only Scans
爲了降低I/O(輸入/輸出)成本, 當SELECT語句的所有數據都包含在索引鍵中時, 索引掃描(通常稱爲 index-only access)直接使用索引鍵,而不訪問相應的表頁。所有商業RDBMS都提供此項技術 ,如DB2和oracle.Postgresql從9.2版本開始引入。
下面的示例描述Postgresql中index-only scans是如何運行的
假設示例如下:
1.表結構
我們有一個表"tbl",其定義如下所示:
testdb=# \d tbl
Table "public.tbl"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
data | text |
Indexes:
"tbl_idx" btree (id, name)
2.索引
表"tbl"有一個索引"tbl_idx", 它由兩列組成:“id"和"name”。
3.tuples
“tbl"已經插入了以下tuples
“Tuple_18”,其id爲"18”, 名稱爲"Queen",存儲在第0號頁面中。
“Tuple_19”,其id爲"19", 名稱爲"BOSTON", 存儲在第1號頁面中。
4.可見性
第0號頁中的所有tuples始終可見;第1號頁中的元組並不總是可見。請注意, 每個頁面的可見性存儲在相應的visibility map中, 第6.2節中描述了visibility map.
讓我們探討一下以下select命令執行的時候,Postgresql是如何去取tuples的
testdb=# SELECT id, name FROM tbl WHERE id BETWEEN 18 and 19;
id | name
----+--------
18 | Queen
19 | Boston
(2 rows)
查詢從表的兩列獲取數據:“id"和"name”, 索引"tbl_idx"包含這些列。因此,在使用索引掃描時, 乍一看似乎並不需要訪問表頁面,因爲索引tuples包含了必要的數據。然而, 事實上, PostgreSQL原則上必須檢查tuples的可見性,並且索引tuples沒有相關事務的任何信息,例如,heap tuples的t_xmin和t_xmax, 在第5.2節中所述。因此, PostgreSQL必須訪問表數據, 以檢查索引tuples中數據的可見性。這就像把馬車放在馬面前。
爲了避免這種尷尬,PostgreSQL使用表的visibility map。如果存儲在一個頁面中的所有tuples都可見, PostgreSQL將使用索引tuples的鍵, 並且不訪問從索引tuples指向的表頁來檢查其可見性;除此之外,PostgreSQL會讀取索引tuples指向的表tuples,並檢查tuples的可見性,這是一般常見的過程。
在此示例中, 不需要訪問"Tuple_18", 因爲存儲"Tuple_18"的第0頁是可見的, 也就是說,在第0頁中包括Tuple_18的所有tuples都可見。相反, 需要訪問"Tuple_19" 來處理併發控制,因爲第1頁的可見性是不可見。參見圖7.7。
圖7.7. Index-Only Scans如何執行