第四章 數據字典詳解

gp 是基於 PostgreSQL開發的,大部分數據字典是一樣的;gp 也有自動的一些數據字典,一般是以 gp_ 開頭

4.1 oid 無處不在

oid 是一種特殊的數據類型,在 PG/GP 中,oid 都是遞增的,每一個表空間、表、索引、數據文件名、函數、約束等都對應有一個唯一標識的 oid。oid 是全局遞增的,可以把 oid 想象成一個遞增的序列(SEQUENCE)

通過下面的語句可以找到數據字典中帶隱藏字段 oid 的所有表,這些表的 oid 增加都是共享一個序列的。還有其他表也是共享這些序列,如 pg_class 的 relfilenode

1

2

3

由於數據庫中存在大量的 oid ,oid 是一個32位的數字,下面幾種可以把 oid 轉換的類型:

4

最常用的是 regclass,它關聯數據字典的 oid,使用方法:

5

6

這樣就可以通過 regclass 尋找一個表的信息,就不用去關聯 pg_class 和 pg_namespace(記錄 schema 信息)。

其他:

  • regproc(regprocedure)與 pg_proc(保存普通函數的命令)
  • regoper(regoperator)與 pg_operator(操作符)的 oid 關聯

4.2 數據庫集羣信息

gp的集羣配置信息在 Master 上面,這些配置信息對集羣管理非常重要,通過這些配置信息可以瞭解整個集羣的狀況,可是得知是否有節點失敗,通過修改這些配置可以實現集羣的擴容等操作

4.2.1 Gp_configuration 和 gp_segment_configuration

在 gp 3.x 版本中,集羣的配置信息記錄在 gp_configuration 中。表 gp_configuration 中的字段含義如下:

7

在 gp 4.x 版本中,由於引入了文件空間(filespace)的概念,一個節點的數據目錄可以是多個,因此將 gp_configuration 拆分成兩個表,gp_segment_configuration 和 pg_filespace_entry 。gp 4.x 引入了基於文件的數據同步策略,索引頁相應增加了幾個數據字典來體現這一特性:

8

這兩個表是在 pg_global 表空間下面的,是全局的,同一個集羣中所有數據庫共用的信息

4.2.2 Gp_id

在 gp 3.x 中,每一個子節點都有 gp_id 表,這個表記錄該節點在集羣中的配置信息

9

在 gp 4.x 中,這個 gp_id 表已經廢棄掉,所有子節點的 gp_id 數據都是一模一樣的:

10

因此這些信息都是在啓動子節點的時候通過 pg_ctl 的啓動參數傳進了的,這些信息在 gp 4.x 中都是以數據庫參數的形式存在的,所以可以建一個視圖,獲取跟 gp 3.x 中 gp_id 表一模一樣的信息

11

gp_id 表除了其本身所要表達的數據之外,還有一個特殊的功能:這個表在每一個數據節點中都只有一條數據,這對獲取子節點的數據十分有用

4.2.3 Gp_configuration_history

當數據節點失敗的時候,gp Master 通過心跳檢測機制檢測出 Segment 失敗,就會觸發 主、備數據節點切換的動作,每一個動作都會記錄在 gp_configuration_history 表中。gp_configuration_history 表結構如下:

12

當數據庫發生切換時可以通過 gp_configuration_history 表來了解數據庫切換的原因,以及發生切換的時間

4.2.4 pg_filespace_entry

文件空間(filespace)(gp 4.x)一個數據庫的數據節點可以有多個數據目錄,所以數據目錄的字段信息從 gp_configuration 中抽離出來,保存在 pg_filespace_entry 表中:

13

4.2.5 集羣配置信息錶轉化

應用場景:gp 3.x 和 gp 4.x 的集羣同時存在,或者 gp 3.x 升級到 gp 4.x 時,外部程序如果需要獲取 gp 集羣的配置信息,就必須對 gp 3.x 和 gp 4.x 分別進行識別處理,使其對應不同的數據字典。

可以統一定義一個視圖,將 gp 4.x 的集羣配置的數據字典轉換成 gp 3.x 的模式:

gp 3.x 的視圖定義:

