openGauss 列存表PSort索引

openGaussbaby發表於2024-04-10

openGauss 列存表 PSort 索引
概述
PSort(Partial sort) Index 是在列存表的列上建的聚簇索引。CUDesc 上有每個 CU 的 min 和 max 值,但如果業務的資料模型較為離散,查詢時透過 min 和 max 值去過濾 CU 會出現大量的 CU 誤讀取,例如每個 CU 的 min 和 max 跨度都比較大時,其查詢效率接近全表掃描。例如下圖中的場景,查詢 2 基本命中所有的 CU, 此時查詢近似全表掃描。

PSort 索引可以對部分割槽間(一般會包含多個 CU 覆蓋的行)內的資料按照索引鍵進行排序,使得 CU 之間的交集儘量減少,提升查詢的效率。

PSort 索引使用
在批次插入列存表的過程中,如果發現有 PSort 索引,會先對這批資料進行排序。PSort 索引表的組織形式也是 cstore 表(CUDesc 是 astore 表),表的欄位包含了索引鍵的各個欄位,加上對應的行號(TID)欄位。插入資料的過程中如果發現有 PSort 索引,會將一定數量的資料按照 PSort 索引的索引鍵進行排序,與 TID 欄位共同拼裝成向量陣列,再插入到 PSort 索引的 cstore 表中。 所以 PSort 索引資料中列數比實際的索引鍵要多一列,多出的這一列用於儲存這條記錄在資料 cstore 儲存中的位置。

// 構建 PSort 索引過程中構造索引資料
inline void ProjectToIndexVector(VectorBatch *scanBatch, VectorBatch *outBatch, IndexInfo *indexInfo)
{
Assert(scanBatch && outBatch && indexInfo);
int numAttrs = indexInfo->ii_NumIndexAttrs;
AttrNumber *attrNumbers = indexInfo->ii_KeyAttrNumbers;
Assert(outBatch->m_cols == (numAttrs + 1));

// index column
for (int i = 0; i < numAttrs; i++) {
    AttrNumber attno = attrNumbers[i];
    Assert(attno > 0 && attno <= scanBatch->m_cols);

    // shallow copy
    outBatch->m_arr[i].copy(&scanBatch->m_arr[attno - 1]);
}

// ctid column
// 最後一列是 tid
outBatch->m_arr[numAttrs].copy(scanBatch->GetSysVector(-1));

outBatch->m_rows = scanBatch->m_rows;

}
cstore 表執行插入流程,如果有 Psort 索引,會先將資料插入排序佇列

void CStoreInsert::BatchInsert(in VectorBatch* pBatch, in int options)
{
Assert(pBatch || IsEnd());

/* keep memory space from leaking during bulk-insert */
MemoryContext oldCnxt = MemoryContextSwitchTo(m_tmpMemCnxt);

// Step 1: relation has partial cluster key
// We need put data into sorter contatiner, and then do
// batchinsert data
if (NeedPartialSort()) {
    Assert(m_tmpBatchRows);

    if (pBatch) {
        Assert(pBatch->m_cols == m_relation->rd_att->natts);
        m_sorter->PutVecBatch(m_relation, pBatch); // 插入區域性排序佇列
    }

    if (m_sorter->IsFull() || IsEnd()) { // 排序佇列滿了或者插入資料輸入結束
        m_sorter->RunSort(); // 按照索引鍵排序

        /* reset and fetch next batch of values */
        DoBatchInsert(options);
        m_sorter->Reset(IsEnd());

        /* reset and free all memory blocks */
        m_tmpBatchRows->reset(false);
    }
}

// Step 2: relation doesn't have partial cluster key
// We need cache data until batchrows is full
else {
    Assert(m_bufferedBatchRows);

    // If batch row is full, we can do batchinsert now
    if (IsEnd()) {
        if (ENABLE_DELTA(m_bufferedBatchRows)) {
            InsertDeltaTable(m_bufferedBatchRows, options);
        } else {
            BatchInsertCommon(m_bufferedBatchRows, options);
        }
        m_bufferedBatchRows->reset(true);
    }

    // we need cache data until batchrows is full
    if (pBatch) {
        Assert(pBatch->m_rows <= BatchMaxSize);
        Assert(pBatch->m_cols && m_relation->rd_att->natts);
        Assert(m_bufferedBatchRows->m_rows_maxnum > 0);
        Assert(m_bufferedBatchRows->m_rows_maxnum % BatchMaxSize == 0);

        int startIdx = 0;
        while (m_bufferedBatchRows->append_one_vector(
                   RelationGetDescr(m_relation), pBatch, &startIdx, m_cstorInsertMem)) {
            BatchInsertCommon(m_bufferedBatchRows, options);
            m_bufferedBatchRows->reset(true);
        }
        Assert(startIdx == pBatch->m_rows);
    }
}

// Step 3: We must update index data for this batch data
// if end of batchInsert
FlushIndexDataIfNeed();

MemoryContextReset(m_tmpMemCnxt);
(void)MemoryContextSwitchTo(oldCnxt);

}

圖 cstore 表插入流程示意圖

插入流程中更新索引資料的程式碼

void CStoreInsert::InsertIdxTableIfNeed(bulkload_rows* batchRowPtr, uint32 cuId)
{
Assert(batchRowPtr);

if (relation_has_indexes(m_resultRelInfo)) {
    /* form all tids */
    bulkload_indexbatch_set_tids(m_idxBatchRow, cuId, batchRowPtr->m_rows_curnum);

    for (int indice = 0; indice < m_resultRelInfo->ri_NumIndices; ++indice) {
        /* form index-keys data for index relation */
        for (int key = 0; key < m_idxKeyNum[indice]; ++key) {
            bulkload_indexbatch_copy(m_idxBatchRow, key, batchRowPtr, m_idxKeyAttr[indice][key]);
        }

        /* form tid-keys data for index relation */
        bulkload_indexbatch_copy_tids(m_idxBatchRow, m_idxKeyNum[indice]);

        /* update the actual number of used attributes */
        m_idxBatchRow->m_attr_num = m_idxKeyNum[indice] + 1;

        if (m_idxInsert[indice] != NULL) {
            /* 插入PSort 索引 */
            m_idxInsert[indice]->BatchInsert(m_idxBatchRow, 0);
        } else {
            /* 插入 cbtree/cgin 索引 */
            CStoreInsert::InsertNotPsortIdx(indice);
        }
    }
}

}
索引插入流程和普通 cstore 資料插入相同。

使用 PSort 索引查詢時,由於 PSort 索引 CU 內部已經有序,因此可以使用二分查詢快速找到對應資料在 psort 索引中的行號,這一行資料的 tid 欄位就是這條資料在資料 cstore 中的行號。

圖-2 PSort 索引查詢示意圖

相關文章