PostgreSQL的基礎數據類型分析記錄-轉


src:http://www.codeweblog.com/postgresql%E7%9A%84%E5%9F%BA%E7%A1%80%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%88%86%E6%9E%90%E8%AE%B0%E5%BD%95/

 

前期,我參與了公司開發的數據庫數據遷移工具的工作,以及之前的對Page的分析記錄,在此進一步將數據庫的數據類型做一下分析記錄。

一、數據庫系統表pg_type

PostgreSQL的所有數據類型都存儲在系統表pg_type中。
pg_type的表結構如下(這里是從源碼中進行介紹的,源碼可以點擊pg_type.h):

CATALOG(pg_type,1247) BKI_BOOTSTRAP BKI_ROWTYPE_OID(71) BKI_SCHEMA_MACRO { NameData typname; /* type name */ Oid typnamespace; /* OID of namespace containing this type */ Oid typowner; /* type owner */ int16 typlen; bool typbyval; char typtype; char typcategory; /* arbitrary type classification */ bool typispreferred; /* is type "preferred" within its category? */ bool typisdefined; char typdelim; /* delimiter for arrays of this type */ Oid typrelid; /* 0 if not a composite type */ Oid typelem; Oid typarray; regproc typinput; /* text format (required) */ regproc typoutput; regproc typreceive; /* binary format (optional) */ regproc typsend; regproc typmodin; regproc typmodout; regproc typanalyze; char typalign; char typstorage; bool typnotnull; Oid typbasetype; int32 typtypmod; int32 typndims; Oid typcollation; #ifdef CATALOG_VARLEN /* variable-length fields start here */ pg_node_tree typdefaultbin; text typdefault; aclitem typacl[1]; #endif } FormData_pg_type; 

下面來簡單介紹pg_type的各個字段含義。

typnametypnamespacetypowner 這三個字段名字上就可以看出來他們的含義。
typlen:這是標明類型的長度的,如果類型是定長的就是寫明字段的長度(字節)。如果是變長的則是-1。比如int4也就是int或者integertyplen為4,占用4個字節,varchar則為-1。
typbyval:判斷內部過程傳遞這個類型的數值時是通過傳值還是傳引用。如果該類型不是 1, 2, 4, 8 字節長將只能按應用傳遞,因此 typbyval 最好是假。 即使可以傳值,typbyval 也可以為假。比如float4就是如此。
typtype:對於基礎類型是b, 對於復合類型是 c (比如,一個表的行類型)。對於域類型是d,對於偽類型是p.
本博文也是主要分析基礎類型。
typcategory:這是對數據類型進行分類的,int2、int4、int8的typcategory都是N。typcategory的分類詳看下表:

Code Category
A Array types
B Boolean types
C Composite types
D Date/time types
E Enum types
G Geometric types
I Network address types
N Numeric types
P Pseudo-types
R Range types
S String types
T Timespan types
U User-defined types
V Bit-string types
X unknown type

typispreferred:這個字段和 typcategory是一起工作的,表示是否在 typcategory分類中首選的。
typisdefined:這個字段是類型能否使用的前提,標識數據類型是否被定義,false的話,根本無法使用。(大家可以將int4的 typis的fined改為false,然后用int4作為表的字段類型建表,會直接報錯type integer is only a shell)。
typdelim:當分析數組輸入時,分隔兩個此類型數值的字符請注意該分隔符是與數組元素數據類型相關聯的,而不是和數組數據類型關聯。
typrelid:如果是復合類型(見 typtype)那么這個字段指向 pg_class 中定義該表的行。對於自由存在的復合類型,pg_class 記錄並不表示一個表,但是總需要它來查找該類型連接的 pg_attribute 記錄。對於非復合類型為零。
typelem:如果不為 0 ,那么它標識 pg_type 里面的另外一行。當前類型可以當做一個產生類型為 typelem 的數組來描述。一個"真正的"數組類型是變長的(typlen = -1),但是一些定長的(typlen > 0)類型也擁有非零的 typelem(比如 name 和 point)。如果一個定長類型擁有一個 typelem ,那么他的內部形式必須是 typelem 數據類型的某個數目的個數值,不能有其它數據。變長數組類型有一個該數組子過程定義的頭(文件)。
typarray:指向同類型的數組類型的Oid。
typinput,typoutput:類型的輸入輸出函數,數據庫進行對數字進行存儲或者輸出,首先由客戶端獲取數據 (一般為字符串 )進行轉化,變為數據庫能夠使用的數據類型。輸出函數亦然。
typreceive,typsend:輸入、輸出轉換函數,多用於二進制格式。
typmodin,typmodout:對於變長的數據的輸入、輸出,這里主要是指vachar、time、timestamp等。這個字段和系統表pg_attribute的atttypmod相關聯。
typanalyze:自定義的 ANALYZE 函數,如果使用標准函數,則為 0。
typalign:當存儲此類型的數值時要求的對齊性質。它應用於磁盤存儲以及該值在 PostgreSQL 內部的大多數形式。如果數值是連續存放的,比如在磁盤上以完全的裸數據的形式存放時,那么先在此類型的數據前填充空白,這樣它就可以按照要求的界限存儲。對齊引用是該序列中第一個數據的開頭。
可能的值有:
c = char 對齊,也就是不需要對齊。
s = short 對齊(在大多數機器上是 2 字節)
i = int 對齊(在大多數機器上是 4 字節)
d = double 對齊(在大多數機器上是 8 字節,但不一定是全部)
typstorage:告訴一個變長類型(那些有 typlen = -1)的)說該類型是否准備好應付非常規值,以及對這種屬性的類型的缺省策略是什么。可能的值有: 
p: 數值總是以簡單方式存儲
e: 數值可以存儲在一個"次要"關系中
m: 數值可以以內聯的壓縮方式存儲
x: 數值可以以內聯的壓縮方式或者在"次要"表里存儲。
請注意 m 域也可以移到從屬表里存儲,但只是最后的解決方法(e 和 x 域先移走)。
typnotnull:代表在某類型上的一個 NOTNULL 約束。目前只用於域。
typbasetype:如果這是一個衍生類型(參閱 typtype),那么該標識作為這個類型的基礎的類型。如果不是衍生類型則為零。
typtypmod:域使用 typtypmod 記錄要作用到它們的基礎類型上的 typmod (如果基礎類型不使用 typmod 則為 -1)。如果這種類型不是域,那么為 -1 。
typndims:如果一個域是數組,那么 typndims 是數組維數的數值(也就是說,typbasetype 是一個數組類型;域的 typelem 將匹配基本類型的 typelem)。非域非數組域為零。
typcollation:指定類型的排序規則。如果類型不支持的排序規則,這將是零。支持排序規則基本類型都會有DEFAULT_COLLATION_OID這里。在一個collatable類型一個域可以有一些其他的排序規則的OID,如果已為域指定。
typdefaultbin:如果為非 NULL ,那么它是該類型缺省表達式的 nodeToString() 表現形式。目前這個字段只用於域。
typdefault:如果某類型沒有相關缺省值,那么 typdefault 是 NULL 。如果 typdefaultbin 不是 NULL ,那么 typdefault 必須包含一個 typdefaultbin 代表的缺省表達式的人類可讀的版本。如果 typdefaultbin 為 NULL 但 typdefault 不是,那么 typdefault 是該類型缺省值的外部表現形式,可以把它交給該類型的輸入轉換器生成一個常量。
typacl[1]:用戶對類型的權限。

