openGauss 之 索引reindex代码走读

一. 前言

     openGauss中的reindex创建索引的方式分成三种,直接reindex, reindex concurrently和并行reindex。 本文通过走读openGauss中的代码了解openGauss是如何实现这三种操作的。

二. 普通reindex index操作

     直接reindex实现比较简单,对被index的索引加8级别和对其基表加5级锁,进行单并发重建索引操作,代码入口在ReindexIndex函数中,如下为主要流程:

ReindexIndex
    reindex_index
        indexLockMode = AccessExclusiveLock;   // reindex索引对索引加的是8级锁
        heapLockMode = ShareLock;              // 对基表加的是5级锁
        RelationSetNewRelfilenode(iRel, InvalidTransactionId, InvalidMultiXactId);   // 设置索引文件到新文件中,等于清空索引 
        index_build
            index_build_storage
                btbuild
                    btbuild_internal
                        reltuples = _bt_spools_heapscan(heap, index, &buildstate, indexInfo, &allPartTuples);  // 扫描堆元素
                        _bt_leafbuild(buildstate.spool, buildstate.spool2);
                            tuplesort_performsort(btspool->sortstate);   // 排序
                            _bt_load(&wstate, btspool, btspool2);        // 将排序好的元祖写入到索引文件中  

三. reindex index concurrently

      reindex index concurrently并不是指多个线程并行创建索引,而是创建索引的过程中不阻塞DML使用基表和旧索引,因为加的是四级锁。整体的步骤为“基于当前的数据新建临时索引 --> 追平临时索引的数据到最新 --> 交换临时索引和旧索引的名称 --> 删除旧索引”。 如下为主要代码走读过程:

ReindexIndex
    lockmode = concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock;  // 并行index加的是4级锁
    indOid = RangeVarGetRelidExtended(indexRelation,lockmode....);   // 对索引加4级锁
    if (concurrent) {
        ReindexRelationConcurrently(indOid, indPartOid, mem_info, false);
            foreach (lc, indexIds) {    // 遍历需要并行创建的索引
                indexRel = index_open(indexId, ShareUpdateExclusiveLock);    // 旧索引表短暂加4级锁
                heapRel = heap_open(indexRel->rd_index->indrelid, ShareUpdateExclusiveLock);    // 基表大部分都处于四级锁状态
                concurrentName = ChooseRelationName(get_rel_name(indexId), NULL, "ccnew", 5)   // 临时表,命名为xxx_ccnew
                newIndexId = index_concurrently_create_copy  // 复制当前索引表结构到新表
                newIndexRel = index_open(newIndexId, ShareUpdateExclusiveLock);  // 对新临时表同样加4级锁
                
                // 第二阶段:基于当前的快照创建基表的临时索引
                forboth (lc, indexIds, lc2, newIndexIds) {
                    StartTransactionCommand();
                    index_concurrently_build(heapId, newIndexId, isPrimary, memInfo, dbWide);
                        heapRel =  heap_open(heapRelationId, ShareUpdateExclusiveLock);  // 耗时较长的流程中主要加四级锁
                        index_build
                            if (indexInfo->ii_Concurrent && indexInfo->ii_ParallelWorkers > 0) {
                                indexInfo->ii_ParallelWorkers = 0;   // 开启CONCURRENTLY后不支持并行
                            }
                            index_build_storage   // 创建索引
                                btbuild
                                    _bt_spools_heapscan(heap, index, &buildstate, indexInfo, &allPartTuples);  // 扫描堆数据
                                    _bt_leafbuild(buildstate.spool, buildstate.spool2);  // 创建索引
                }
                
                // 第三阶段:追平2个快照之间的数据
                foreach (lc, newIndexIds) {
                    validate_index(heapId, newIndexId, snapshot);
                        heapRelation = heap_open(heapId, ShareUpdateExclusiveLock);  // 第二阶段基表同样是四级锁
                        index_bulk_delete(&ivinfo, NULL, validate_index_callback, (void*)&state)  // 扫描新的临时索引表数据
                            btbulkdelete_internal
                                btvacuumscan  // 对第二阶段的索引进行扫描
                                   validate_index_callback   // 新索引扫描并且push到tuplesort中
                        tuplesort_performsort(state.tuplesort);   // 对新的临时索引表进行排序
                        tableam_index_validate_scan(heapRelation, indexRelation, indexInfo, snapshot, &state);   // 重新扫描基表追平索引的数据
                            HeapamIndexValidateScan
                                validate_index_heapscan
                                    while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL) {  // 遍历基表数据
                                        // 对平索引的数据和基表的数据,用于判断索引的数据是否在基表的数据中
                                        while (!tuplesort_empty && (!indexcursor || ItemPointerCompare(indexcursor, &rootTuple) < 0)) {
                                            tuplesort_empty = !tuplesort_getdatum(state->tuplesort, true, &ts_val, &ts_isnull);
                                        }
                                        
                                        // 索引无数据匹配基表的数据,将基表的数据insert到索引中。
                                        // 如果有匹配到的数据,ItemPointerCompare(indexcursor, &rootTuple)为0
                                        if ((tuplesort_empty || ItemPointerCompare(indexcursor, &rootTuple) > 0) && !in_index[root_offnum - 1]) {
                                            index_insert(indexRelation, &rootTuple)  // 把当前堆表的元祖同步到新的索引表上
                                        }
                                    }
                            
                }
                
                
                // 阶段4:
                forboth (lc, indexIds, lc2, newIndexIds) {
                    index_concurrently_swap(newIndexId, oldIndexId, oldName);  // 交换新旧索引的名称
                }
                
                // 阶段5:
                foreach (lc, indexIds) {
                    index_concurrently_set_dead(heapId, oldIndexId);  // 对旧索引设置为不可用
                }
                
                
                // 阶段6: 删除旧索引
                performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_CONCURRENTLY_LOCK | PERFORM_DELETION_INTERNAL);
                
            }   
    }