14

gp 4.x 的視圖定義:

15

16

4.3 常用數據字典

4.3.1 pg_class

pg_class:數據字典中最重要的一個表,保存了所有表、視圖、序列、索引的原數據信息,每一個 DDL/DML cozy都必須跟這個表發生聯繫:

17

18

19

20

建在這個表上的索引:

21

權限控制,通過pg_class.relacl 可查:

22

具體解釋:

23

利用數據庫中的其他函數更方便的查權限:

24

查詢 role_aquery 用戶是否具有訪問 public.cxfa3 表的 select 權限,結果爲 't' 表示有這個權限,結果爲 'f' 表示沒有這個權限

25

26

4.3.2 pg_attribute

pg_attribute:記錄字段內容:

27

28

29

同一個表在 pg_attribute 中的記錄數會比實際表的字段數多,這是因爲表中有很多的隱藏字段:

30

31

4.3.3 gp_distribution_policy

表的分佈鍵保存在 gp_distribution_policy 表中:

32

4.3.4 pg_statistic 和 pg_stats

數據庫中表的統計信息保存在 pg_statistic 中,表中的記錄是由 ANALYZE 創建的,並且隨後被查詢規劃器使用。注意所有統計信息天生都是近似的數值

對應 pg_statistic 有一個視圖 pg_stats,可以方便我們查看 pg_statistic 的內容。這個視圖的數據比 pg_statistic 好理解:

33

34

4.4 分區表信息

4.4.1 如何實現分區表

分區的意思是把邏輯上的一個大表分割成物理上的幾塊。gp 中分區表的實現基本上是與 pgsql 中實現的原理一樣,都是通過表繼承、規則、約束來是實現的。

pgsql 中分區表的建立步驟:

  1. 創建“主表”,所有分區都從它繼承:這個表沒有數據,不要澡這個表上定義任何檢查約束,除非希望約束同樣也適用於所有分區;在主表上定義任何索引或唯一約束都每一意義。

  2. 創建幾個“子表”,每個都從主表上繼承。通常,這些表不會增加任何字段。子表即分區。

  3. 爲分區表增加約束,定義每個分區允許的鍵值

    典型的例子是:

    35

    ​確保這些約束在不同的分區中不會有重疊的鍵值,一個常見的錯誤是設置下面這樣的範圍:

    36

    ​這裏的錯誤是因爲沒明確指定 200 所屬的區間

    注意在範圍和列表分區的語法方面沒有什麼區別,這些術語只是用於描述.

  4. 對於每個分區,在關鍵字字段上創建一個索引,以及其他想創建的索引。關鍵字字段索引並非必需的,但是在大多數情況下它是很有幫助的。如果希望關鍵字值是惟一的,那麼應該總是爲每個分區創建一個唯一或主鍵約束

  5. 定義一個規則或觸發器,把對主表的修改重定向帶合適的分區表

    對於 gp 來說,分區表的實現原理與上面一樣,只不過 gp 對分區表進行了一個更好的封裝,使用戶使用起來更加方便,可以通過 create table 直接建立分區表,可以使用 alter table 等對分區表進行操作

    跟 pgsql 一樣,分區表的子表也是通過對父表的繼承得來的,這些繼承關係是放在 pg_inherts 這個數據字典中的。

4.4.2 pg_partition

一個表是否是分區表保存在 pg_partition 中,如果一個表是分區表(不包括自分區),則對應有一條記錄在這個數據字典中:

37

該表有 3 個索引:

38

如果想查詢一個表是否是分區表,只要將 pg_partition 與 pg_class 關聯,然後執行 count 即可,如果這個表中有數據,則爲分區表,否則不是分區表。

39

4.4.3 pg_partition_rule

分區表的分區規則保存在 pg_partition_rule。在這個表中,可以找到一個分區表對應的子表有哪些即分區規則等信息:

40

41

4.4.4 pg_partitions 視圖及其優化