rolename=xxxx -- privileges granted to a role
        =xxxx -- privileges granted to PUBLIC

            r -- SELECT ("read")
            w -- UPDATE ("write") a -- INSERT ("append") d -- DELETE D -- TRUNCATE x -- REFERENCES t -- TRIGGER X -- EXECUTE U -- USAGE C -- CREATE c -- CONNECT T -- TEMPORARY arwdDxt -- ALL PRIVILEGES (for tables, varies for other objects) * -- grant option for preceding privilege /yyyy -- role that granted this privilege 

以上就是對系統表pg_type的介紹。下面主要針對每一個基礎數據類型分析。

二、類型詳解:

1、整數類型

(1)整數類型:

首先是整數類型int2、int4(等價integer)、int8。
為了方便說明,用下表來說明一下:


PostgreSQL類型名

 

占位(字節) C\C++類型名 Java類型名 取值范圍
int2(samllint) 2 short int
short

 

-32,768到32,767
int4(int、integer) 4 int int -2,147,483,648到2,147,483,647
int8(bigint) 8 long int long -9,223,372,036,854,775,808到9,223,372,036, 854,775,807

在數據庫物理文件存儲的整數數字是以二進制的形式存儲的。下面做一下小實驗:

highgo=# create table aa(a1 int2, a2 int4, a3 int8);
CREATE TABLE
highgo=# insert into aa values (204,56797,2863311530); INSERT 0 1 highgo=# checkpoint ; CHECKPOINT 

通過hexdump(輸出16進制 )對二進制文件進行查看:

[root@localhost 12943]# hexdump 16385 0000000 0000 0000 1420 018a 0000 0000 001c 1fd8 0000010 2000 2004 0000 0000 9fd8 0050 0000 0000 0000020 0000 0000 0000 0000 0000 0000 0000 0000 * 0001fd0 0000 0000 0000 0000 069b 0000 0000 0000 0001fe0 0000 0000 0000 0000 0001 0003 0800 0018 0001ff0 00cc 0000 dddd 0000 aaaa aaaa 0000 0000 0002000 

cc、dddd、aaaaaaaa正好是我插入的三個數字204, 56797, 2863311530。

(2)浮點數

float4、float8:這兩個類型有些不同,先看看范圍:

float4(real) 4 float float 6 位十進制數字精度
float8(double precision) 8 double double 15 位十進制數字精度

在源碼中為:

typedef float float4; typedef double float8; 

存儲方式和C\C++中是相同的。可以看一下示例:

postgres=# create table floatdouble(f1 float4, d1 float8);
CREATE TABLE
postgres=# insert into floatdouble values (12345, 12345); INSERT 0 1 postgres=# checkpoint ; CHECKPOINT 

看一下物理文件存儲的數據(這里都是以16進制顯示的):

[root@localhost 12814]# hexdump 16399 0000000 0000 0000 9bc0 0188 0000 0000 001c 1fd8 0000010 2000 2004 0000 0000 9fd8 0050 0000 0000 0000020 0000 0000 0000 0000 0000 0000 0000 0000 * 0001fd0 0000 0000 0000 0000 06b9 0000 0000 0000 0001fe0 0000 0000 0000 0000 0001 0002 0800 0018 0001ff0 e400 4640 0000 0000 0000 0000 1c80 40c8 0002000 