四. 多线程并行创建索引

      多线程并行创建索引是要通过设置设置表的parallel_workers参数方式,比如ALTER TABLE xxx set(parallel_workers=4)后,重建索引时候,会有4个线程并行创建索引,整体流程和普通reindex差不多,本章只关注并行部分的处理差异。

1. index_build的时候获取并行度
index_build
    int parallel_workers = get_parallel_workers(heapRelation);  // 获取parallel_workers设置的并行度
        (relation)->rd_options)->parallel_workers
    indexInfo->ii_ParallelWorkers    // 设置堆数据扫描的并行度到扫描的索引信息中



2. 根据indexInfo->ii_ParallelWorkers启动各个线程扫描推数据:
_bt_spools_heapscan
    if (indexInfo->ii_ParallelWorkers > 0) {
        _bt_begin_parallel(buildstate, indexInfo->ii_ParallelWorkers, workmem, isplain);
            // 启动多线程,_bt_parallel_build_main为线程入口,_bt_parallel_cleanup为线程出口
            LaunchBackgroundWorkers(request, btshared, _bt_parallel_build_main, _bt_parallel_cleanup)
                for (int i = 0; i < nworkers; ++i) {
                    if (RegisterBackgroundWorker(bwc, flag)) {     // 启动各个worker扫描数据
                        actualWorkers++;
                }
    }


3. 扫描数据时分配各个worker的扫描block id
在并行堆扫描中,各个work的分配按照block id先到先得的方式分配,核心代码如下所示:
_bt_parallel_build_main
    _bt_parallel_scan_and_sort
        HeapamIndexBuildScan
            IndexBuildHeapScan
                heap_getnext(scan, ForwardScanDirection)
                    heapgettup
                        page = HeapParallelscanNextpage(scan);
                            nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);  // 各个worker线程拿扫描的block id,采用锁原子自增的方式分配,worker线程拿到的自增值是多少就扫描哪些block
                    callback(heapTuple)   // 将元组推送到公共的memtuples中
            
4. 后续的排序和写入索引文件和普通创建索引一样。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值