在 gp 中,定義了一個 pg_partitions 的視圖,方便對分區表進行查看,這個視圖的定義非常複雜,考慮了多重分區的情況,對很多數據字典做了連接和內連接。因此,當數據字典很大時,查詢這個預提的效率極差,不能很好地利用索引,查詢採用的是全表掃描。下面定義一個函數來代替 pg_partitions 視圖,充分利用索引,查詢是通過 regclass 減少表的關聯,大大提高查詢的效率。

123

在查詢時通過 ::regclass 來查詢表信息:

42

4.5 自定義類型以及類型轉換

在 gp 中,經常使用 cast 函數或 ::type 進行類型轉換,究竟哪兩種類型之間是可以轉換的,哪兩種類型之間不能轉換,轉換的規則是什麼,這些都在 pg_case 中定義了:

43

要想知道 text 類型到 date 類型的轉換是用了哪個函數,可以這麼查:

44

可以看出,cast('20110302' as date) 和 '20110302'::date 其實都調用了 date('20110302') 函數進行類型轉換。

自定義轉換類型,例如gp 默認的類型轉換中,是沒有 regclass 類型到 text 類型的轉換的:

45

先創建一個類型轉換函數:

46

然後定義一個 cast 類型轉換規則:

47

這樣就定義好了一個類型轉換,效果如下:

48

4.6 主、備節點同步的相關數據字典

在 gp4.x 版本之後,數據庫主、備節點之間的同步通過基於數據文件的物理備份來實現,這與 gp 3.x 的 邏輯備份有很大的區別。

在 gp 4.x 中,分別由表4-15 中的 5 章數據字典來保存基於數據文件的備份信息。這些數據字典都是用於在主節點與備節點間基於文件備份的同步信息。

49

在這幾張表中,數據量最大、最重要的表應該是 gp_persistent_relation_node 和 gp_relation_node 。這些數據字典在每一個節點中都有,如果主節點和備節點處於完全同步的狀態,則主節點和備節點對應的這幾張數據字典表的內容應該是一模一樣的。

4.7 數據字典應用示例

4.7.1 獲取表的字段信息

表名放在 pg_class 中, schema 名放在 pg_namespace 中,字段信息放在 pg_attribute 中。一般關聯着 3 張表:

50

使用 regclass 就會簡化很多:

SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod) as data_type 
FROM pg_catalog.pg_attribute a
WHERE a.attrelid = 'pg_catalog.pg_class'::regclass
  AND a.attnum > 0
  AND NOT a.attisdropped 
ORDER BY a.attnum;

其實regclass 就是一個類型,oid 或 text 到 regclass有一個類型轉換,與奪標關聯不一樣。

注意:在多數據字典表關聯的情況下,如果表不存在,會返回空記錄,不會報錯,如果採用了 regclass,則會報錯,所以在不確定表是否存在的情況下,慎用 regclass。

4.7.2 獲取表的分佈鍵

gp_distribution_policy 是記錄分佈鍵信息的數據字典,localoid 與 pg_class 的 oid 關聯。attrnums 是一個數組,記錄字段的 attnum,與 pg_attribyte 從的attnum 關聯。

51

這樣就可以關聯 pg_attribute 來獲取分佈鍵:

52

結果如下:

53

4.7.3 獲取一個視圖的定義

函數 pg_get_viewdef,可以直接獲取視圖的定義:

54

使用這個系統函數可以獲取上視圖的定義,可以傳入表的 oid 或表名,第org參數表示十分格式化輸出,默認不格式化輸出。

55

這個函數是獲取數據字典 pg_rewrite(存儲爲表和視圖定義的重寫規則),將規則重新還原出 sql 語句展示給我們。可以通過下面語句去查詢數據庫保存的重寫規則,圖4-1是一個簡單視圖的規則定義:

56

與 pg_get_viewdef 類似的函數還有很多,如圖4-2所示。

57

4.7.4 查詢 comment(備註信息)

comment 信息是放在表 pg_description 中的:

58

查詢表上的 comment 信息:

59

查詢表中字段的 comment 信息:

60

4.7.5 獲取數據庫建表語句

get_create_sql 是用於獲取表和視圖的 DDL 語句,不支持外部表,建表語句包括以下內容:

  1. 字段信息
  2. 索引信息
  3. 分區信息(主要考慮到性能,目前只支持單一分區鍵,一層分區)
  4. comment 信息
  5. distributed key
  6. 是否壓縮、列存儲、appendonly
  7. 只有一個參數,tablename 爲 schemaname.tablename,輸出爲一個 text 文本。