12345變為了 e400 4640(float4),12345變為了 1c80 40c8。

現在簡單介紹一下float,它的存儲方式為:

PostgreSQL的基礎數據類型分析記錄

共計32位,折合4字節。由最高到最低位分別是第 31、30、29、……、0位。31位是符號位,1表示該數為負,0反之。30-23位,一共8位是指數位。22-0位,一共23 位是尾數位。

現在讓我們按照IEEE浮點數表示法,一步步的將float型浮點數12345轉換為十六進制代碼。首先數字是正整數,所以符號位為0,接下來12345的二進制表示為11000000111001,小數點向左移,一直移到離最高位只有1位,就是最高位的1。即1.1000000111001*2^13,所有的二進制數字最前邊都有一個1,所以可以去掉,那么尾數位的精確度其實可以為24 bit。再來看指數位,因為是有8 bit,所以只為能夠表示的為0~255,也可以說是-128~127,所以指數為為正的話,必須加上127,即13+127=140,即10001100。好了,所有的數據都整理好了,現在表示12345的float存儲方式即01000110010000001110010000000000,現在把它轉化為16進制,即4640 e400,而存儲文件是從下向上寫入的,所以表示為 e400 4640。

double,它的存儲方式為:

PostgreSQL的基礎數據類型分析記錄

指數位與尾數部分都要比float增加了長度,所以計算方法還是同上,只不過現在的指數位要加的是1023,尾數部分自動補更多的零。

注:PostgreSQL 還支持 SQL 標准表示法 float 和 float(p) 用於聲明非精確的數值類型。其中的 p 聲明以二進制位表示的最低可接受精度。在選取 real 類型的時候,PostgreSQL 接受 float(1) 到 float(24),在選取 double precision 的時候,接受 float(25) 到 float(53) 。在允許范圍之外的 p 值將導致一個錯誤。沒有聲明精度的 float 將被當作 double precision 。

(3)Numeric

數字類型還有一種便是numeric(decimal),這種數據類型是數字當中最為復雜的一種了,他是一種結構體,在源碼中為:

typedef int16 NumericDigit;

struct NumericShort { uint16 n_header; /* Sign + display scale + weight */ NumericDigit n_data[1]; /* Digits */ }; struct NumericLong { uint16 n_sign_dscale; /* Sign + display scale */ int16 n_weight; /* Weight of 1st digit */ NumericDigit n_data[1]; /* Digits */ }; union NumericChoice { uint16 n_header; /* Header word */ struct NumericLong n_long; /* Long form (4-byte header) */ struct NumericShort n_short; /* Short form (2-byte header) */ }; struct NumericData { int32 vl_len_; /* varlena header (do not touch directly!) */ union NumericChoice choice; /* choice of format */ }; 

因為這里使用的是union,所以我們可以對struct重新定義一下,按照在內存中的表現形式:

struct NumericShort_memory { int32 vl_len_; uint16 n_header; NumericDigit n_data[1]; }; struct NumericLong_memory { int32 vl_len_; uint16 n_sign_dscale; int16 n_weight; NumericDigit n_data[1]; }; 

還有一個比較重要的結構體,它的作用是最為char*和numeric之間進行轉化的中間體:

typedef struct NumericVar { int ndigits; /* # of digits in digits[] - can be 0! */ int weight; /* weight of first digit */ int sign; /* NUMERIC_POS, NUMERIC_NEG, or NUMERIC_NAN */ int dscale; /* display scale */ NumericDigit *buf; /* start of palloc'd space for digits[] */ NumericDigit *digits; /* base-NBASE digits */ } NumericVar; 

組成numeric的結構體就有四個,比較復雜,而且基本上都是通過數組進行存儲的,他的范圍為小數點前為131072位,小數點后為16383位。
首先要講的是NumericVar,這是將數據變為numeirc的第一步,現在以‘12345.678’為例子講一下答題過程,具體的函數以后可能會繼續講一下。數據庫首先讀取字符串'12345.678',然后將字符串變為NumericVar,要說明的是,數據都是存儲到buf(這應該是在物理文件中的補齊所設置的,不過不是特別確定)和digits中的,比如'12345.678',是這樣存儲的 0000 0001 2345 6780,這些都是數字存入到數組中。ndigits是指的digits數組元素的個數,這里就是3,而weight表示的是整數部分所占用的數組元素個數,不過進行了一系列的運算,在保證有整數部分, weight = (整數部分個數 + 4 - 1)/4 - 1。sign,這是對數字進行標記的,有正負標記。dscale則表示的是小數部分數字個數。

下面主要講一下NumericData,按照上面的順序說明一下各個結構體的結構,

NumericShort,這是數據庫對小數據進行存儲用的格式。其中n_header是對數據的標記,根據正負、類型(指的是數字大小類型:NUMERIC_SIGN_MASK、NUMERIC_POS、NUMERIC_NEG、NUMERIC_SHORT、NUMERIC_NAN)weight進行運算得到一個標記。n_data和NumericVar中的digits是相同的。

標記的運算:

