在 pg v11 和 v12 上 常見測試用例
CREATE TABLE rel ( a bigint NOT NULL, b bigint NOT NULL ); ALTER TABLE rel ADD CONSTRAINT rel_pkey PRIMARY KEY (a, b); CREATE INDEX rel_b_idx ON rel (b); \d rel Table "public.rel" Column | Type | Collation | Nullable | Default --------+--------+-----------+----------+--------- a | bigint | | not null | b | bigint | | not null | Indexes: "rel_pkey" PRIMARY KEY, btree (a, b) "rel_b_idx" btree (b)
- 它確保“a”和“b” 兩字段的每種組合最多有一個條目。
- 它可以加快與給定“b”相關的所有“a”的搜索速度。
加入測試數據
INSERT INTO rel (a, b) SELECT i, i / 10000 FROM generate_series(1, 20000000) AS i; /* 收集統計信息 */ VACUUM (ANALYZE) rel;
B-tree索引提高1:插入很多重複的索引和數值
當我們比較的b列索引的大小的第一個區別是顯而易見的:
v11: \di+ rel_b_idx List of relations Schema | Name | Type | Owner | Table | Size | Description --------+-------------+-------+----------+-------+--------+------------- public | rel_b_idx | index | postgres | rel | 545 MB | (1 row)
v12: \di+ rel_b_idx Schema | Name | Type | Owner | Table | Size | Description --------+-------------+-------+----------+-------+--------+------------- public | rel_b_idx | index | postgres | rel | 408 MB | (1 row)
v11 比 v12 還要大 33%
每一個b列在index發生10000次,因此會有很多葉子節點的所有密鑰是相同的(每個葉子節點可以包含幾百項)。
V12之前,葉子頁必須是分立的,有時是最右邊的葉子節點,但有時不是。最右邊的葉子節點總是朝着右端,
以優化單調遞增插入拆分。與此相反,其他葉子節點是在中間,其中浪費的空間分割。
與V12,該表的行的物理地址(“元組ID”或TID)是索引關鍵字的一部分,所以重複的索引條目存儲在表的順序。
這會造成這樣的條目索引掃描訪問的物理順序表,它可以是一個顯著的性能優勢,特別是在機械磁盤。
換句話說,重複索引條目的相關性將是完美的。而且,僅由重複的頁將在右端分裂,產生密集索引。
加入類似的優化多列索引,但它並不適用於我們的主鍵索引,因爲重複不是在第1列。
主鍵索引在V11和V12緊湊,因爲第一列是單調遞增的,所以葉頁拆分在最右邊的頁面總是發生。
PostgreSQL的已經有針對的優化。
B-tree索引提高2:內部索引頁面的壓縮存儲
對於主鍵索引的改進是不那麼明顯,因爲它們幾乎在尺寸在V11和V12相同。我們必須更深入的挖掘這裏。
首先,觀察指標,只有在這兩個V11和V12(塊緩存)掃描:
在v11: EXPLAIN (ANALYZE, BUFFERS, COSTS off, SUMMARY off, TIMING off) S SELECT a, b FROM rel W WHERE a = 420024 AND b = 42; QUERY PLAN - --------------------------------------------------------------- Index Only Scan using rel_pkey on rel (actual rows=1 loops=1) Index Cond: ((a = 420024) AND (b = 42)) Heap Fetches: 0 Buffers: shared hit=5 ( (4 rows) 在v12: EXPLAIN (ANALYZE, BUFFERS, COSTS off, SUMMARY off, TIMING off) S SELECT a, b FROM rel W WHERE a = 420024 AND b = 42; QUERY PLAN - --------------------------------------------------------------- Index Only Scan using rel_pkey on rel (actual rows=1 loops=1) Index Cond: ((a = 420024) AND (b = 42)) Heap Fetches: 0 Buffers: shared hit=4 ( (4 rows)
在v12中,將讀取少一(索引)的塊,這意味着該索引少一級。
由於索引的大小几乎相同,因此必須意味着內部頁面可以容納更多的索引條目。
在v12中,索引具有更大的扇出度。
如上所述,PostgreSQL的V12引入的TID作爲索引關鍵字,這會浪費在內部索引頁的空間過多量的一部分。
所以同一個commit引入的來自內部 Page “冗餘”索引屬性。該TID是多餘的,
因爲是從包含子句非鍵屬性(V11這些也從內部索引頁除去)。
不過,PostgreSQL的V12也可以截斷不需要的錶行識別這些指標的屬性。
在我們的主鍵索引,出價是一個冗餘列,並從內部索引頁,
從而節省了8個字節的每個索引條目空間。讓我們一起來看看與pageinspect擴展內部索引頁:
在 v11: SELECT * FROM bt_page_items('rel_pkey', 2550); itemoffset | ctid | itemlen | nulls | vars | data - ------------+------------+---------+-------+------+------------------------------------------------- 1 | (2667,88) | 24 | f | f | cd 8f 0a 00 00 00 00 00 45 00 00 00 00 00 00 00 2 | (2462,0) | 8 | f | f | 3 | (2463,15) | 24 | f | f | d6 c0 09 00 00 00 00 00 3f 00 00 00 00 00 00 00 4 | (2464,91) | 24 | f | f | db c1 09 00 00 00 00 00 3f 00 00 00 00 00 00 00 5 | (2465,167) | 24 | f | f | e0 c2 09 00 00 00 00 00 3f 00 00 00 00 00 00 00 6 | (2466,58) | 24 | f | f | e5 c3 09 00 00 00 00 00 3f 00 00 00 00 00 00 00 7 | (2467,134) | 24 | f | f | ea c4 09 00 00 00 00 00 40 00 00 00 00 00 00 00 8 | (2468,25) | 24 | f | f | ef c5 09 00 00 00 00 00 40 00 00 00 00 00 00 00 9 | (2469,101) | 24 | f | f | f4 c6 09 00 00 00 00 00 40 00 00 00 00 00 00 00 10 | (2470,177) | 24 | f | f | f9 c7 09 00 00 00 00 00 40 00 00 00 00 00 00 00 . ... 205 | (2666,12) | 24 | f | f | c8 8e 0a 00 00 00 00 00 45 00 00 00 00 00 00 00 ( (205 rows) 在數據輸入我們所看到的援助和出價字節。該實驗在 little-endian 機器上進行的, 所以在第6行的數目將是0x09C3E5和0x3F的或(十進制數)639973和63.每個索引條目是24個字節寬,這8個字節是所述元組報頭。 在 v12: SELECT * FROM bt_page_items('rel_pkey', 2700); itemoffset | ctid | itemlen | nulls | vars | data - ------------+----------+---------+-------+------+------------------------- 1 | (2862,1) | 16 | f | f | ab 59 0b 00 00 00 00 00 2 | (2576,0) | 8 | f | f | 3 | (2577,1) | 16 | f | f | 1f 38 0a 00 00 00 00 00 4 | (2578,1) | 16 | f | f | 24 39 0a 00 00 00 00 00 5 | (2579,1) | 16 | f | f | 29 3a 0a 00 00 00 00 00 6 | (2580,1) | 16 | f | f | 2e 3b 0a 00 00 00 00 00 7 | (2581,1) | 16 | f | f | 33 3c 0a 00 00 00 00 00 8 | (2582,1) | 16 | f | f | 38 3d 0a 00 00 00 00 00 9 | (2583,1) | 16 | f | f | 3d 3e 0a 00 00 00 00 00 10 | (2584,1) | 16 | f | f | 42 3f 0a 00 00 00 00 00 . ... 286 | (2861,1) | 16 | f | f | a6 58 0b 00 00 00 00 00 ( (286 rows)
該數據僅包含a列,因爲a列已經被截斷了。這減少了索引項的大小爲16,讓更多的條目適合索引頁上。
升級注意事項
由於索引存儲在V12被改變,新的B-tree索引第4版已經推出。
由於與pg_upgrade不改變數據文件升級,索引仍然會在3.0版本升級後。
PostgreSQL的V12可以使用這些指標,但上述的優化將不可用。
你需要重新索引的索引將其升級到4.0版本(這已經在PostgreSQL的V12變得更加容易與REINDEX兼)。
其他B-tree索引功能在推出V12
有PostgreSQL中V12添加了一些其他方面的改進。如下簡單列表:
1. 減少B樹索引插入,以提高性能鎖定開銷。
2. REINDEX CONCURRENTLY,重建無停機時間的索引。
3. 完善與許多屬性的索引僅索引掃描性能。
4. 添加視圖 pg_stat_progress_create_index 報到CREATE INDEX和REINDEX進展。
總結
擁有許多重複的條目索引, V12 更有優勢 , 推薦 pg_upgrade後用 REINDEX CONCURRENTLY 重新索引。