前言
在Opengauss/磐维里,存在可变长的数据类型,那么这种类型在数据库存储中,到底是以什么形式存储的,是如何存储的,这篇文章带你了解深处的秘密。
varlena解析
首先,varlena有一个基础的通用结构:
struct varlena
{
char vl_len_[4];
char vl_dat[FLEXIBLE_ARRAY_MEMBER];
};
typedef struct varlena bytea;
typedef struct varlena byteawithoutorderwithequalcol;
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) */
typedef struct varlena NVarChar2; /* var-length char, ie SQL nvarchar2(n) */
由上面定义可见,bytea、text、bpchar、varchar、NVarChar2这些类型,都是基于varlena的。
具体的存储中,varlena分为几种细分类型:
- varattrib_1b
- varattrib_4b
- varattrib_1b_e
varattrib_1b
先看一下varattrib_1b的定义:
typedef struct {
uint8 va_header;
char va_data[FLEXIBLE_ARRAY_MEMBER]; /* Data begins here */
} varattrib_1b;
有一个1字节长度的va_header,它里面记录了标志位和整个字符的长度,具体结构为:
| tag | length |
|---|---|
| 1bit | 7bit |
这种类型可以存储不超过127字节长度的数据。如果超过127字节,那么会用下面这种形式存储。
varattrib_4b
看一下varattrib_4b的定义:
typedef union
{
struct /* Normal varlena (4-byte length) */
{
uint32 va_header;
char va_data[FLEXIBLE_ARRAY_MEMBER];
} va_4byte;
struct /* Compressed-in-line format */
{
uint32 va_header;
uint32 va_tcinfo; /* Original data size (excludes header) and
* compression method; see va_extinfo */
char va_data[FLEXIBLE_ARRAY_MEMBER]; /* Compressed data */
} va_compressed;
} varattrib_4b;
这是一个union,里面包含了两种类型:
- va_4byte:未压缩的varattrib_4b类型
- va_compressed:压缩类型的varattrib_4b
va_4byte
va_header是一个4字节。其中,tag是标志位,compress标记是否是压缩类型,length是整个字符的长度。这种类型可以存储不超过1G的数据。
| tag | compress | length |
|---|---|---|
| 1 bit | 1 bit | 30 bit |
va_compressed
和va_4byte类似,只不过compress标志位会标志该列数据为压缩类型。pg内部使用列pglz算法来做压缩的,后续有机会解析一下pglz_decompress部分的代码。
varattrib_1b_e
这种类型并不会把具体的数据存储在当前page中,他是一个指针,指向到了外部的数据地址
首先来看一下结构体定义:
typedef struct
{
uint8 va_header; /* Always 0x80 or 0x01 */
uint8 va_tag; /* Type of datum */
char va_data[FLEXIBLE_ARRAY_MEMBER]; /* Type-specific data */
} varattrib_1b_e;
同样的,结构体里定义了1个字节的va_header,1个字节的va_tag。
这里的va_tag很重要,标识了到底是哪种类型的external,来看下定义:
typedef enum vartag_external
{
VARTAG_INDIRECT = 1,
VARTAG_EXPANDED_RO = 2,
VARTAG_EXPANDED_RW = 3,
VARTAG_ONDISK = 18
} vartag_external;
可以看到,1B_E类型可以分为4种小类,下面来逐一介绍:
VARTAG_ONDISK
这种类型会把数据存储在磁盘中,表现为一个toast表,指向了具体的oid
typedef struct varatt_external
{
int32 va_rawsize; /* Original data size (includes header) */
uint32 va_extinfo; /* External saved size (without header) and
* compression method */
Oid va_valueid; /* Unique ID of value within TOAST table */
Oid va_toastrelid; /* RelID of TOAST table containing it */
} varatt_external;
从结构体中也可以清晰看到,va_rawsize代表了原始数据大小,包含了header。va_extinfo代表外部存储的数据大小,不包含header的。va_valueid记录了toast表的table oid号。va_toastrelid代表了数据文件的table real oid,可以通过它来找到具体的数据文件来解析。
varatt_expanded
这种类型会把数据存储在内存当中,对应的是VARTAG_EXPANDED_RO、VARTAG_EXPANDED_RW类型,从名字也可以看出,一个是只读一个是可读写的。
指针类型是ExpandedObjectHeader,也可以清晰看到这个结构体的内容,基本还是记录了va_header,但是这个值会始终是(-1),还记录了上下文信息,还有指针的信息等
#define EOH_HEADER_MAGIC (-1)
typedef struct ExpandedObjectHeader ExpandedObjectHeader;
struct ExpandedObjectHeader
{
/* Phony varlena header */
int32 vl_len_; /* always EOH_HEADER_MAGIC, see below */
/* Pointer to methods required for object type */
const ExpandedObjectMethods *eoh_methods;
/* Memory context containing this header and subsidiary data */
MemoryContext eoh_context;
/* Standard R/W TOAST pointer for this object is kept here */
char eoh_rw_ptr[EXPANDED_POINTER_SIZE];
/* Standard R/O TOAST pointer for this object is kept here */
char eoh_ro_ptr[EXPANDED_POINTER_SIZE];
};
typedef struct varatt_expanded
{
ExpandedObjectHeader *eohptr;
} varatt_expanded;
vartag_indirect
最后一种类型,这种类型比较特殊,先看下定义:
typedef struct varatt_indirect
{
struct varlena *pointer; /* Pointer to in-memory varlena */
} varatt_indirect;
可以看到,指针类型的本质是一个struct varlena结构体,这又回到了最上面的那种基础类型了。也就是说,这种类型可以指向varatt_external,varatt_expanded,或varattrib_1b,varattrib_4b 类型