result->choice.n_short.n_header =
    (sign == NUMERIC_NEG ? (NUMERIC_SHORT | NUMERIC_SHORT_SIGN_MASK)
        : NUMERIC_SHORT)
        | (var->dscale << NUMERIC_SHORT_DSCALE_SHIFT)
        | (weight < 0 ? NUMERIC_SHORT_WEIGHT_SIGN_MASK : 0) | (weight & NUMERIC_SHORT_WEIGHT_MASK); 

NumericLong,這是數據庫對大數據進行存儲用的格式。其中n_sign_dscale是對數據的標記,根據正負、類型(指的是數字大小類型:NUMERIC_SIGN_MASK、NUMERIC_POS、NUMERIC_NEG、NUMERIC_SHORT、NUMERIC_NAN)進行運算得到一個標記。weight和NumericVar的是相同的。n_data和NumericVar中的digits是相同的。

標記的運算:

result->choice.n_long.n_sign_dscale =
    sign | (var->dscale & NUMERIC_DSCALE_MASK);
result->choice.n_long.n_weight = weight;

NumericChoice,這是union,這能引用同一個存儲塊。然后最后總的NumericData,這里的vl_len_是對數據所占位計算而來的,計算方法見下。

在Java中可以用getBigDecimal來讀取數據。

下面看一下物理存儲:

postgres=# create table numerictest (n1 numeric);
CREATE TABLE
postgres=# select pg_relation_filepath('numerictest'); pg_relation_filepath ---------------------- base/12892/16390 (1 row) postgres=# insert into numerictest values (123),(1234),(12345),(12345.678),(12345.6789),(12345.678901),(12345.123456789); INSERT 0 7 postgres=# checkpoint ; CHECKPOINT 
[root@localhost 12892]# hexdump 16390 0000000 0000 0000 91b0 0173 0000 0000 0038 1ee0 0000010 2000 2004 0000 0000 9fe0 003a 9fc0 003a 0000020 9fa0 003e 9f78 0042 9f50 0042 9f28 0046 0000030 9f00 004a 9ee0 0036 0000 0000 0000 0000 0000040 0000 0000 0000 0000 0000 0000 0000 0000 * 0001ee0 06a0 0000 0000 0000 0000 0000 0000 0000 0001ef0 0008 0001 0802 0018 0007 0080 0000 0000 0001f00 069f 0000 0000 0000 0000 0000 0000 0000 0001f10 0007 0001 0802 0018 811b 0184 2900 d209 0001f20 2e04 2816 0023 0000 069f 0000 0000 0000 0001f30 0000 0000 0000 0000 0006 0001 0802 0018 0001f40 0117 0183 2900 8509 641a 0000 0000 0000 0001f50 069f 0000 0000 0000 0000 0000 0000 0000 0001f60 0005 0001 0802 0018 0113 0182 2900 8509 0001f70 001a 0000 0000 0000 069f 0000 0000 0000 0001f80 0000 0000 0000 0000 0004 0001 0802 0018 0001f90 8113 0181 2900 7c09 001a 0000 0000 0000 0001fa0 069f 0000 0000 0000 0000 0000 0000 0000 0001fb0 0003 0001 0802 0018 010f 0180 2900 0009 0001fc0 069f 0000 0000 0000 0000 0000 0000 0000 0001fd0 0002 0001 0802 0018 000b d280 0004 0000 0001fe0 069f 0000 0000 0000 0000 0000 0000 0000 0001ff0 0001 0001 0802 0018 000b 7b80 0000 0000 0002000 

這里列一個表具體的看一下(這里只說一下short類型的):

數值 ndigits digits 16進制 標記 文件存儲
123 1 0123 7b 0x8000 000b 7b80 0000
1234 1 1234 04d2 0x8000 000b d280 0004
12345 2 0001 2345 0001 0929 0x8001 010f 0180 2900 0009
12345.678 3 0001 2456 6780 0001 0929 1a7c 0x8181 8113 0181 2900 7c09 001a
12345.6789 3 0001 2345 6789 0001 0929 1a85 0x8201 0113 0182 2900 8509 001a 0000
12345.678901 4 0001 2345 6789 0100 0001 0929 1a85 0064 0x8301 0117 0183 2900 8509 641a 0000 0000
12345.123465789 5 0001 2345 1234 5678 9000 0001 0929 04d2 162e 2328 0x8481 811b 0184 2900 d209 2e04 2816 0023 0000
0 0 0000 0000 0x8000 0007 0080

注:這里的16進制是按照digits內存儲的整數轉換的,比如12345在數組digits內為0001,2345,轉化為16進制為0001 0929。lujian
再比如帶有小數的數字例如,12345.678,在數組中為0001,2345,6780,轉化為16進制為0001 0929 1a7c。
這上面的存儲的前兩個字節中的第一個(看起來是第二個),這個值和數據長度vl_len_是相關的,它的計算公式為:

正常的計算為:

Short:
len = NUMERIC_HDRSZ_SHORT + n * sizeof(NumericDigit);
Long:
len = NUMERIC_HDRSZ + n * sizeof(NumericDigit);

SET_VARSIZE(result, len);
#define SET_VARSIZE(PTR, len)           SET_VARSIZE_4B(PTR, len)
#define SET_VARSIZE_4B(PTR,len) \ (((varattrib_4b *) (PTR))->va_4byte.va_header = (((uint32) (len)) << 2)) 

