前期,我参与了公司开发的数据库数据迁移工具的工做,以及以前的对Page的分析记录,在此进一步将数据库的数据类型作一下分析记录。 html
PostgreSQL的全部数据类型都存储在系统表pg_type中。
pg_type的表结构以下(这里是从源码中进行介绍的,源码能够点击pg_type.h): node
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的各个字段含义。
typname、typnamespace、typowner 这三个字段名字上就能够看出来他们的含义。
typlen:这是标明类型的长度的,若是类型是定长的就是写明字段的长度(字节)。若是是变长的则是-1。好比int4也就是int或者integer,typlen为4,占用4个字节,varchar则为-1。
typbyval:判断内部过程传递这个类型的数值时是经过传值仍是传引用。若是该类型不是 1, 2, 4, 8 字节长将只能按应用传递,所以 typbyval 最好是假。 即便能够传值,typbyval 也能够为假。好比float4就是如此。
typtype:对于基础类型是b, 对于复合类型是 c (好比,一个表的行类型)。对于域类型是d,对于伪类型是p.
本博文也是主要分析基础类型。
typcategory:这是对数据类型进行分类的,int二、int四、int8的typcategory都是N。typcategory的分类详看下表: git
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 |
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的介绍。下面主要针对每个基础数据类型分析。 算法
首先是整数类型int二、int4(等价integer)、int8。
为了方便说明,用下表来讲明一下: sql
PostgreSQL类型名 shell |
占位(字节) | 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。 app
float四、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 000200012345变为了 e400 4640(float4),12345变为了 1c80 40c8。
如今简单介绍一下float,它的存储方式为:
共计32位,折合4字节。由最高到最低位分别是第 3一、30、2九、……、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,它的存储方式为:
指数位与尾数部分都要比float增长了长度,因此计算方法仍是同上,只不过如今的指数位要加的是1023,尾数部分自动补更多的零。
注:PostgreSQL 还支持 SQL 标准表示法 float 和 float(p) 用于声明非精确的数值类型。其中的 p 声明以二进制位表示的最低可接受精度。在选取 real 类型的时候,PostgreSQL 接受 float(1) 到 float(24),在选取 double precision 的时候,接受 float(25) 到 float(53) 。在容许范围以外的 p 值将致使一个错误。没有声明精度的 float 将被看成 double precision 。
数字类型还有一种即是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。
再好比带有小数的数字例如,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 。你能够认为整数的标度为零。
数字类型中的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倍。 这也是为何能表示两位小数的缘由。
字符类型有:char、char(n)、bpchar、bpchar(n)、character(n) 、varchar、varchar(n)、character varying(n)、text、name、cstring。
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算法压缩算法的详情,能够参阅相关文献,这里就很少展开了。
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进制)。
这里列举数据库支持的日期类型的大概信息:
名字 | 存储空间(单位:字节) | 描述 | 最低值 | 最高值 | 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 |
这里首先要说明的是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 00020000x1275即4725。
这里的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。
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
这两个类型都包含了日期与时间,惟一不一样的地方即是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
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;这里只是一个混合的结构体。
注:这里的时间类型格式还有其余形式,我这就不一一列举了,大致过程相似,都是将日期变为数字,进行存储。
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
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
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
在这里只是介绍了比较简单的几种数据类型,其中的用法上写的都比较简单,可是基本上都是如此。如有什么不对的地方请及时和我交流,但愿和你们共同窗习。