8.4.4 关键源码解析
1. 项目结构
智能索引推荐功能在项目中的源代码路径在openGauss-server/src/gausskernel/dbmind中,涉及的相关文件如表8-8所示。
表8-8 智能索引推荐功能源代码路径
文件路径 | 说明 |
kernel/index_advisor.cpp | 单条查询语句的索引推荐。 |
kernel/hypopg_index.cpp | 虚拟索引特性实现 |
tools/index_advisor/index_advisor_workload.py | 基于工作负载的索引推荐 |
其中,单条查询语句的索引推荐功能和虚拟索引的功能通过数据库的系统函数进行调用,基于工作负载的索引推荐功能需要通过数据库外部的脚本运行。
2. 关键代码解析
单条语句索引推荐的所有实现部分都只存在于index_advisor.cpp文件中,该功能的主要入口为suggest_index函数,它通过系统函数gs_index_advise进行调用,代码如下:
SuggestedIndex *suggest_index(const char *query_string, _out_ int *len)
{
……
// 对查询语句进行词法和语法解析,获得解析树
List *parse_tree_list = raw_parser(query_string);
…
// 递归地搜索解析树中的SelectStmt结构
Node *parsetree = (Node *)lfirst(list_head(parse_tree_list));
find_select_stmt(parsetree);
…
// 依次解析和处理SelectStmt结构中的各个子句部分
ListCell *item = NULL;
foreach (item, g_stmt_list) {
SelectStmt *stmt = (SelectStmt *)lfirst(item);
/* 处理SelectStmt 结构体中涉及的FROM子句,提取涉及的表,解析和保存这些表中的join关系 */
parse_from_clause(stmt->fromClause);
…
if (g_table_list) {
// 处理WHERE子句,提取条件表达式中的谓词并添加候选索引,解析和保存其中的join关系
parse_where_clause(stmt->whereClause);
// 根据保存的join关系确定驱动表
determine_driver_table();
// 处理GROUP子句,如果满足条件,则将其中的谓词添加为候选索引
if (parse_group_clause(stmt->groupClause, stmt->targetList)) {
add_index_from_group_order(g_driver_table, stmt->groupClause, stmt->targetList, true);
/* 处理ORDER子句,如果满足条件,则将其中的谓词添加为候选索引 */
} else if (parse_order_clause(stmt->sortClause, stmt->targetList)) {
add_index_from_group_order(g_driver_table, stmt->sortClause, stmt->targetList, false);
}
// 如果是多表查询,则根据保存的join关系为被驱动表添加候选索引
if (g_table_list->length > 1 && g_driver_table) {
add_index_for_drived_tables();
}
/* 对全局变量中的每个table依次进行处理,函数generate_final_index将前述过程生成的候选索引进行字符串拼接,并检查和已存在的索引是否重复 */
ListCell *table_item = NULL;
foreach (table_item, g_table_list) {
TableCell *table = (TableCell *)lfirst(table_item);
if (table->index != NIL) {
Oid table_oid = find_table_oid(query_tree->rtable, table->table_name);
if (table_oid == 0) {
continue;
}
generate_final_index(table, table_oid);
}
}
g_driver_table = NULL;
}
}
……
return array;
}
虚拟索引的核心功能全部位于hypopg_index.cpp文件中。用户通过SQL语句调用系统函数hypopg_create_index来创建虚拟索引,该系统函数主要通过调用hypo_index_store_parsetree函数来完成虚拟索引的创建。虚拟索引的结构体名为hypoIndex,该结构体的许多字段是从它涉及的表的RelOptInfo结构体中读取的,hypoIndex的结构如下:
typedef struct hypoIndex {
Oid oid; /* 虚拟索引的oid,该oid是唯一的 */
Oid relid; /* 涉及的表的oid */
…
char *indexname; /* 虚拟索引名 */
BlockNumber pages; /* 预估索引使用的磁盘页数 */
double tuples; /* 预估索引所涉及的元组数目 */
/* 索引描述信息 */
int ncolumns; /* 涉及的总列数 */
int nkeycolumns; /* 涉及的关键列数 */
…
Oid relam; /* 记录索引操作回调函数的元组的oid, 从pg_am系统表中获取的 */
…
} hypoIndex;
函数hypo_index_store_parsetree的输入参数为创建索引的SQL语句和其语法树,依据该语句的解析结果来创建新的虚拟索引,代码如下:
hypoIndex *hypo_index_store_parsetree(IndexStmt *node, const char *queryString)
{
……
// 获得创建索引的表的oid
relid = RangeVarGetRelid(node->relation, AccessShareLock, false);
……
// 对该创建索引的语句进行语法解析
node = transformIndexStmt(relid, node, queryString);
……
// 新建虚拟索引,该虚拟索引的结构体类型hypoIndex定于位于文件openGauss-server/src/include/dbmind/hypopg_index.h,与索引结构体IndexOptInfo类似
entry = hypo_newIndex(relid, node->accessMethod, nkeycolumns, ninccolumns, node->options);
// 根据语法树的解析结果为虚拟索引entry内的各个成员赋值
PG_TRY();
{
……
entry->unique = node->unique;
entry->ncolumns = nkeycolumns + ninccolumns;
entry->nkeycolumns = nkeycolumns;
……
}
PG_CATCH();
{
hypo_index_pfree(entry);
PG_RE_THROW();
}
PG_END_TRY();
// 设置虚拟索引的名字
hypo_set_indexname(entry, indexRelationName.data);
// 将新建的虚拟索引entry添加到虚拟索引的全局链表hypoIndexes上,该全局变量为节点类型为hypoIndex*的List链表,记录了全部创建过的虚拟索引
hypo_addIndex(entry);
return entry;
}
// 该函数被赋值给全局的函数指针get_relation_info_hook,当数据库执行EXPLAIN时,会通过该函数指针跳转到本函数
void hypo_get_relation_info_hook(PlannerInfo *root, Oid relationObjectId, bool inhparent, RelOptInfo *rel)
{
/* 判断是否开启GUC参数enable_hypo_index,当SQL语句是EXPLAIN命令时,变量isExplain的值为真 */
if (u_sess->attr.attr_sql.enable_hypo_index && isExplain) {
Relation relation;
relation = heap_open(relationObjectId, AccessShareLock);
if (relation->rd_rel->relkind == RELKIND_RELATION) {
ListCell *lc;
/* 遍历全局变量链表hypoIndexes中的每个创建过的虚拟索引 */
foreach (lc, hypoIndexes) {
hypoIndex *entry = (hypoIndex *)lfirst(lc);
// 判断该虚拟索引和该表是否匹配
if (hypo_index_match_table(entry, RelationGetRelid(relation))) {
// 如果匹配,则将该索引加入该表的indexlist中,indexlist是节点类型为IndexOptInfo的链表,是结构体类型RelOptInfo的成员,记录了表的所有的索引
hypo_injectHypotheticalIndex(root, relationObjectId, inhparent, rel, relation, entry);
}
}
}
heap_close(relation, AccessShareLock);
}
……
}