當數據庫向物理文件進行寫入的時候,數據將會發生改變,計算公式如下:

else if (VARLENA_ATT_IS_PACKABLE(att[i]) && VARATT_CAN_MAKE_SHORT(val)) { /* convert to short varlena -- no alignment */ data_length = VARATT_CONVERTED_SHORT_SIZE(val); SET_VARSIZE_SHORT(data, data_length); memcpy(data + 1, VARDATA(val), data_length - 1); } 

注: 一個 numeric 類型的標度(scale)是小數部分的位數,精度(precision)是全部數據位的數目,也就是小數點兩邊的位數總和。因此數字 23.5141 的精度為 6 而標度為 4 。你可以認為整數的標度為零。

2、貨幣類型

數字類型中的money,也不能說它完全是數字類型,還能夠支持‘$1000.00’,這種格式。在C\C++和Java中都沒有對應的數字類型。他的范圍是-92233720368547758.08 to +92233720368547758.07,int8是它的100倍,它在物理文件存儲為:

postgres=# create table moneytable(m1 money);
CREATE TABLE
postgres=# insert into moneytable values ('$1') ; INSERT 0 1 postgres=# select * from moneytable ; m1 ------- $1.00 (1 row) postgres=# checkpoint ; CHECKPOINT postgres=# select pg_relation_filepath('moneytable'); pg_relation_filepath ---------------------- base/12814/16467 (1 row) postgres=# insert into moneytable values ('2') ; INSERT 0 1 postgres=# checkpoint ; CHECKPOINT postgres=# insert into moneytable values ('100') ; INSERT 0 1 postgres=# checkpoint ; CHECKPOINT [root@localhost 12814]# hexdump 16467 0000000 0000 0000 68e0 019e 0000 0000 0024 1fa0 0000010 2000 2004 0000 0000 9fe0 0040 9fc0 0040 0000020 9fa0 0040 0000 0000 0000 0000 0000 0000 0000030 0000 0000 0000 0000 0000 0000 0000 0000 * 0001fa0 06eb 0000 0000 0000 0000 0000 0000 0000 0001fb0 0003 0001 0800 0018 2710 0000 0000 0000 0001fc0 06ea 0000 0000 0000 0000 0000 0000 0000 0001fd0 0002 0001 0800 0018 00c8 0000 0000 0000 0001fe0 06e9 0000 0000 0000 0000 0000 0000 0000 0001ff0 0001 0001 0900 0018 0064 0000 0000 0000 0002000 

每個值都變為原來的100倍。 這也是為什么能表示兩位小數的原因。

3、字符類型

字符類型有:char、char(n)、bpchar、bpchar(n)、character(n) 、varchar、varchar(n)、character varying(n)、text、name、cstring。

(1)一般字符類型

char、char(n) 、character(n)、bpchar、bpchar(n), 這些(這些類型都是bpchar的馬甲)是同一種類型,使用的是同一個輸入輸出函數。

character(n) 、varchar、varchar(n)、character varying(n),這些(這些類型都是varchar的馬甲)是同一種類型,使用的是相同的輸入輸出函數。

text是一種非SQL標准類型,它和上邊除了char單字節外,用的都是相同的結構體:

typedef struct varlena bytea; typedef struct varlena text; typedef struct varlena BpChar; /* blank-padded char, ie SQL char(n) */ typedef struct varlena VarChar; /* var-length char, ie SQL varchar(n) */ 
struct varlena { char vl_len_[4]; /* Do not touch this field directly! */ char vl_dat[1]; }; 

這里還要說一個類型cstring,這個類型,在C中為char*。不能作為一個類型對字段進行定義。它和text的關系比較近。

在textin中是這么定義的:

Datum
textin(PG_FUNCTION_ARGS) { char *inputText = PG_GETARG_CSTRING(0); PG_RETURN_TEXT_P(cstring_to_text(inputText)); } 
text *
cstring_to_text(const char *s) { return cstring_to_text_with_len(s, strlen(s)); } 
text *
cstring_to_text_with_len(const char *s, int len) { text *result = (text *) palloc(len + VARHDRSZ); SET_VARSIZE(result, len + VARHDRSZ); memcpy(VARDATA(result), s, len); return result; } 

這里對text的處理只是在cstring基礎上加了一個長度而已。其他的類型處理還是比較多的。

這里bpchar對數據的存儲為當聲明長度的時候,輸入函數會對輸入的數據進行判斷,當長度大於聲明的長度時,數據庫會中斷請求,報錯。當小於時,函數會對數據進行填補空格,直到達到長度為止。
varchar的輸入函數不會對數據進行補白,但是當聲明長度時,超過時,同樣會報錯。
text不需要進行長度聲明,它的存儲幾乎沒有限制。

但是,這些存儲確實是有限制的:

if (*tl > MaxAttrSize)
    ereport(ERROR,
    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
        errmsg("length for type %s cannot exceed %d", typename, MaxAttrSize))); 
#define MaxAttrSize (10 * 1024 * 1024) 

這里的限制大小是10GB,但是還有一個數據庫本身對文件的限制:

Maximum size for a database? unlimited (32 TB databases exist) Maximum size for a table? 32 TB Maximum size for a row? 400 GB Maximum size for a field? 1 GB Maximum number of rows in a table? unlimited Maximum number of columns in a table? 250-1600 depending on column types Maximum number of indexes on a table? unlimited 