下面是這個函數的代碼:

61

62

如果該表不是一般表或試圖,則報錯.

63

64

65

66

67

68

69

70

71

圖4-3 是一個 get_create_sql 的例子:

72

4.7.6 查詢表上的視圖

要獲取一個表上依賴的視圖很麻煩,因爲視圖定義在 pg_rewite_rule 中,存放成一種規則,而且上面沒有索引,所以獲取比較麻煩。下面介紹通過 pg_depand 表來獲取表上依賴視圖的方法(一個視圖是定義在表上的,這個視圖肯定是依賴於這個表的,所以在 pg_depend 中有響應的信息)

創建一個通過 oid 來獲取模式名和表名的函數:

73

然後建立視圖:

74

75

下面是如何使用這個視圖的例子:

76

4.7.7 查詢表的數據文件創建時間

有一個比較取巧的方法來獲取一個表的修改時間。在 gp 中,每一個表都對應文件系統上的幾個文件,這樣我們就可以通過數據文件的創建時間和修改時間估計這個表的創建時間。

通過一些自定義函數,利用數據字典獲取數據文件對應的數據目錄和文件名,就可以在數據庫中獲取文件的時間,從而可以定義一個視圖來方便查詢,相當於自定義一個元數據視圖。

(1)如何在數據庫中獲取一個文件的信息:

gp 自帶了一個函數 pg_stat_file。通過它可以獲取文件的信息。下面介紹 data_direcotry 目錄下 pg_hba.conf 文件的信息。

77

但是這個函數只有用在數據庫 data_directory 目錄(用 show data_directory) 下才可顯示。

這在 gp3.x 版本中已經可以完成了,但是 gp4.x 引入了filespace 的概念(具體見第九章),所以在 filespace下面的wfjm不能用 pg_stat_file 來查看。因此下面重新利用 plpythonu 編寫一個函數來獲取文件信息,同時捕獲文件不存在等異常,代碼如下:

78

79

(2)利用數據字典拼接出文件目錄和文件名:

  • 文件名:pg_class 的 relfilenode 字段。
  • 表空間:pg_class 的 reltablespace 字段。
  • 表空間對應的 filespace:pg_tablespace。
  • Filespace 目錄:pg_filespace_entry。
  • database 的 oid :查詢 pg_database 和當前數據庫名。

視圖的定義如下:

80

81

視圖使用效果如圖4-4 所示:

82

4.7.8 分區表總大小

下面提供一個函數將檢查分區大小的操作封裝起來:

83

84

85

86

87

效果如圖4-5所示:

88

4.7.9 如何分析數據字典變化

對於 gp 數據字典來說,總的數據字典也只有 60 來張表,如果對於每一個 DDL 命令,我們能夠知道有哪些數據字典發生了變化,這樣對於我們深入瞭解數據庫底層邏輯有很大的幫助,瞭解了這些,對數據庫優化跟原數據的應用有更深入的幫助。

下面介紹一種方法來觀察每一個 sql 對應數據字典的變化,原理是對所有數據字典在 DDL 操作之前跟之後都做一個快照(記錄每個表的最大事務 ID 等信息),然後比較兩個快照時間的數據發生了哪些變化,從而分析數據字典的變化。

首先需要創建一張表和兩個函數:

  • catalog_result 表:保存 snapshot 結果的表,每一個表有一個 ID。
  • catalog_snap 函數:對當前數據字典的最大 ctid,最小 xmin(事務 id)以及記錄數,做一個快照(snapshot),返回快照 ID。
  • diff_catalog 函數:比較兩個 snapshot 之間的數據差異,找出變化的數據字典。

gp 中記錄事務 id 的數據類型 xid 不能進行比較,故使用 UDF 將其轉換成integer 類型,方便比較:

89

表結構:

90

創建 snapshot 的函數:

91

比較兩個 snapshot 之間差異的函數:

92

93

使用方式如下:

94

