一. 前言
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(¶llel_scan->phs_nallocated, 1); // 各个worker线程拿扫描的block id,采用锁原子自增的方式分配,worker线程拿到的自增值是多少就扫描哪些block
callback(heapTuple) // 将元组推送到公共的memtuples中
4. 后续的排序和写入索引文件和普通创建索引一样。

1094

被折叠的 条评论
为什么被折叠?