所以目前對字段最大存儲為1GB。

下面介紹一下在物理文件存儲的格式:

建立表test:

postgres=# create table test(t1 char, t2 char(10), t3 varchar, t4 varchar(10), t5 bpchar, t6 text);
CREATE TABLE
postgres=# checkpoint ; CHECKPOINT postgres=# select pg_relation_filepath('test'); pg_relation_filepath ---------------------- base/12892/16490 (1 row) 

插入數值:

postgres=# insert into test values ('a','a','a','a','a','a');
INSERT 0 1
postgres=# insert into test values ('b','b','b','b','b','b'); INSERT 0 1 postgres=# insert into test values ('a','aa','aa','aa','aa','aa'); INSERT 0 1 postgres=# insert into test values ('b','bb','bb','bb','bb','bb'); INSERT 0 1 postgres=# checkpoint ; CHECKPOINT postgres=# select * from test; t1 | t2 | t3 | t4 | t5 | t6 ----+------------+----+----+----+---- a | a | a | a | a | a b | b | b | b | b | b a | aa | aa | aa | aa | aa b | bb | bb | bb | bb | bb (4 rows) 

看一下物理文件:

[root@localhost 12892]# hexdump 16490 0000000 0000 0000 ab48 0189 0000 0000 0028 1f30 0000010 2000 2004 0000 0000 9fd0 005a 9fa0 005a 0000020 9f68 0062 9f30 0062 0000 0000 0000 0000 0000030 0000 0000 0000 0000 0000 0000 0000 0000 * 0001f30 06de 0000 0000 0000 0000 0000 0000 0000 0001f40 0004 0006 0802 0018 6205 6217 2062 2020 0001f50 2020 2020 0720 6262 6207 0762 6262 6207 0001f60 0062 0000 0000 0000 06dd 0000 0000 0000 0001f70 0000 0000 0000 0000 0003 0006 0802 0018 0001f80 6105 6117 2061 2020 2020 2020 0720 6161 0001f90 6107 0761 6161 6107 0061 0000 0000 0000 0001fa0 06dc 0000 0000 0000 0000 0000 0000 0000 0001fb0 0002 0006 0802 0018 6205 6217 2020 2020 0001fc0 2020 2020 0520 0562 0562 0562 0062 0000 0001fd0 06db 0000 0000 0000 0000 0000 0000 0000 0001fe0 0001 0006 0802 0018 6105 6117 2020 2020 0001ff0 2020 2020 0520 0561 0561 0561 0061 0000 0002000 
字段類型 文本內容 物理文件內容 文本內容 物理文件內容 文本內容 物理文件內容 文本內容 物理文件內容
char a
0x6105

 

b 0x6205 a 0x6105 b 0x626207
char(10) a 0x6117 2020 2020 2020 2020 20 b 0x6217 2020 2020 2020 2020 20 aa 0x6117 2062 2020 2020 2020 20 bb 0x6217 2062 2020 2020 2020 20
varchar a 0x6105 b 0x6205 aa 0x626207 bb 0x626207
varchar(10) a 0x6105 b 0x6205 aa 0x626207 bb 0x626207
bpchar a 0x6105 b 0x6205 aa 0x626207 bb 0x626207
text a 0x6105 b 0x6205 aa 0x626207 bb 0x626207

這里的數據都受到SET_VARSIZE_SHORT的影響,表示長度的位置標為1字節,然后進行計算。

還要說明的是,當數據達到一定長度時,數據庫會對數據進行壓縮,主要是采用的TOAST機制。采用了一種LZ壓縮算法,這是一種無損壓縮算法,該算法在函數toast_compress_datum 中進行了具體實現。簡單來說,LZ壓縮算法被認為是基於字符串匹配的算法。LZ算法壓縮算法的詳情,可以參閱相關文獻,這里就不多展開了。

(2)name

name:基礎類型, 在C\C++中沒有直接對應的類型,在源碼中是這樣定義的:

typedef struct nameData { char data[NAMEDATALEN]; } NameData; typedef NameData *Name; 

,在物理文件的存儲如下:

postgres=# create table nametable(n1 name);
CREATE TABLE
postgres=# insert into nametable values ('liu'); INSERT 0 1 postgres=# checkpoint ; CHECKPOINT postgres=# select pg_relation_filepath('nametable'); pg_relation_filepath ---------------------- base/12814/16461 (1 row) 
[root@localhost 12814]# hexdump 16461 0000000 0000 0000 5528 019b 0000 0000 001c 1fa8 0000010 2000 2004 0000 0000 9fa8 00b0 0000 0000 0000020 0000 0000 0000 0000 0000 0000 0000 0000 * 0001fa0 0000 0000 0000 0000 06de 0000 0000 0000 0001fb0 0000 0000 0000 0000 0001 0001 0800 0018 0001fc0 696c 0075 0000 0000 0000 0000 0000 0000 0001fd0 0000 0000 0000 0000 0000 0000 0000 0000 * 0002000 

liu = 6c 69 75(16進制)。

4、日期時間類型

這里列舉數據庫支持的日期類型的大概信息:

