声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》一书以及OpenGauss社区学习文档和一些学习资料
在【OpenGauss源码学习 —— 列存储(CU)(一)】中我们初步认识了 CU 的结构和作用,以及在【 OpenGauss源码学习 —— 列存储(CU)(二)】中学习了CU的压缩和解压方法。最后通过【 OpenGauss源码学习 —— 列存储(CU)(三)】学习了 CU 的加密和解密操作。
CU(Column Unit)是用于管理列存储数据库中的列片段数据的类,每个 CU 代表一个列的数据片段,包括数据值、空值位图等信息。CU 类允许对列数据进行压缩、解压、访问和操作,以提高列存储系统的性能和存储效率。它为列级别的数据处理提供了精细的控制。
然而,对于一个完整的列存储数据库系统,需要更高级别的管理和操作,例如表级别的数据文件的创建、删除、截断,以及对整个列存储表的元数据的维护。这就是CStore 类发挥作用的地方。CStore 类提供了这些高级别的操作,用于管理整个列存储表,包括多个 CU 的集合。通过 CStore 类,可以执行诸如创建和删除列存储表、处理数据文件、维护表级元数据等任务,实现了对整个列存储系统的控制。
综上所述,CStore 和 CU 的关系可以被总结为以下三点:
1. CStore 类用于管理列存储表的整体操作,而 CU 类用于管理具体的列片段数据。
2. 在列存储系统中,一个表通常会被拆分为多个列片段(CU),每个列片段存储一个列的数据。
3. CStore 可能会使用 CU 类来处理特定列的数据,例如在保存或加载数据时,可能需要操作多个 CU 对象。
因此,本文将围绕 CStore 类展开学习。
CStore 类用于管理列存储数据库表的操作和元数据。它提供了一组方法,包括数据文件的创建、删除、截断,以及对整个列存储表的元数据信息的维护。该类用于实现表级别的管理,使得在列存储数据库系统中更容易管理和操作表的数据,从而提高了数据存储和查询性能。
CStore 类的源码如下所示:(路径:src/include/access/cstore_am.h
)
/*
* CStore include a set of common API for ColStore.
* In future, we can add more API.
*/
class CStore : public BaseObject {
// public static area
public:
// create data files
static void CreateStorage(Relation rel, Oid newRelFileNode = InvalidOid);
// unlink data files
static void UnlinkColDataFile(const RelFileNode &rnode, AttrNumber attrnum, bool bcmIncluded);
static void InvalidRelSpaceCache(RelFileNode *rnode);
// trunccate data files which relation CREATE and TRUNCATE in same XACT block
static void TruncateStorageInSameXact(Relation rel);
// form and deform CU Desc tuple
static HeapTuple FormCudescTuple(_in_ CUDesc *pCudesc, _in_ TupleDesc pCudescTupDesc,
_in_ Datum values[CUDescMaxAttrNum], _in_ bool nulls[CUDescMaxAttrNum],
_in_ Form_pg_attribute pColAttr);
static void DeformCudescTuple(_in_ HeapTuple pCudescTup, _in_ TupleDesc pCudescTupDesc,
_in_ Form_pg_attribute pColAttr, _out_ CUDesc *pCudesc);
// Save CU description information into CUDesc table
static void SaveCUDesc(_in_ Relation rel, _in_ CUDesc *cuDescPtr, _in_ int col, _in_ int options);
// form and deform VC CU Desc tuple.
// We add a virtual column for marking deleted rows.
// The VC is divided into CUs.
static HeapTuple FormVCCUDescTup(_in_ TupleDesc cudesc, _in_ const char *delMask, _in_ uint32 cuId,
_in_ int32 rowCount, _in_ uint32 magic);
static void SaveVCCUDesc(_in_ Oid cudescOid, _in_ uint32 cuId, _in_ int rowCount, _in_ uint32 magic,
_in_ int options, _in_ const char *delBitmap = NULL);
static bool IsTheWholeCuDeleted(_in_ char *delBitmap, _in_ int rowsInCu);
bool IsTheWholeCuDeleted(_in_ int rowsInCu);
// get Min/Max value from given *pCudesc*.
// *min* = true, return the Min value.
// *min* = false, return the Max value.
static Datum CudescTupGetMinMaxDatum(_in_ CUDesc *pCudesc, _in_ Form_pg_attribute pColAttr, _in_ bool min,
_out_ bool *shouldFree);
static bool SetCudescModeForMinMaxVal(_in_ bool fullNulls, _in_ bool hasMinMaxFunc, _in_ bool hasNull,
_in_ int maxVarStrLen, _in_ int attlen, __inout CUDesc *cuDescPtr);
static bool SetCudescModeForTheSameVal(_in_ bool fullNulls, _in_ FuncSetMinMax SetMinMaxFunc, _in_ int attlen,
_in_ Datum attVal, __inout CUDesc *cuDescPtr);
static uint32 GetMaxCUID(_in_ Oid cudescHeap, _in_ TupleDesc cstoreRelTupDesc, _in_ Snapshot snapshotArg = NULL);
static uint32 GetMaxIndexCUID(_in_ Relation heapRel, _in_ List *btreeIndex);
static CUPointer GetMaxCUPointerFromDesc(_in_ int attrno, _in_ Oid cudescHeap);
static CUPointer GetMaxCUPointer(_in_ int attrno, _in_ Relation rel);
public:
CStore();
virtual ~CStore();
virtual void Destroy();
// Scan APIs
void InitScan(CStoreScanState *state, Snapshot snapshot = NULL);
void InitReScan();
void InitPartReScan(Relation rel);
bool IsEndScan() const;
// late read APIs
bool IsLateRead(int id) const;
void ResetLateRead();
// update cstore scan timing flag
void SetTiming(CStoreScanState *state);
// CStore scan : pass vector to VE.
void ScanByTids(_in_ CStoreIndexScanState *state, _in_ VectorBatch *idxOut, _out_ VectorBatch *vbout);
void CStoreScanWithCU(_in_ CStoreScanState *state, BatchCUData *tmpCUData, _in_ bool isVerify = false);
// Load CUDesc information of column according to loadInfoPtr
// LoadCUDescCtrl include maxCUDescNum for this load, because if we load all
// it need big memory to hold
//
bool LoadCUDesc(_in_ int col, __inout LoadCUDescCtl *loadInfoPtr, _in_ bool prefetch_control,
_in_ Snapshot snapShot = NULL);
// Get CU description information from CUDesc table
bool GetCUDesc(_in_ int col, _in_ uint32 cuid, _out_ CUDesc *cuDescPtr, _in_ Snapshot snapShot = NULL);
// Get tuple deleted information from VC CU description.
void GetCUDeleteMaskIfNeed(_in_ uint32 cuid, _in_ Snapshot snapShot);
bool GetCURowCount(_in_ int col, __inout LoadCUDescCtl *loadCUDescInfoPtr, _in_ Snapshot snapShot);
// Get live row numbers.
int64 GetLivedRowNumbers(int64 *deadrows);
// Get CU data.
// Note that the CU is pinned
CU *GetCUData(_in_ CUDesc *cuDescPtr, _in_ int colIdx, _in_ int valSize, _out_ int &slotId);
CU *GetUnCompressCUData(Relation rel, int col, uint32 cuid, _out_ int &slotId, ForkNumber forkNum = MAIN_FORKNUM,
bool enterCache = true) const;
// Fill Vector APIs
int FillVecBatch(_out_ VectorBatch *vecBatchOut);
// Fill Vector of column
template <bool hasDeadRow, int attlen>
int FillVector(_in_ int colIdx, _in_ CUDesc *cu_desc_ptr, _out_ ScalarVector *vec);
template <int attlen>
void FillVectorByTids(_in_ int colIdx, _in_ ScalarVector *tids, _out_ ScalarVector *vec);
template <int attlen>
void FillVectorLateRead(_in_ int seq, _in_ ScalarVector *tids, _in_ CUDesc *cuDescPtr, _out_ ScalarVector *vec);
void FillVectorByIndex(_in_ int colIdx, _in_ ScalarVector *tids, _in_ ScalarVector *srcVec,
_out_ ScalarVector *destVec);
// Fill system column into ScalarVector
int FillSysColVector(_in_ int colIdx, _in_ CUDesc *cu_desc_ptr, _out_ ScalarVector *vec);
template <int sysColOid>
void FillSysVecByTid(_in_ ScalarVector *tids, _out_ ScalarVector *destVec);
template <bool hasDeadRow>
int FillTidForLateRead(_in_ CUDesc *cuDescPtr, _out_ ScalarVector *vec);
void FillScanBatchLateIfNeed(__inout VectorBatch *vecBatch);
/* Set CU range for scan in redistribute. */
void SetScanRange();
// Judge whether dead row
bool IsDeadRow(uint32 cuid, uint32 row) const;
void CUListPrefetch();
void CUPrefetch(CUDesc *cudesc, int col, AioDispatchCUDesc_t **dList, int &count, File *vfdList);
/* Point to scan function */
typedef void (CStore::*ScanFuncPtr)(_in_ CStoreScanState *state, _out_ VectorBatch *vecBatchOut);
void RunScan(_in_ CStoreScanState *state, _out_ VectorBatch *vecBatchOut);
int GetLateReadCtid() const;
void IncLoadCuDescCursor();
public: // public vars
// Inserted/Scan Relation
Relation m_relation;
private: // private methods.
// CStore scan : pass vector to VE.
void CStoreScan(CStoreScanState *state, VectorBatch *vecBatchOut);
void CStoreMinMaxScan(CStoreScanState *state, VectorBatch *vecBatchOut);
// The number of holding CUDesc is max_loaded_cudesc
// if we load all CUDesc once, the memory will not enough.
// So we load CUdesc once for max_loaded_cudesc
void LoadCUDescIfNeed();
// Do RoughCheck if need
// elimiate CU by min/max value of CU.
void RoughCheckIfNeed(_in_ CStoreScanState *state);
// Refresh cursor
void RefreshCursor(int row, int deadRows);
void InitRoughCheckEnv(CStoreScanState *state);
void BindingFp(CStoreScanState *state);
void InitFillVecEnv(CStoreScanState *state);
// indicate whether only accessing system column or const column.
// true, means that m_virtualCUDescInfo is a new and single object.
// false, means that m_virtualCUDescInfo just a pointer to m_CUDescInfo[0].
//
inline bool OnlySysOrConstCol(void)
{
return ((m_colNum == 0 && m_sysColNum != 0) || m_onlyConstCol);
}
int LoadCudescMinus(int start, int end) const;
bool HasEnoughCuDescSlot(int start, int end) const;
bool NeedLoadCUDesc(int32 &cudesc_idx);
void IncLoadCuDescIdx(int &idx) const;
bool RoughCheck(CStoreScanKey scanKey, int nkeys, int cuDescIdx);
void FillColMinMax(CUDesc *cuDescPtr, ScalarVector *vec, int pos);
inline TransactionId GetCUXmin(uint32 cuid);
// only called by GetCUData()
CUUncompressedRetCode GetCUDataFromRemote(CUDesc *cuDescPtr, CU *cuPtr, int colIdx, int valSize, const int &slotId);
/* defence functions */
void CheckConsistenceOfCUDescCtl(void);
void CheckConsistenceOfCUDesc(int cudescIdx) const;
void CheckConsistenceOfCUData(CUDesc *cuDescPtr, CU *cu, AttrNumber col) const;
private:
// control private memory used locally.
// m_scanMemContext: for objects alive during the whole cstore-scan
// m_perScanMemCnxt: for memory per heap table scan and temp space
// during decompression.
MemoryContext m_scanMemContext;
MemoryContext m_perScanMemCnxt;
// current snapshot to use.
Snapshot m_snapshot;
// 1. Accessed user column id
// 2. Accessed system column id
// 3. flags for late read
// 4. each CU storage fro each user column.
int *m_colId;
int *m_sysColId;
bool *m_lateRead;
CUStorage **m_cuStorage;
// 1. The CUDesc info of accessed columns
// 2. virtual CUDesc for sys or const columns
LoadCUDescCtl **m_CUDescInfo;
LoadCUDescCtl *m_virtualCUDescInfo;
// Accessed CUDesc index array
// After RoughCheck, which CU will be accessed
//
int *m_CUDescIdx;
// adio param
int m_lastNumCUDescIdx;
int m_prefetch_quantity;
int m_prefetch_threshold;
bool m_load_finish;
// Current scan position inside CU
//
int *m_scanPosInCU;
// Rough Check Functions
//
RoughCheckFunc *m_RCFuncs;
typedef int (CStore::*m_colFillFun)(int seq, CUDesc *cuDescPtr, ScalarVector *vec);
typedef struct {
m_colFillFun colFillFun[2];
} colFillArray;
typedef void (CStore::*FillVectorByTidsFun)(_in_ int colIdx, _in_ ScalarVector *tids, _out_ ScalarVector *vec);
typedef void (CStore::*FillVectorLateReadFun)(_in_ int seq, _in_ ScalarVector *tids, _in_ CUDesc *cuDescPtr,
_out_ ScalarVector *vec);
FillVectorByTidsFun *m_fillVectorByTids;
FillVectorLateReadFun *m_fillVectorLateRead;
colFillArray *m_colFillFunArrary;
typedef void (CStore::*fillMinMaxFuncPtr)(CUDesc *cuDescPtr, ScalarVector *vec, int pos);
fillMinMaxFuncPtr *m_fillMinMaxFunc;
ScanFuncPtr m_scanFunc; // cstore scan function ptr
// node id of this plan
int m_plan_node_id;
// 1. Number of accessed user columns
// 2. Number of accessed system columns.
int m_colNum;
int m_sysColNum;
// 1. length of loaded CUDesc info or virtual CUDesc info
// 2. length of m_CUDescIdx
int m_NumLoadCUDesc;
int m_NumCUDescIdx;
// 1. CU id of current deleted mask.
// 2. Current access cursor in m_CUDescIdx
// 3. Current access row cursor inside CU
uint32 m_delMaskCUId;
int m_cursor;
int m_rowCursorInCU;
uint32 m_startCUID; /* scan start CU ID. */
uint32 m_endCUID; /* scan end CU ID. */
unsigned char m_cuDelMask[MaxDelBitmapSize];
// whether dead rows exist
bool m_hasDeadRow;
// Is need do rough check
bool m_needRCheck;
// Only access const column
bool m_onlyConstCol;
bool m_timing_on; /* timing CStoreScan steps */
RangeScanInRedis m_rangeScanInRedis; /* if it is a range scan at redistribution time */
// cbtree index flag
bool m_useBtreeIndex;
// the first column index, start from 0
int m_firstColIdx;
// for late read
// the cuDesc id of batch in the cuDescArray.
int m_cuDescIdx;
// for late read
// the first late read column idx which is filled with ctid.
int m_laterReadCtidColIdx;
};
为了能够查阅起来更加直观,我通过以下表格汇总了 CStore 类中包含的成员函数极其作用。
函 数 | 作 用 |
---|---|
CreateStorage | 创建数据文件,通常用于创建与给定关系(表)相关的列存储数据文件。 |
UnlinkColDataFile | 取消链接(删除)与给定关系、属性号和是否包括 BCM(块压缩信息)相关的数据文件。 |
InvalidRelSpaceCache | 使与给定文件节点(RelFileNode)相关的关系空间缓存无效,通常在数据文件操作后使用。 |
TruncateStorageInSameXact | 在同一事务块中,对于在创建和截断关系数据文件的情况,截断数据文件以释放空间。 |
FormCudescTuple | 用于创建和解析 CU 描述元组(Tuple)的方法,CU 描述包含列片段的元数据信息。 |
SaveCUDesc | 将 CU 描述信息保存到 CU 描述表中,以维护列片段的元数据。 |
FormVCCUDescTup | 用于创建和保存虚拟列 CU 描述元组,通常用于标记已删除的行。 |
IsTheWholeCuDeleted | 用于检查列片段是否完全被删除,通常通过检查删除位图来确定。 |
CudescTupGetMinMaxDatum | 从 CU 描述中获取最小或最大值的数据,用于统计和查询操作。 |
SetCudescModeForMinMaxVal | 用于设置 CU 描述的模式,以表示最小和最大值的值。 |
SetCudescModeForTheSameVal | 用于设置 CU 描述的模式,以表示相同的值。 |
GetMaxCUID | 用于获取最大的 CU ID,通常用于管理 CU 描述表。 |
GetMaxCUPointerFromDesc | 用于获取最大的 CU 指针,通常用于查询和数据操作。 |
InitScan | 初始化列存储扫描状态,通常与快照(Snapshot)相关联。 |
InitReScan | 初始化重新扫描列存储数据的状态。 |
InitPartReScan | 初始化部分重新扫描列存储数据的状态,通常与关系(Relation)相关联。 |
IsEndScan | 判断列存储扫描是否已结束。 |
IsLateRead 和 ResetLateRead | 用于支持延迟读取(late read)操作,以及重置延迟读取状态。 |
SetTiming | 更新列存储扫描的时间标志。 |
ScanByTids 和 CStoreScanWithCU | 用于执行列存储数据扫描操作,可能包括基于索引的扫描和 CU 数据的获取。 |
LoadCUDesc | 加载列的 CU 描述信息,通常用于元数据的维护。 |
GetCUDesc | 获取指定列的 CU 描述信息,通常用于查询操作。 |
GetCUDeleteMaskIfNeed | 获取 CU 的删除掩码信息,通常用于标记已删除的行。 |
GetCURowCount | 获取 CU 的行数信息,通常用于查询操作。 |
GetLivedRowNumbers | 获取活跃行数,通常用于统计操作。 |
GetCUData | 获取 CU 数据,通常用于填充向量或数据操作。 |
FillVecBatch | 填充向量批次数据。 |
FillVector 和 FillVectorByTids | 填充列向量,通常用于查询操作。 |
FillVectorLateRead | 在延迟读取情况下填充列向量。 |
FillVectorByIndex | 通过索引填充列向量。 |
FillSysColVector 和 FillSysVecByTid | 填充系统列向量,通常用于系统列的查询操作。 |
FillTidForLateRead 和 FillScanBatchLateIfNeed | 用于支持延迟读取操作。 |
SetScanRange | 设置扫描范围,通常用于数据重分布。 |
CUListPrefetch 和 CUPrefetch | 预取 CU 数据,以提高性能。 |
RunScan | 运行列存储扫描操作。 |
GetLateReadCtid 和 IncLoadCuDescCursor | 获取延迟读取的 CTID(行标识符)和增加加载 CU 描述游标。 |
CStoreScan 和 CStoreMinMaxScan | 用于执行列存储数据扫描操作,包括通常扫描和最小/最大值扫描。 |
LoadCUDescIfNeed | 加载 CU 描述信息,如果需要的话,通常用于元数据的管理。 |
RoughCheckIfNeed | 执行粗略检查,根据 CU 的最小/最大值来排除不必要的 CU,以提高查询性能。 |
RefreshCursor | 刷新扫描游标,通常在扫描时使用。 |
InitRoughCheckEnv | 初始化粗略检查的环境,以准备执行粗略检查。 |
BindingFp 和 InitFillVecEnv | 绑定扫描状态,通常在扫描时使用。 |
OnlySysOrConstCol | 用于指示是否仅访问系统列或常量列,通常与 CU 描述相关。 |
LoadCudescMinus | 加载 CU 描述信息,通常用于缓存。 |
HasEnoughCuDescSlot | 检查是否有足够的 CU 描述槽,通常用于缓存。 |
NeedLoadCUDesc 和 IncLoadCuDescIdx | 确定是否需要加载 CU 描述信息以及增加加载 CU 描述的索引。 |
RoughCheck | 执行粗略检查,以确定是否需要访问指定 CU,通常用于查询操作。 |
FillColMinMax | 填充列的最小和最大值,通常用于统计操作。 |
GetCUXmin | 获取 CU 的 Xmin 值,通常用于元数据的查询。 |
GetCUDataFromRemote | 从远程获取 CU 数据,通常用于跨节点数据访问。 |
CheckConsistenceOfCUDescCtl 和 CheckConsistenceOfCUDesc | 用于检查 CU 描述控制和 CU 描述的一致性,通常用于数据完整性检查。 |
以上表格中列举的这些函数是用于执行列存储数据库系统中的扫描、元数据加载、数据填充、性能优化和数据完整性检查等操作的关键功能,支持列存储数据的高效查询和操作。由于这里成员函数数量较多,在后续的学习中可能不能一一介绍,感兴趣的小伙伴可以自行阅读源码。
这是 CStore 类的 CreateStorage 函数,用于在列存储关系(Relation)中为每个属性创建数据文件。函数首先获取关系的描述信息,然后遍历关系的每个属性,跳过已经标记为被删除的属性。对于每个属性,它创建一个新的 CUStorage 对象,该对象用于管理列存储数据的存储,然后调用 CreateStorage 函数来创建实际的数据文件。最后,它记录日志并将相关信息插入到待删除列表中。
该函数用于在初始化列存储关系时,为每个属性创建相应的数据文件,以准备存储列存储数据。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp
)
/* DONT call in redo */
// 不要在重做(redo)操作中调用该函数
void CStore::CreateStorage(Relation rel, Oid newRelFileNode)
{
// 获取关系的描述符(描述关系的结构信息)
TupleDesc desc = RelationGetDescr(rel);
// 获取属性的数量
int nattrs = desc->natts;
// 获取属性描述符的数组
Form_pg_attribute* attrs = desc->attrs;
// 获取关系的持久性
char relpersistence = rel->rd_rel->relpersistence;
// 获取关系的节点信息
RelFileNode rd_node = rel->rd_node;
// 如果提供了有效的新文件节点,使用新的文件节点
if (OidIsValid(newRelFileNode)) {
rd_node.relNode = newRelFileNode;
}
// 遍历每个属性
for (int i = 0; i < nattrs; i++) {
// 如果属性标记为已删除,跳过
if (attrs[i]->attisdropped)
continue;
// 获取属性编号
int attrid = attrs[i]->attnum;
// 创建列存储文件节点对象
CFileNode cnode(rd_node, attrid, MAIN_FORKNUM);
// 创建CUStorage对象用于管理列存储数据的存储
CUStorage* custorage = New(CurrentMemoryContext) CUStorage(cnode);
// 断言确保CUStorage对象成功创建
Assert(custorage);
// 创建实际的数据文件
custorage->CreateStorage(0, false);
// 删除CUStorage对象
DELETE_EX(custorage);
// 记录日志并将相关信息插入到待删除列表中
CStoreRelCreateStorage(&rd_node, attrid, relpersistence, rel->rd_rel->relowner);
}
}
在这段代码中,提到了通过创建一个 CUStorage 对象用于管理列存储数据的存储。其中,CUStorage 类的主要作用是管理列存储中的数据文件(.cu 文件)的存储和读写操作。它负责将列存储单元(CU)的数据保存到磁盘、从磁盘加载 CU 数据、分配存储空间等任务。CUStorage 类与 CU 类关系密切,因为它用于保存和加载 CU 中的数据。CUStorage 类通常是由 CStore 类或其他管理列存储文件的类所使用,以执行列存储数据的持久化和读取操作。
总之,CUStorage 类是列存储中负责管理 CU 数据文件的核心组件之一,与 CU 和 CStore 类协同工作,用于实现列存储数据的高效存储和读取。后续有机会的话我们再对 CUStorage 类进行进一步学习。
这里简单了解一下 CUStorage::CreateStorage 函数。CUStorage::CreateStorage 函数用于创建列存储数据文件,并执行相关的文件处理步骤。函数源码如下所示:(路径:src/gausskernel/storage/cstore/custorage.cpp
)
void CUStorage::CreateStorage(int fileId, bool isRedo) const
{
// 声明一个文件描述符,用于表示创建的文件
File fd;
// 声明一个字符数组,用于存储文件的路径
char filePath[MAXPGPATH];
// 调用 GetFileName 函数,将文件路径存储在 filePath 中。fileId 是传入的参数,用于指定文件的标识
GetFileName(filePath, MAXPGPATH, fileId);
// 调用 CreateFile 函数,创建一个名为 filePath 的文件,其中 fileId 表示文件的标识,isRedo 是一个布尔值,表示是否在重做(redo)操作中创建文件。函数返回的文件描述符被存储在 fd 中
fd = CreateFile(filePath, fileId, isRedo);
// 关闭刚创建的文件,释放文件描述符。这是一个文件处理结束的步骤
close(fd);
}
UnlinkColDataFile 函数用于解除关系的一个列的数据文件关联,可以根据 bcmIncluded 参数决定是否同时解除 BCM 文件的关联。如果不延迟 DDL 操作,会解除数据文件的关联,否则会在日志中记录消息以表示延迟解除关联。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp
)
/* 解除关系的一个列的数据文件关联。 */
void CStore::UnlinkColDataFile(const RelFileNode& rnode, AttrNumber attrnum, bool bcmIncluded)
{
// 断言,确保列号(attrnum)大于 0
Assert(attrnum > 0);
// 创建列文件节点对象 cFileNode,用于表示数据文件的位置和属性
CFileNode cFileNode(rnode, attrnum, MAIN_FORKNUM);
// 使 CStore 分配器无效化列的空间缓存
CStoreAllocator::InvalidColSpaceCache(cFileNode);
// 创建列存储存储对象 cuStorage,用于处理列存储的数据文件
CUStorage cuStorage(cFileNode);
// 如果不延迟 DDL 操作
if (!t_thrd.xact_cxt.xactDelayDDL) {
/* 解除数据文件的关联:C1.0, C1.1 ... */
CStoreUnlinkCuDataFiles(&cuStorage);
} else {
// 在日志中记录延迟解除关联的消息
ereport(LOG,
(errmsg(
"延迟解除列文件 %u/%u/%u 属性 %d 的关联", rnode.spcNode, rnode.dbNode, rnode.relNode, attrnum)));
}
// 如果包括 BCM 文件
if (bcmIncluded) {
/* 解除 BCM 文件的关联:C1_bcm, C1_bcm.1 ... */
CStoreUnlinkCuBcmFiles(&cuStorage);
}
// 销毁 cuStorage 对象,释放相关资源
cuStorage.Destroy();
}
CStoreUnlinkCuDataFiles 函数用于解除关联 CU 文件,文件名的格式类似 “16385_c1.0”, “16385_c1.1” 等。它会不断增加文件 ID,直到找不到下一个数据文件为止,然后解除文件的关联。如果解除失败,会记录警告消息。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp
)
// 解除 cu 文件的关联:16385_c1.0 16385_c1.1 16385_c1.2 ...
static void CStoreUnlinkCuDataFiles(CUStorage* cuStorage)
{
// 初始化文件 ID 为 0
int fileId = 0;
// 临时文件名缓冲区
char tmpFileName[MAXPGPATH];
// 进入循环,直到找不到下一个数据文件
while (1) {
// 如果 cuStorage 中的数据文件存在
if (!cuStorage->IsDataFileExist(fileId))
break;
// 获取数据文件的文件名
cuStorage->GetFileName(tmpFileName, MAXPGPATH, fileId);
// 解除文件的关联,如果解除失败则记录警告消息
if (unlink(tmpFileName)) {
ereport(WARNING, (errmsg("无法解除文件的关联 \"%s\": %m", tmpFileName)));
}
// 增加文件 ID,继续下一个文件的解除
++fileId;
}
}
解除关联(unlink)文件通常是为了删除不再需要的文件,这在数据库管理系统中也是一个常见的操作。在上下文中,为什么要解除 CU 文件的关联可能有以下几种情况:
CStore::FormCudescTuple 函数的作用是将 CUDesc 结构中的信息构建成数据库表中的一个元组,包括列编号、CU ID、最小值、最大值、行数、CU 模式、CU 大小、CU 指针以及CU 魔数等信息,并返回构建好的元组。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp
)
// CStore::FormCudescTuple - 创建Cudesc元组,填充CUDesc对象的信息并返回元组
// 参数:
// pCudescTupDesc: Cudesc元组的描述
// pCudesc: 包含完整Cudesc元组信息的CUDesc对象
// pTupVals: 用于构建元组的值数组
// pTupNulls: 用于构建元组的空值标志数组
// pColAttr: 与pCudesc匹配的列属性,用于列存储表
// 返回:
// 创建并填充的Cudesc元组
HeapTuple CStore::FormCudescTuple(_in_ CUDesc* pCudesc, _in_ TupleDesc pCudescTupDesc,
_in_ Datum pTupVals[CUDescMaxAttrNum], _in_ bool pTupNulls[CUDescMaxAttrNum], _in_ Form_pg_attribute pColAttr)
{
// 初始化pTupNulls数组,确保不包含空值
errno_t rc = memset_s(pTupNulls, CUDescMaxAttrNum, false, CUDescMaxAttrNum);
securec_check(rc, "\0", "\0");
// 设置元组的属性值:
// CUDescColIDAttr: 列编号
pTupVals[CUDescColIDAttr - 1] = Int32GetDatum(pColAttr->attnum);
// CUDescCUIDAttr: cu_id
pTupVals[CUDescCUIDAttr - 1] = UInt32GetDatum(pCudesc->cu_id);
// 计算最小值和最大值的数据长度和指针
int minDataLen = 0, maxDataLen = 0;
char *minDataPtr = NULL, *maxDataPtr = NULL;
if (pColAttr->attlen > 0) {
if (pColAttr->attbyval) {
// 如果属性是按值存储,使用int8的大小存储
minDataLen = maxDataLen = sizeof(Datum);
} else if (pColAttr->attlen <= MIN_MAX_LEN) {
// 如果属性长度不超过MIN_MAX_LEN,则使用属性的长度存储
minDataLen = maxDataLen = pColAttr->attlen;
} else {
// 如果属性长度大于MIN_MAX_LEN,则抛出异常
Assert(minDataLen == 0 && maxDataLen == 0);
}
// 设置最小值和最大值的指针
minDataPtr = pCudesc->cu_min;
maxDataPtr = pCudesc->cu_max;
} else {
// 属性的长度为0的情况
Assert(pCudesc->cu_min[0] >= 0 && pCudesc->cu_min[0] < MIN_MAX_LEN);
Assert(pCudesc->cu_max[0] >= 0 && pCudesc->cu_max[0] < MIN_MAX_LEN);
// 设置最小值和最大值的数据长度和指针
minDataLen = pCudesc->cu_min[0];
minDataPtr = pCudesc->cu_min + 1;
maxDataLen = pCudesc->cu_max[0];
maxDataPtr = pCudesc->cu_max + 1;
}
// 设置CUDescMinAttr和CUDescMaxAttr属性,分别表示最小值和最大值的文本数据
pTupVals[CUDescMinAttr - 1] = PointerGetDatum(cstring_to_text_with_len(minDataPtr, minDataLen));
pTupVals[CUDescMaxAttr - 1] = PointerGetDatum(cstring_to_text_with_len(maxDataPtr, maxDataLen));
// 设置CUDescRowCountAttr属性为行数
pTupVals[CUDescRowCountAttr - 1] = Int32GetDatum(pCudesc->row_count);
// 设置CUDescCUModeAttr属性为CU模式
pTupVals[CUDescCUModeAttr - 1] = Int32GetDatum(pCudesc->cu_mode);
// 设置CUDescSizeAttr属性为CU大小
pTupVals[CUDescSizeAttr - 1] = Int32GetDatum(pCudesc->cu_size);
// 设置CUDescCUPointerAttr属性为CU指针
text* tmpStr3 = cstring_to_text_with_len((const char*)&pCudesc->cu_pointer, sizeof(CUPointer));
pTupVals[CUDescCUPointerAttr - 1] = PointerGetDatum(tmpStr3);
// 设置CUDescCUMagicAttr属性为CU魔数
pTupVals[CUDescCUMagicAttr - 1] = UInt32GetDatum(pCudesc->magic);
Assert(pTupVals[CUDescCUMagicAttr - 1] > 0);
// 标记CUDescCUExtraAttr属性为null
// 使用tableam_tops_form_tuple函数创建并返回构建好的元组
return (HeapTuple)tableam_tops_form_tuple(pCudescTupDesc, pTupVals, pTupNulls, HEAP_TUPLE);
}
这里举一个具体案例来说明该函数的功能:假设有一个列存储表,其中存储了某个销售产品的销售额数据。每个CU(列单元)代表一段数据,每个 CU 包括了该段数据的最小值、最大值、行数等信息。现在,在该表中新增一段数据,需要创建一个新的 CU 并将其相关信息存储到数据库中。这时,就可以使用该函数将新 CU 的相关信息构建成一个数据库表中的元组,并插入到 CUDesc 表中,以便数据库系统能够管理和查询该新 CU 的数据。这样,该函数将新的 CU 描述信息转化为数据库中的一个元组,为数据库中的数据操作提供了必要的元数据。
DeformCudescTuple 函数的主要作用是将 CUDesc 表中的元组解析成 CUDesc 结构,以提取 CU 的描述信息。CU 描述信息包括 CU 的 ID 、最小值、最大值、行数、模式、大小和指针等属性,这些信息在列存储表中用于管理和查询 CU 的数据。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp
)
/*
* description: 该函数用于将CUDesc表的元组解析成CUDesc结构,以提取CU的描述信息。
*/
void CStore::DeformCudescTuple(
_in_ HeapTuple pCudescTup, // 输入参数,CUDesc表中的元组
_in_ TupleDesc pCudescTupDesc, // 输入参数,CUDesc表的元组描述
_in_ Form_pg_attribute pColAttr, // 输入参数,列属性信息,对应于CUDesc的列
_out_ CUDesc* pCudesc // 输出参数,用于存储解析后的CU描述信息
)
{
errno_t rc = EOK;
bool isnull = false;
// 从CUDesc元组中提取并设置CU的ID
pCudesc->cu_id = DatumGetUInt32(fastgetattr(pCudescTup, CUDescCUIDAttr, pCudescTupDesc, &isnull));
Assert(!isnull);
// 将最小值存入pCudesc->cu_min
char* valPtr = DatumGetPointer(fastgetattr(pCudescTup, CUDescMinAttr, pCudescTupDesc, &isnull));
if (!isnull) {
if (pColAttr->attlen > 0) {
if (pColAttr->attbyval) {
// 对于固定大小的基本类型,使用 sizeof(Datum) 存储
Assert((int)VARSIZE_ANY_EXHDR(valPtr) == sizeof(Datum));
rc = memcpy_s(pCudesc->cu_min, MIN_MAX_LEN, VARDATA_ANY(valPtr), sizeof(Datum));
securec_check(rc, "", "");
} else if (pColAttr->attlen <= MIN_MAX_LEN) {
// 对于可变大小类型,存储具体长度
Assert((int)VARSIZE_ANY_EXHDR(valPtr) == pColAttr->attlen);
rc = memcpy_s(pCudesc->cu_min, MIN_MAX_LEN, VARDATA_ANY(valPtr), pColAttr->attlen);
securec_check(rc, "", "");
} else {
Assert(pCudesc->cu_min[0] == 0);
}
} else {
// 对于可变长度的列,元组第一个字节表示长度
pCudesc->cu_min[0] = VARSIZE_ANY_EXHDR(valPtr);
if (pCudesc->cu_min[0] > 0) {
Assert(pCudesc->cu_min[0] < MIN_MAX_LEN);
rc = memcpy_s(pCudesc->cu_min + 1, (MIN_MAX_LEN - 1), VARDATA_ANY(valPtr), pCudesc->cu_min[0]);
securec_check(rc, "", "");
} else {
Assert(pCudesc->cu_min[0] == 0);
}
}
}
// 将最大值存入pCudesc->cu_max
valPtr = DatumGetPointer(fastgetattr(pCudescTup, CUDescMaxAttr, pCudescTupDesc, &isnull));
if (!isnull) {
if (pColAttr->attlen > 0) {
if (pColAttr->attbyval) {
Assert((int)VARSIZE_ANY_EXHDR(valPtr) == sizeof(Datum));
rc = memcpy_s(pCudesc->cu_max, MIN_MAX_LEN, VARDATA_ANY(valPtr), sizeof(Datum));
securec_check(rc, "", "");
} else if (pColAttr->attlen <= MIN_MAX_LEN) {
Assert((int)VARSIZE_ANY_EXHDR(valPtr) == pColAttr->attlen);
rc = memcpy_s(pCudesc->cu_max, MIN_MAX_LEN, VARDATA_ANY(valPtr), pColAttr->attlen);
securec_check(rc, "", "");
} else {
Assert(pCudesc->cu_max[0] == 0);
}
} else {
pCudesc->cu_max[0] = VARSIZE_ANY_EXHDR(valPtr);
if (pCudesc->cu_max[0] > 0) {
Assert(pCudesc->cu_max[0] < MIN_MAX_LEN);
rc = memcpy_s(pCudesc->cu_max + 1, (MIN_MAX_LEN - 1), VARDATA_ANY(valPtr), pCudesc->cu_max[0]);
securec_check(rc, "", "");
} else {
Assert(pCudesc->cu_max[0] == 0);
}
}
}
// 从CUDesc元组中提取并设置CU的行数
pCudesc->row_count = DatumGetInt32(fastgetattr(pCudescTup, CUDescRowCountAttr, pCudescTupDesc, &isnull));
Assert(!isnull);
// 从CUDesc元组中提取并设置CU的模式
pCudesc->cu_mode = DatumGetInt32(fastgetattr(pCudescTup, CUDescCUModeAttr, pCudescTupDesc, &isnull));
Assert(!isnull);
// 从CUDesc元组中提取并设置CU的大小
pCudesc->cu_size = DatumGetInt32(fastgetattr(pCudescTup, CUDescSizeAttr, pCudescTupDesc, &isnull));
Assert(!isnull);
// 从CUDesc元组中提取并设置CU的指针
char* cu_ptr = DatumGetPointer(fastgetattr(pCudescTup, CUDescCUPointerAttr, pCudescTupDesc, &isnull));
if (!isnull) {
Assert(VARSIZE_ANY_EXHDR(cu_ptr) == sizeof(CUPointer));
pCudesc->cu_pointer = *(CUPointer*)VARDATA_ANY(cu_ptr);
} else
Assert(pCudesc->cu_pointer == 0);
// 从CUDesc元组中提取并设置CU的标识
pCudesc->magic = DatumGetUInt32(fastgetattr(pCudescTup, CUDescCUMagicAttr, pCudescTupDesc, &isnull));
Assert(!isnull);
}
细心的小伙伴可以发现,上面的代码中,反复提到了一个结构 CUDesc 。那它到底是什么呢?有什么作用呢?和 CU 又是什么关系呢?
CUDesc 结构体用于表示列存储引擎中的 Column Unit(CU)的描述信息。CU 是列存储表的基本单位,每个 CU 包含一组行数据,并存储有关这些数据的元信息。CUDesc 结构体的字段和方法用于描述和操作 CU 的属性和状态。
CUDesc 结构体描述了 CU 的元信息,而 CU 类存储了 CU 的实际数据。这两个类协同工作,以便在列存储引擎中管理和查询列存储数据。CUDesc 通常用于获取元信息,以便定位和操作 CU 中的实际数据。
CUDESC 表和 CU 对应关系示意图如下所示:
CUDesc 类源码如下所示:(路径:src/include/storage/cu.h
)
struct CUDesc : public BaseObject {
TransactionId xmin;
/*
* CU的序列号
*/
uint32 cu_id;
// CU的最小值
// 如果数据类型是固定长度,cu_min存储数值。
// 如果数据类型是可变长度,cu_min存储长度和值
// 格式: 长度(1字节) 值(长度 <= MIN_MAX_LEN)
//
char cu_min[MIN_MAX_LEN];
// CU的最大值
// 如果数据类型是固定长度,cu_max存储数值。
// 如果数据类型是可变长度,cu_max存储长度和值
// 格式: 长度(1字节) 值(长度 <= MIN_MAX_LEN)
//
char cu_max[MIN_MAX_LEN];
/*
* CU的行数
*/
int row_count;
/*
* CU的数据大小
*/
int cu_size;
/*
* CU信息掩码
*/
int cu_mode;
/*
* CU在CU存储中的指针
*/
CUPointer cu_pointer;
/*
* 魔数用于验证CU数据的有效性
*/
uint32 magic;
public:
CUDesc();
~CUDesc();
// 设置为NULL CU
void SetNullCU();
// 检查是否为NULL CU
bool IsNullCU() const;
// 设置为正常CU
void SetNormalCU();
// 设置为包含相同值的CU
void SetSameValCU();
// 检查是否为正常CU
bool IsNormalCU() const;
// 检查是否为包含相同值的CU
bool IsSameValCU() const;
// 设置为没有最小最大值的CU
void SetNoMinMaxCU();
// 检查是否为没有最小最大值的CU
bool IsNoMinMaxCU() const;
// 设置CU包含NULL值
void SetCUHasNull();
// 检查CU是否包含NULL值
bool CUHasNull() const;
// 重置CU
void Reset();
// 销毁CU
void Destroy() {}
};
CStore::SaveCUDesc 函数用于将列单元描述(CUDesc)信息存储到 CUDesc 表中,以便列存储数据库能够跟踪和管理列单元的元信息,支持列存储引擎的操作。这有助于维护和优化数据的存储结构,并支持列存储表的查询和维护。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp
)
// 将CUDesc(列单元描述)保存到CUDesc表中
void CStore::SaveCUDesc(_in_ Relation rel, _in_ CUDesc* cuDescPtr, _in_ int col, int options) {
// 确保关系对象不为空
Assert(rel != NULL);
// 确保列号大于等于零
Assert(col >= 0);
// 如果要保存的列已经被标记为已删除,则抛出错误
if (rel->rd_att->attrs[col]->attisdropped) {
ereport(PANIC,
(errmsg("无法为已删除的列 \"%s\" 保存CUDesc 到表 \"%s\" 中",
NameStr(rel->rd_att->attrs[col]->attname),
RelationGetRelationName(rel))));
}
// 打开CUDesc表
Relation cudesc_rel = heap_open(rel->rd_rel->relcudescrelid, RowExclusiveLock);
// 打开CUDesc表的索引
Relation idx_rel = index_open(cudesc_rel->rd_rel->relcudescidx, RowExclusiveLock);
// 创建一个用于存储插入数据的数组和NULL值信息的数组
Datum values[CUDescMaxAttrNum];
bool nulls[CUDescMaxAttrNum];
// 创建一个HeapTuple,包含CUDesc的信息
HeapTuple tup = CStore::FormCudescTuple(cuDescPtr, cudesc_rel->rd_att, values, nulls, rel->rd_att->attrs[col]);
// 通过插入操作将CUDesc元组插入到CUDesc表中
// options参数用于控制是否生成XLOG记录以确保数据持久性
(void)heap_insert(cudesc_rel, tup, GetCurrentCommandId(true), options, NULL);
// 在CUDesc表的相关索引中插入对应的条目,以支持索引查找和唯一性检查
index_insert(idx_rel,
values,
nulls,
&(tup->t_self),
cudesc_rel,
idx_rel->rd_index->indisunique ? UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
// 释放CUDesc元组的内存和values数组中的数据
heap_freetuple(tup);
pfree(DatumGetPointer(values[CUDescMinAttr - 1]));
pfree(DatumGetPointer(values[CUDescMaxAttr - 1]));
pfree(DatumGetPointer(values[CUDescCUPointerAttr - 1]));
// 关闭CUDesc表和索引,释放锁
index_close(idx_rel, RowExclusiveLock);
heap_close(cudesc_rel, RowExclusiveLock);
}
CStore::GetMaxCUID 函数用于获取指定列的最大 CU(列单元) ID,从 CUDesc 表中检索该信息,并支持列存储表的操作。该函数首先创建一个名为 key 的扫描键,然后打开 CUDesc 关系表,扫描其索引,最终返回指定列的最大CU ID,以便用于列存储表的操作。函数执行过程中考虑了列是否被删除,获取相关的元数据信息,并利用索引执行有序扫描以提高效率。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp
)
uint32 CStore::GetMaxCUID(Oid cudescHeap, TupleDesc cstoreRelTupDesc, Snapshot snapshotArg) {
ScanKeyData key;
HeapTuple tup;
bool isnull = false;
/* 使用任何快照查找最大的CU ID,包括已中止或崩溃的事务。 */
Snapshot snapshot = NULL;
snapshot = snapshotArg ? snapshotArg : SnapshotAny;
// 查找一个未被删除的列。
int attrId = 0;
for (int i = 0; i < cstoreRelTupDesc->natts; ++i) {
if (!cstoreRelTupDesc->attrs[i]->attisdropped) {
attrId = cstoreRelTupDesc->attrs[i]->attnum;
break;
}
}
Assert(attrId > 0);
// 打开CUDesc关系和它的索引
Relation heapRel = heap_open(cudescHeap, AccessShareLock);
TupleDesc heapTupDesc = RelationGetDescr(heapRel);
Relation indexRel = index_open(heapRel->rd_rel->relcudescidx, AccessShareLock);
uint32 maxCuId = FirstCUID;
// 设置用于按列ID从索引中获取数据的扫描键。
ScanKeyInit(&key, (AttrNumber)CUDescColIDAttr, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(attrId));
SysScanDesc cudesc_scan = systable_beginscan_ordered(heapRel, indexRel, snapshot, 1, &key);
// 使用BackwardScanDirection扫描以优化获取列的最后一个CU描述。
if ((tup = systable_getnext_ordered(cudesc_scan, BackwardScanDirection)) != NULL) {
maxCuId = DatumGetUInt32(fastgetattr(tup, CUDescCUIDAttr, heapTupDesc, &isnull));
Assert(!isnull);
}
systable_endscan_ordered(cudesc_scan);
index_close(indexRel, AccessShareLock);
heap_close(heapRel, AccessShareLock);
return maxCuId;
}
对于 CStore 类中的公共静态函数暂且先介绍到这里,对于未介绍的部分等后续涉及到再做详细的学习。
致谢:这里感谢以下作者的相关资料:
- openGauss数据库源码解析系列文章——存储引擎源码解析(三)
- openGauss存储技术(二)——列存储引擎和内存引擎
- 解读数仓中的数据对象及相关关系