最後使用 diff_catalog 來查看哪些數據字典發生了變化,如圖4-6 所示:

95

以下是使用此方法觀察數據字典的注意事項:

  • 要在比較乾淨的測試環境中進行實驗,這樣分析數據字典時比較快速。
  • 這個方法只是主節點上數據字典的變化,要觀察 Segment 的變化,可以直接登錄到 Segment 節點進行同樣的操作。
  • 在同一時間,只能有一個 DDL 操作,避免會話進行操作而造成干擾。
  • 測試環境應當儘量對數據字典發生 insert 和 update 操作,但是這對常見的 DDL 操作已經足夠了,如果要觀察到所有的操作,則需要將整個數據字典對進行保存,執行操作後將變化後的數據字典與變化前的變化差異比較得到結果。

4.7.10 獲取數據庫鎖信息

視圖 pg_locks 保存了數據庫的鎖信息,但是這個視圖很不方便。要查詢一個表被哪個進程鎖住了,就需要將 pg_class、pg_locks、pg_stat_activity 關聯起來,如下:

SELECT pgl.locktype AS lorlocktyppe, pgl.database AS lordatabase, pgc.relname AS lorrelname, pgl.relation AS lorrelation, pgl.transaction AS lortransaction, pgl.pid AS lorpid, pgl.mode AS lormode, pgl.granted AS lorgranted, pgsa.current_query AS lorcurrentquery
FROM pg_locks pgl
JOIN pg_class pgc ON pgl.relation = pgc.oid
JOIN pg_stat_activity pgsa ON pgl.pid = pgsa.procpid
ORDER BY pgc.relname;

注意:上面這個 sql 就是 gp_toolkit.gp_locks_on_relation 的定義,在 gp3.x 中可以自己創建。

pg_stat_activity 可以獲取當前正在運行的 sql。

下面介紹兩個函數,殺掉當前的進程,參數都是這個 sql 的進程號(pid)。

  • pg_cancel_backend:取消一個正在執行的 sql。
  • pg_terminate_backend:終止一個正在執行的 sql。

pg_terminate_backend 比 pg_cancel_backend 的強度大,一般要殺掉 sql 進程,可以先用 pg_cancel_backend 殺掉 sql 進程,如果殺不掉,再用 pg_terminate_backend 將 sql 進程殺掉。

例如,可以利用這個函數,將鎖住表 test001 的進程殺掉,如圖4-7所示:

96

4.8 gp_toolkit 介紹

gp 4.x 之後的版本提供了 gp_toolkit 的工具箱,方便用戶對數據字典進行分析和管理,這個工具箱都是基於數據上字典建立的視圖(所有的視圖都在 gp_toolkit 的 schema 下面,如果經常使用這個工具箱,建議將這個 schema 加入到 search_path 中):

97

98

99

注意:如果 gp_toolkit 沒有安裝,可以用下面這個命令進行安裝:

psql -f $GPHOME/share/postgresql/gp_toolkit.sql

下面我們選一個視圖(gp_bloat_expecter_pages)來詳細講解,以加深對 gp 數據字典的認識。

這個視圖是對堆表(heap)理想大小與實際大小的比較。首先看一下這個視圖的定義:

100

101

其中,pg_class 中的 relpages 字段作爲實際大小,理想大小通過 pg_statistic 加上 pg_class 兩個合起來算,算法如下:

102

從這個視同的定義我們可以看出表大小的估算方法,也可以看到這個視圖的侷限性:

  • 表大小都是估算值,因爲統計信息只是估計值,
  • 依賴於統計信心收集的時間,要更加準確,需要重新分析、收集最新的信息。

知道了這個方法,我們可以簡單驗證這個估算方法的準確性,如圖4-8所示:

103

這些表的數據都是遞增的,即每月發生過 update 和 delete的,所以數據文件中應該是沒有空洞的。可以看出,估計值還是有挺大的差異的,所以這個表得出的只能是大概的估計值,對於不同的表,偏差會比較大。

4.9 小結

本章介紹了常用數據字典的表結構,介紹了各個數據字典之間的關係,還通過幾個例子加深對數據字典的認識。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章