名字 存儲空間(單位:字節) 描述 最低值 最高值 Resolution
timestamp [ (p) ] [ without time zone ] 8 日期和時間 4713 BC 294276 AD 1 microsecond / 14 digits
timestamp [ (p) ] with time zone 8 日期和時間,帶時區 4713 BC 294276 AD 1 microsecond / 14 digits
date 4 只用於日期 4713 BC 5874897 AD 1 day
time [ (p) ] [ without time zone ] 8 只用於一日內時間 00:00:00 24:00:00 1 microsecond / 14 digits
time [ (p) ] with time zone 12 只用於一日內時間,帶時區 00:00:00+1459 24:00:00-1459 1 microsecond / 14 digits
interval [ fields ] [ (p) ] 12 時間間隔 -178000000 years 178000000 years 1 microsecond / 14 digits

(1)date

這里首先要說明的是date類型,它的定義其實很簡單:

typedef int32 DateADT;

PostgreSQL按照儒略日(Julian day,JD),即公元前4713年1月1日作為起始,具體的原因這里就不去探究了。

它其實是一個整型數字,之所以能夠表示 'yyyy-mm-dd'的原因主要是date類型的輸入輸出函數。它對輸入的字符,即格式為'yyyy-mm-dd'或'yyyy:mm:dd'或'yyyy.mm.dd'的字符串進行讀取,然后進行一系列的運算然后得到一個32bits的數字,存入到物理文件中。比如'2012-12-08'存入數據庫中為4725。

postgres=# create table datetest(d1 date);
CREATE TABLE
postgres=# insert into datetest values ('2012-12-8'); INSERT 0 1 postgres=# checkpoint ; CHECKPOINT postgres=# select pg_relation_filepath('datetest'); pg_relation_filepath ---------------------- base/12892/16499 (1 row) [root@localhost 12892]# hexdump 16499 0000000 0000 0000 5380 018c 0000 0000 001c 1fe0 0000010 2000 2004 0000 0000 9fe0 0038 0000 0000 0000020 0000 0000 0000 0000 0000 0000 0000 0000 * 0001fe0 06e4 0000 0000 0000 0000 0000 0000 0000 0001ff0 0001 0001 0800 0018 1275 0000 0000 0000 0002000 

0x1275即4725。

(2)time和time with time zone

這里的time和time with time zone,表示時間的部分和date類似都是整型。為了增加時區,這里有新的結構體TimeTzADT,它們的源碼為:

#ifdef HAVE_INT64_TIMESTAMP typedef int64 TimeADT; #else typedef float8 TimeADT; #endif typedef struct { TimeADT time; /* all time units other than months and years */ int32 zone; /* numeric time zone, in seconds */ } TimeTzADT; 

這里對事件的存儲,是按照秒數來計算的,並且由於能夠表示到小數點后6位,在此擴大了1000000倍。即,10:10:10.000001表示為數字36610000001。
還有對時區的存儲也是表示為秒數,比如正八區(+8:00:00)為-28800,即0xFFFF8F80。

postgres=# create table timeandtimetz(t1 time, t2 timetz);
CREATE TABLE
postgres=# insert into timeandtimetz values ('10:10:10.000001', '10:10:10.000001 +8:00:00'); the y is 2014 , the m is 4 , the d is 21 the century is 68 the julian1 is 2454943 the julian2 is 2456595 the julian3 is 2456769 the time_in time is 36610000001 the timetz_in time is 36610000001 the timetz_in tz is -28800 INSERT 0 1 postgres=# checkpoint ; CHECKPOINT postgres=# select pg_relation_filepath('timeandtimetz'); pg_relation_filepath ---------------------- base/12892/16508 (1 row) [root@localhost 12892]# hexdump 16508 0000000 0000 0000 1308 018f 0000 0000 001c 1fd0 0000010 2000 2004 0000 0000 9fd0 0058 0000 0000 0000020 0000 0000 0000 0000 0000 0000 0000 0000 * 0001fd0 06ec 0000 0000 0000 0000 0000 0000 0000 0001fe0 0001 0002 0800 0018 4481 8620 0008 0000 0001ff0 4481 8620 0008 0000 8f80 ffff 0000 0000 0002000 

(3)timestamp 和 timestamp with time zone

這兩個類型都包含了日期與時間,唯一不同的地方便是timestamp with time zone帶有時區,它們的定義為:

typedef int64 Timestamp;
typedef int64 TimestampTz; 

同樣是經過一系列的轉換,公式,將格式為'yyyy-mm-dd hh:mm:ss +/-hh:mm:ss',變為一個長整型。比如:'2013-1-1 20:00:00.000001',為410385600000001;'2013-1-1 20:00:00.000001 +8:00:00'為410356800000001。

postgres=# create table timesandtimestz(t1 timestamp(6), t2 timestamptz(6));
CREATE TABLE
postgres=# insert into timesandtimestz values ('2013-1-1 20:00:00.000001', '2013-1-1 20:00:00.000001 +8:00:00'); the y is 2013 , the m is 1 , the d is 1 the century is 68 the julian1 is 2454213 the julian2 is 2455865 the julian3 is 2456294 the y is 2013 , the m is 1 , the d is 1 the century is 68 the julian1 is 2454213 the julian2 is 2455865 the julian3 is 2456294 timestamp_in timestamp is 410385600000001 the y is 2013 , the m is 1 , the d is 1 the century is 68 the julian1 is 2454213 the julian2 is 2455865 the julian3 is 2456294 timestamptz_out timestamptz is 410356800000001 INSERT 0 1 postgres=# checkpoint ; CHECKPOINT postgres=# select pg_relation_filepath('timesandtimestz'); pg_relation_filepath ---------------------- base/12892/16528 (1 row) [root@localhost 12892]# hexdump 16528 0000000 0000 0000 0488 0196 0000 0000 001c 1fd8 0000010 2000 2004 0000 0000 9fd8 0050 0000 0000 0000020 0000 0000 0000 0000 0000 0000 0000 0000 * 0001fd0 0000 0000 0000 0000 06fc 0000 0000 0000 0001fe0 0000 0000 0000 0000 0001 0002 0800 0018 0001ff0 b001 57e8 753e 0001 9001 a34b 7537 0001 0002000 

(4)interval

interval,時間間隔類型,這個反而是所有時間類型當中最復雜的數據類型。

typedef struct { TimeOffset time; /* all time units other than days, months and * years */ int32 day; /* days, after time for alignment */ int32 month; /* months and years, after time for alignment */ } Interval; typedef int64 TimeOffset; 

這里只是一個混合的結構體。

注:這里的時間類型格式還有其他形式,我這就不一一列舉了,大體過程類似,都是將日期變為數字,進行存儲。

5、對象標識符類型

oid:基礎類型,占位4字節。下面是Oid的定義:

typedef unsigned int Oid; 

完全按照ascii碼表示的。

postgres=# create table oidt(o1 oid);
CREATE TABLE
postgres=# insert into oidt values (1); INSERT 0 1 postgres=# insert into oidt values (2); INSERT 0 1 postgres=# insert into oidt values (1); INSERT 0 1 postgres=# checkpoint ; CHECKPOINT postgres=# select pg_relation_filepath('oidt'); pg_relation_filepath ---------------------- base/12892/16522 (1 row) [root@localhost 12892]# hexdump 16522 0000000 0000 0000 cf08 0193 0000 0000 0024 1fa0 0000010 2000 2004 0000 0000 9fe0 0038 9fc0 0038 0000020 9fa0 0038 0000 0000 0000 0000 0000 0000 0000030 0000 0000 0000 0000 0000 0000 0000 0000 * 0001fa0 06f8 0000 0000 0000 0000 0000 0000 0000 0001fb0 0003 0001 0800 0018 0001 0000 0000 0000 0001fc0 06f7 0000 0000 0000 0000 0000 0000 0000 0001fd0 0002 0001 0800 0018 0002 0000 0000 0000 0001fe0 06f6 0000 0000 0000 0000 0000 0000 0000 0001ff0 0001 0001 0800 0018 0001 0000 0000 0000 0002000 

6、布爾型

bool:基礎類型,占位1字節。以0、1來表示false, true。

postgres=# create table boolt(b1 bool);
CREATE TABLE
postgres=# insert into boolt values ('t'),('f'); INSERT 0 2 postgres=# checkpoint ; CHECKPOINT postgres=# select pg_relation_filepath('boolt'); pg_relation_filepath ---------------------- base/12892/16525 (1 row) [root@localhost 12892]# hexdump 16522 0000000 0000 0000 cf08 0193 0000 0000 0024 1fa0 0000010 2000 2004 0000 0000 9fe0 0038 9fc0 0038 0000020 9fa0 0038 0000 0000 0000 0000 0000 0000 0000030 0000 0000 0000 0000 0000 0000 0000 0000 * 0001fa0 06f8 0000 0000 0000 0000 0000 0000 0000 0001fb0 0003 0001 0800 0018 0001 0000 0000 0000 0001fc0 06f7 0000 0000 0000 0000 0000 0000 0000 0001fd0 0002 0001 0800 0018 0002 0000 0000 0000 0001fe0 06f6 0000 0000 0000 0000 0000 0000 0000 0001ff0 0001 0001 0800 0018 0001 0000 0000 0000 0002000 

7、二進制類型

bytea,二進制類型,和text等用的相同的結構體,同樣受到數據庫的限制。

typedef struct varlena bytea; 
postgres=# create table byteat(b1 bytea);
CREATE TABLE
postgres=# insert into byteat values ('ab'); INSERT 0 1 postgres=# checkpoint ; CHECKPOINT postgres=# select pg_relation_filepath('byteat'); pg_relation_filepath ---------------------- base/12892/16516 (1 row) postgres=# insert into byteat values ('abcde'); the data_length is 6 INSERT 0 1 postgres=# checkpoint ; CHECKPOINT [root@localhost 12892]# hexdump 16516 0000000 0000 0000 b558 0192 0000 0000 0020 1fc0 0000010 2000 2004 0000 0000 9fe0 0036 9fc0 003c 0000020 0000 0000 0000 0000 0000 0000 0000 0000 * 0001fc0 06f4 0000 0000 0000 0000 0000 0000 0000 0001fd0 0002 0001 0802 0018 610d 6362 6564 0000 0001fe0 06f3 0000 0000 0000 0000 0000 0000 0000 0001ff0 0001 0001 0802 0018 6107 0062 0000 0000 0002000 

三、總結

在這里只是介紹了比較簡單的幾種數據類型,其中的用法上寫的都比較簡單,但是基本上都是如此。若有什么不對的地方請及時和我交流,希望和大家共同學習。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM