【PostgreSQL内核学习(二十一)—— 执行器(InitPlan)】

执行器(InitPlan)

  • 概述
  • InitPlan 函数
    • 代码段解释
    • ExecInitNode 函数
  • 总结

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 postgresql-10.1 的开源代码和《OpenGauss数据库源码解析》和《PostgresSQL数据库内核分析》一书

概述

  在【OpenGauss源码学习 —— 执行器(execMain)】一文中,我们学习了执行器中执行查询的核心函数和逻辑。在本文中,我们将深入研究 InitPlan 的内容。InitPlan 是数据库查询执行过程中的关键组成部分,它用于在查询计划的执行过程中初始化子查询或表达式的结果,以确保正确的查询执行顺序和结果。通过本文的学习,我们将深入了解 InitPlan 的原理和实现细节,从而更好地理解数据库查询的执行机制。具体学习的模块如下图红色框体所示:
在这里插入图片描述

InitPlan 函数

  InitPlan 函数是数据库查询执行过程中的初始化函数,其作用和功能主要集中在准备和设置查询计划(Query Plan)的执行环境。在关系型数据库管理系统(如 PostgreSQL)中,这是执行数据库查询的关键步骤。函数的主要职责包括:

  • 权限验证检查对于涉及的表是否有足够的访问权限,确保查询的安全性。
  • 设置执行状态初始化查询的执行状态(EState),包括设置查询涉及的表(range table)和计划语句(PlannedStmt)。
  • 处理结果关系对数据库操作影响的表(如插入更新删除操作的目标表)进行初始化和锁定。这包括处理分区表的情况,确保正确处理表的分区结构。
  • 锁定更新/共享关系对于涉及 FOR UPDATEFOR SHARE 的表,提前进行锁定,以避免在执行计划树初始化过程中的锁升级。
  • 初始化元组表创建和初始化元组表,这是用于在查询执行过程中临时存储和处理数据的结构。
  • 子计划的初始化对查询计划中的所有子计划(子查询)进行初始化,设置它们的私有状态。这对于处理复杂查询中的嵌套子查询非常关键。
  • 初始化查询计划树的节点对查询计划树中的每个节点进行初始化,这是查询执行的核心部分,涉及打开文件分配资源等准备工作。
  • 设置垃圾过滤器对于 SELECT 查询,如果目标列表中包含“垃圾”属性(即不需要的数据),则初始化垃圾过滤器,以清理这些不需要的数据。

  具体操作如下:

  • 函数首先接收一个 QueryDesc 类型的参数,这包含了操作类型计划的语句执行状态等信息。
  • 根据计划语句操作类型,函数进行不同的初始化操作,这可能包括打开文件分配内存锁定表等。
  • 函数处理可能存在的子计划,并初始化它们。这是重要的,因为子计划可能会影响主查询的执行。
  • 最后,函数准备好执行计划树中的所有节点,以便执行实际的数据库查询。

  总的来说,InitPlan 函数是数据库查询执行的重要部分,负责准备和初始化所有必要的结构和状态,以便数据库管理系统可以有效地执行一个查询计划。函数源码如下所示:(路径:src\backend\executor\execMain.c

/* ----------------------------------------------------------------
 *        InitPlan
 *
 *        初始化查询计划: 打开文件、分配存储空间并启动规则管理器
 * ----------------------------------------------------------------
 */
static void
InitPlan(QueryDesc *queryDesc, int eflags)
{
    CmdType     operation = queryDesc->operation; // 获取操作类型,例如 SELECT、UPDATE 等
    PlannedStmt *plannedstmt = queryDesc->plannedstmt; // 获取已计划的语句
    Plan       *plan = plannedstmt->planTree; // 获取计划的查询树
    List       *rangeTable = plannedstmt->rtable; // 获取涉及的范围表(关系表)列表
    EState     *estate = queryDesc->estate; // 获取执行状态
    PlanState  *planstate; // 声明用于后续初始化的 PlanState 指针
    TupleDesc  tupType; // 声明用于描述元组的结构
    ListCell   *l; // 声明用于列表迭代的 ListCell 指针
    int         i; // 声明用于迭代的整型变量

    /* 执行权限检查 */
    ExecCheckRTPerms(rangeTable, true); // 检查对涉及的表的访问权限

    /* 初始化节点的执行状态 */
    estate->es_range_table = rangeTable; // 设置执行状态中的范围表
    estate->es_plannedstmt = plannedstmt; // 设置执行状态中的已计划语句

    /* 初始化结果关系,打开和锁定结果关联 */
    if (plannedstmt->resultRelations)
    {
        List       *resultRelations = plannedstmt->resultRelations; // 获取结果关系列表
        int         numResultRelations = list_length(resultRelations); // 计算结果关系的数量
        ResultRelInfo *resultRelInfos; // 声明结果关系信息数组
        ResultRelInfo *resultRelInfo; // 声明单个结果关系信息

        /* 为结果关系信息数组分配内存 */
        resultRelInfos = (ResultRelInfo *)
            palloc(numResultRelations * sizeof(ResultRelInfo));
        resultRelInfo = resultRelInfos;
        foreach(l, resultRelations)
        {
            Index       resultRelationIndex = lfirst_int(l); // 获取结果关系索引
            Oid         resultRelationOid; // 声明结果关系的 OID
            Relation    resultRelation; // 声明结果关系

            /* 获取结果关系的 OID,并打开结果关系 */
            resultRelationOid = getrelid(resultRelationIndex, rangeTable);
            resultRelation = heap_open(resultRelationOid, RowExclusiveLock);

            /* 初始化单个结果关系信息 */
            InitResultRelInfo(resultRelInfo,
                              resultRelation,
                              resultRelationIndex,
                              NULL,
                              estate->es_instrument);
            resultRelInfo++;
        }
        estate->es_result_relations = resultRelInfos; // 设置执行状态中的结果关系信息数组
        estate->es_num_result_relations = numResultRelations; // 设置执行状态中的结果关系数量
        estate->es_result_relation_info = NULL; // 初始化结果关系信息为 NULL

	    /* 对分区结果关系进行处理 */
	    estate->es_root_result_relations = NULL; // 初始化根结果关系为 NULL
	    estate->es_num_root_result_relations = 0; // 初始化根结果关系数量为 0
	    if (plannedstmt->nonleafResultRelations)
	    {
	        int         num_roots = list_length(plannedstmt->rootResultRelations); // 获取根结果关系的数量
	
	        /* 为分区表的根建立 ResultRelInfos */
	        resultRelInfos = (ResultRelInfo *)
	            palloc(num_roots * sizeof(ResultRelInfo)); // 分配内存
	        resultRelInfo = resultRelInfos;
	        foreach(l, plannedstmt->rootResultRelations)
	        {
	            Index       resultRelIndex = lfirst_int(l); // 获取结果关系索引
	            Oid         resultRelOid; // 声明结果关系的 OID
	            Relation    resultRelDesc; // 声明结果关系描述
	
	            /* 获取结果关系的 OID 并打开结果关系描述 */
	            resultRelOid = getrelid(resultRelIndex, rangeTable);
	            resultRelDesc = heap_open(resultRelOid, RowExclusiveLock);
	
	            /* 初始化根结果关系信息 */
	            InitResultRelInfo(resultRelInfo,
	                              resultRelDesc,
	                              lfirst_int(l),
	                              NULL,
	                              estate->es_instrument);
	            resultRelInfo++;
	        }
	
	        estate->es_root_result_relations = resultRelInfos; // 设置执行状态中的根结果关系信息
	        estate->es_num_root_result_relations = num_roots; // 设置执行状态中的根结果关系数量
	
	        /* 锁定非叶结果关系 */
	        foreach(l, plannedstmt->nonleafResultRelations)
	        {
	            Index       resultRelIndex = lfirst_int(l);
	
	            /* 对不在根结果关系列表中的关系加锁 */
	            if (!list_member_int(plannedstmt->rootResultRelations,
	                                 resultRelIndex))
	                LockRelationOid(getrelid(resultRelIndex, rangeTable),
	                                RowExclusiveLock);
	        }
	    }
	}
	else
	{
	    /* 如果没有结果关系,则相应地设置状态 */
	    estate->es_result_relations = NULL;
	    estate->es_num_result_relations = 0;
	    estate->es_result_relation_info = NULL;
	    estate->es_root_result_relations = NULL;
	    estate->es_num_root_result_relations = 0;
	}
	
	/*
	 * 类似地,在初始化计划树之前,我们需要锁定被选为 FOR [KEY] UPDATE/SHARE 的关系,
	 * 否则可能会有锁升级的风险。同时,构建 ExecRowMark 列表。
	 * 这里忽略了分区的子表(因为 isParent=true),它们将被第一个引用它们的 Append 或 MergeAppend 节点锁定。
	 * (注意,对应于分区子表的 RowMarks 与其它的在同一个列表中,即 plannedstmt->rowMarks。)
	 */
	estate->es_rowMarks = NIL; // 初始化执行状态中的行标记列表为 NIL
	foreach(l, plannedstmt->rowMarks) // 遍历行标记
	{
		PlanRowMark *rc = (PlanRowMark *) lfirst(l); // 获取当前行标记
		Oid			relid; // 声明关系的 OID
		Relation	relation; // 声明关系
		ExecRowMark *erm; // 声明执行行标记

		/* 忽略父级行标记,因为它们在运行时不相关 */
		if (rc->isParent)
			continue;

		/* 获取关系的 OID(如果是子查询则为 InvalidOid) */
		relid = getrelid(rc->rti, rangeTable);

		/* 根据标记类型锁定关系 */
		switch (rc->markType)
		{
			case ROW_MARK_EXCLUSIVE:
			case ROW_MARK_NOKEYEXCLUSIVE:
			case ROW_MARK_SHARE:
			case ROW_MARK_KEYSHARE:
				relation = heap_open(relid, RowShareLock); // 使用 RowShareLock 打开关系
				break;
			case ROW_MARK_REFERENCE:
				relation = heap_open(relid, AccessShareLock); // 使用 AccessShareLock 打开关系
				break;
			case ROW_MARK_COPY:
				/* 不需要访问物理表 */
				relation = NULL;
				break;
			default:
				elog(ERROR, "unrecognized markType: %d", rc->markType); // 无法识别的 markType
				relation = NULL;	/* 保持编译器安静 */
				break;
		}

		/* 检查关系是否是合法的标记目标 */
		if (relation)
			CheckValidRowMarkRel(relation, rc->markType);

		erm = (ExecRowMark *) palloc(sizeof(ExecRowMark)); // 分配执行行标记内存
		erm->relation = relation; // 设置关系
		erm->relid = relid; // 设置关系 ID
		erm->rti = rc->rti; // 设置关系表索引
		erm->prti = rc->prti; // 设置父关系表索引
		erm->rowmarkId = rc->rowmarkId; // 设置行标记 ID
		erm->markType = rc->markType; // 设置标记类型
		erm->strength = rc->strength; // 设置强度
		erm->waitPolicy = rc->waitPolicy; // 设置等待策略
		erm->ermActive = false; // 设置为非激活状态
		ItemPointerSetInvalid(&(erm->curCtid)); // 设置当前 TID 为无效
		erm->ermExtra = NULL; // 设置额外信息为 NULL
		estate->es_rowMarks = lappend(estate->es_rowMarks, erm); // 将执行行标记添加到列表中
	}

	/* 初始化执行器的元组表为空 */
	estate->es_tupleTable = NIL; // 设置元组表为 NIL
	estate->es_trig_tuple_slot = NULL; // 设置触发器元组槽为 NULL
	estate->es_trig_oldtup_slot = NULL; // 设置旧触发器元组槽为 NULL
	estate->es_trig_newtup_slot = NULL

	/* 标记 EvalPlanQual 为非活动状态 */
	estate->es_epqTuple = NULL; // 设置 EPQ 元组为 NULL
	estate->es_epqTupleSet = NULL; // 设置 EPQ 元组集为 NULL
	estate->es_epqScanDone = NULL; // 设置 EPQ 扫描完成标志为 NULL
	
	/*
	 * 为每个子计划初始化私有状态信息。在执行主查询树的 ExecInitNode 之前必须做这个,
	 * 因为 ExecInitSubPlan 期望能找到这些条目。
	 */
	Assert(estate->es_subplanstates == NIL); // 断言确保子计划状态列表为空
	i = 1;						/* 子计划索引从 1 开始 */
	foreach(l, plannedstmt->subplans) // 遍历所有子计划
	{
		Plan	   *subplan = (Plan *) lfirst(l); // 获取当前子计划
		PlanState  *subplanstate; // 声明子计划状态
		int			sp_eflags; // 声明子计划的执行标志
	
		/*
		 * 子计划不需要执行向后扫描或标记/恢复。如果是无参数子计划(非 initplan),
		 * 我们建议它准备好高效处理 REWIND;否则就没必要了。
		 */
		sp_eflags = eflags
			& (EXEC_FLAG_EXPLAIN_ONLY | EXEC_FLAG_WITH_NO_DATA); // 设置子计划执行标志
		if (bms_is_member(i, plannedstmt->rewindPlanIDs))
			sp_eflags |= EXEC_FLAG_REWIND; // 如果子计划需要 REWIND,添加标志
	
		subplanstate = ExecInitNode(subplan, estate, sp_eflags); // 初始化子计划状态
	
		estate->es_subplanstates = lappend(estate->es_subplanstates,
										   subplanstate); // 将子计划状态添加到列表
	
		i++; // 子计划索引递增
	}
	
	/*
	 * 为查询树中的所有节点初始化私有状态信息。这将打开文件、分配存储空间,
	 * 并准备开始处理元组。
	 */
	planstate = ExecInitNode(plan, estate, eflags); // 初始化主查询树的节点
	
	/*
	 * 获取描述返回元组类型的元组描述符。
	 */
	tupType = ExecGetResultType(planstate); // 获取结果类型的元组描述符
	
	/*
	 * 如有必要,初始化垃圾过滤器。如果顶层目标列表中有任何垃圾属性,
	 * SELECT 查询就需要一个过滤器。
	 */
	if (operation == CMD_SELECT)
	{
		bool		junk_filter_needed = false; // 声明是否需要垃圾过滤器的标志
		ListCell   *tlist; // 声明目标列表的迭代器
	
		foreach(tlist, plan->targetlist) // 遍历目标列表
		{
			TargetEntry *tle = (TargetEntry *) lfirst(tlist); // 获取当前目标项
	
			if (tle->resjunk) // 如果是垃圾属性
			{
				junk_filter_needed = true; // 标记需要垃圾过滤器
				break;
			}
		}
	
		if (junk_filter_needed) // 如果需要垃圾过滤器
		{
			JunkFilter *j; // 声明垃圾过滤器
	
			j = ExecInitJunkFilter(planstate->plan->targetlist,
								   tupType->tdhasoid,
								   ExecInitExtraTupleSlot(estate)); // 初始化垃圾过滤器
			estate->es_junkFilter = j; // 设置执行状态中的垃圾过滤器
			/* 期望返回清理后的元组类型 */
			tupType = j->jf_cleanTupType; // 设置清理后的元组类型
		}
	}
	
	queryDesc->tupDesc = tupType; // 设置查询描述的元组描述符
	queryDesc->planstate = planstate; // 设置查询描述的计划状态
}

代码段解释

PlanRowMark *rc = (PlanRowMark *) lfirst(l); // 获取当前行标记

  这行代码的作用是从一个链表结构中提取出当前遍历到的元素,并将其转换为 PlanRowMark 类型的指针。这里,PlanRowMark 是一个结构体,用于存储与特定数据库表或查询中的行相关的标记信息。这种标记通常用于数据库查询计划中,以指示对特定行的特定操作或限制。

举个例子说明

  假设你正在执行一个数据库查询,该查询包括对多个表的操作,并且一些操作需要特别标记(例如,更新或删除某些特定行时需要加锁)。在这种情况下,PlanRowMark 结构体将用于存储这些操作的相关信息。
  例如,你有一个查询计划,它包含对两个表的操作:表 A 和表 B。在这个计划中,你想对表 A 中的某些行加上“FOR UPDATE”锁,而对表 B 中的某些行加上“FOR SHARE”锁。这样的锁定操作可以防止其他事务在当前事务完成之前修改或删除这些行。
  在执行这个计划的初始化过程中,会创建一个包含 PlanRowMark 元素的列表,每个元素代表一个需要特殊处理的表及其行。当遍历到这个列表中的一个元素时,这行代码就会从列表中提取出一个 PlanRowMark 结构体,其中包含了如何处理与表 A 或表 B 相关行的信息。例如,对于表 A 的行,这个结构体可能会指示这些行应该被加上“FOR UPDATE”锁。


ExecInitNode 函数

  ExecInitNode 函数是数据库查询执行过程中的一个关键函数用于递归初始化查询计划树中的所有节点。它根据不同类型的节点(如扫描节点连接节点聚合节点等)执行相应的初始化操作。
  ExecInitNode 函数对查询计划中的每个节点进行递归初始化。它根据节点类型(如结果节点扫描节点连接节点等)调用相应的初始化函数。这些函数为每种类型的节点设置特定的执行状态和行为。

  • 节点初始化对于每种节点类型(如结果节点、聚合节点、扫描节点等),函数通过调用特定的初始化函数(如 ExecInitResultExecInitSeqScan)来准备节点的执行。这包括为节点设置运行时所需的资源、数据结构和状态。
  • 检查栈深度为了避免栈溢出,函数在开始节点的初始化之前检查栈深度。
  • 初始化子计划对于包含子计划的节点(如子查询),函数初始化这些子计划,并将它们的状态连接到主节点的状态上。
  • 设置性能监控如果启用了性能监控,函数将为节点分配监控工具。

  通过这种方式,ExecInitNode 确保查询计划中的每个节点都被正确初始化,并且具备执行查询所需的全部信息和资源。这个过程是查询执行的基础,它确保了当查询被执行时,每个节点都能按照预期的逻辑和顺序工作,从而有效地完成整个查询任务。函数源码如下所示:(路径:src\backend\executor\execProcnode.c

/* ------------------------------------------------------------------------
 *      ExecInitNode
 *
 *      递归初始化查询计划树的根节点及其子节点。
 *
 *      输入参数:
 *        'node' 是由查询规划器产生的计划树中的当前节点
 *        'estate' 是计划树的共享执行状态
 *        'eflags' 是在 executor.h 中描述的标志位的位或运算结果
 *
 *      返回一个与给定计划节点对应的 PlanState 节点。
 * ------------------------------------------------------------------------
 */
PlanState *
ExecInitNode(Plan *node, EState *estate, int eflags)
{
    PlanState  *result; // 声明返回的 PlanState 结构体
    List       *subps; // 子计划列表
    ListCell   *l; // 用于遍历列表的临时变量

    /* 若节点为 NULL,则到达叶子节点的末端,无需进一步处理 */
	if (node == NULL)
		return NULL;
		
	/* 确保有足够的栈空间可用。需要在这里以及 ExecProcNode()(通过 ExecProcNodeFirst())中检查,以确保在初始化节点树时不会超过栈深度。 */
	check_stack_depth();
	
	/* 根据节点类型选择相应的初始化函数 */
	switch (nodeTag(node))
	{
	    /* 控制节点 */
	    case T_Result:
	        result = (PlanState *) ExecInitResult((Result *) node, estate, eflags);
	        break;
	    /* ...其他节点类型的处理... */
	
	    /* 扫描节点 */
	    case T_SeqScan:
	        result = (PlanState *) ExecInitSeqScan((SeqScan *) node, estate, eflags);
	        break;
	    /* ...其他节点类型的处理... */
	
	    /* 连接节点 */
	    case T_NestLoop:
	        result = (PlanState *) ExecInitNestLoop((NestLoop *) node, estate, eflags);
	        break;
	    /* ...其他节点类型的处理... */
	
	    /* 材料化节点 */
	    case T_Material:
	        result = (PlanState *) ExecInitMaterial((Material *) node, estate, eflags);
	        break;
	    /* ...其他节点类型的处理... */
	
	    /* 默认情况下,报告无法识别的节点类型错误 */
	    default:
	        elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
	        result = NULL;  /* 避免编译器警告 */
	        break;
	}
	
	/* 添加一个包装器,用于在第一次执行时检查栈深度 */
	result->ExecProcNodeReal = result->ExecProcNode;
	result->ExecProcNode = ExecProcNodeFirst;
	
	/* 初始化节点中存在的任何初始化计划(initPlans)。规划器将它们放在一个单独的列表中。 */
	subps = NIL;
	foreach(l, node->initPlan)
	{
	    SubPlan    *subplan = (SubPlan *) lfirst(l);
	    SubPlanState *sstate;
	
	    Assert(IsA(subplan, SubPlan));
	    sstate = ExecInitSubPlan(subplan, result);
	    subps = lappend(subps, sstate);
	}
	result->initPlan = subps;
	
	/* 如果请求,为该节点设置监控工具 */
	if (estate->es_instrument)
	    result->instrument = InstrAlloc(1, estate->es_instrument);
	
	return result;
}

总结

  ExecutorStart 在查询执行阶段之前调用,作为整个执行过程的入口点。它负责触发 InitPlan,然后继续进行查询执行的后续步骤。在 ExecutorStart 中,首先调用 InitPlan 来完成查询计划的初始化工作,然后设置好执行环境,准备好所有必要的数据结构和状态。这包括分配内存准备执行环境等。
  InitPlan 是数据库查询执行过程中的一个重要函数,主要负责初始化查询计划(Query Plan)。在执行数据库查询之前InitPlan 函数设置和准备了所有必要的执行状态,包括但不限于权限检查结果关系的初始化锁定子计划的初始化。此外,它还负责为各种节点类型(如扫描节点连接节点等)设置相应的执行状态和行为。简而言之,InitPlan 确保了查询计划的各个组成部分都准备就绪,可以进行高效的查询执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/326287.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

最新使用宝塔反代openai官方API接口搭建详细教程及502 Bad Gateway错误问题解决

一、前言 宝塔反代openai官方API接口详细教程,实现国内使用ChatGPT502 Bad Gateway问题解决, 此方法最简单快捷,没有复杂步骤,不容易出错,即最简单,零代码、零部署的方法。 二、实现前提 一台海外VPS服务…

SpringBoot教程(七) | SpringBoot解决跨域问题

SpringBoot教程(七) | SpringBoot解决跨域问题 上篇文章我们介绍了SpringBoot的拦截器的写法,其中有一个比较重要的步骤,就是把我们写好的拦截器注册到Spring的一个配置类中,这个类是实现了WebMvcConfigurer 接口,这个类很重要&a…

PRU pruss, rproc_pru和prueth uboot源码分析

概述 首先看一下PRU_ICSSG的功能框图,对于AM64来说,包含两个PRU_ICSSG模块。每个PRU_ICSSG共包含两个slice。 每个PRU core自己Local Instruction RAM, 容量不同。 PRU 内核执行任何指令之前,…

NXP-RT1176开发(一)——环境搭建(MCUXpressoIDE/VSCode)

目录 1. 安装IDE 1.1 官方开发的IDE软件 1.2 Config工具下载 1.3 说明(需先有SDK) 2. 下载SDK 3. VScode环境下编译 3.1 安装插件 3.2 确保本地有交叉编译工具链和CMAKE 3.3 加载本地SDK 3.4 导入例程编译 1. 安装IDE 该处理器编译规则可以MDK…

科普丨数据泄露防护DLP是什么(DLP数据泄露防护系统推荐)

在数字化时代,数据泄露的风险日益严峻,给企业和组织带来了巨大的威胁。为了应对这一挑战,数据泄露防护DLP(Data Loss Prevention)应运而生,成为保护数据安全的重要手段之一。 一、DLP数据泄露防护系统是什么…

高级RAG技术、以及算法实现

知识库地址:Advanced RAG techniques 检索增强生成(Retrieval Augmented Generation, RAG)为大语言模型(Large Language Model, LLM)提供了一种机制,通过从数据源检索到的信息为其生成的答案提供依据。简而…

C语言--质数算法和最大公约数算法

文章目录 1.在C语言中,判断质数的常见算法有以下几种:1.1.试除法(暴力算法):1.2.优化试除法:1.3.埃拉托色尼筛法:1.4.米勒-拉宾素性检验:1.5.线性筛法:1.6.费马小定理&am…

【前端发版】vue前端发版 步骤

1、 提交代码 代码合并通过之后到deb分支 2、git checkout 切换到dev分支上 运行起来看看自己刚刚提交的代码有没有错误 3、拉取最新代码 git pull 3、yarn run build 4、打包好的文件叫dist 重新命名为服务器里替换包名 5、登录文件传输 开始替换 替换的过程中 首先删除备…

如何一台电脑操作两个adb 设备

1.首先使用 adb devies 命令 2.然后使用 adb -s 上面的返回的id号 shell 进入对应的开发板

优化您的服务请求,增强用户体验和服务交付

您的服务请求模板是否像一个复杂的迷宫,给您的团队带来延误和困惑?您的技术人员是否厌倦了为了解最终用户的需求而与他们来回奔波?强大且可定制的请求模板可能正是您所需要的! 服务交付团队(尤其是 IT)的…

ubuntu服务器安装Slurm

相关内容,网上不少,这里记录一下自己出现的问题和解决方法,采用的是Ubuntu22.04,方法可以参考知乎上面这篇文章Ubuntu服务器安装配置slurm,整个安装过程没有什么问题,主要步骤贴在这里但在使用过程中&#…

Vue3响应式系统(一)

一、副作用函数。 副作用函数指的是会产生副作用的函数。例如:effect函数会直接或间接影响其他函数的执行,这时我们便说effect函数产生了副作用。 function effect(){document.body.innerText hello vue3 } 再例如: //全局变量let val 2f…

【MATLAB】Linux版本 高分辨率屏 调整显示缩放

0 引言 安装了linux版本的MATLAB R2023b之后,发现工具栏字体很小不方便使用,所以上网找到了MATLAB论坛上某位大佬的教程:参考链接,放在这里供各位参考 。 1 环境 这里注明我的matlab安装环境仅供参考,未在其他环境下…

Java基础 - 黑马

我是南城余!阿里云开发者平台专家博士证书获得者! 欢迎关注我的博客!一同成长! 一名从事运维开发的worker,记录分享学习。 专注于AI,运维开发,windows Linux 系统领域的分享! 知…

msvcp140.dll丢失都有什么办法可以解决呢?分享几种解决办法

msvcp140.dll是Windows操作系统中的一个重要动态链接库文件,它与许多软件的正常运行密切相关。当系统或软件无法找到或访问到该dll文件时,就会出现msvcp140.dll丢失的问题。这可能导致某些软件无法启动或崩溃,给用户带来不便。为了解决这个问…

Proxy的使用方法和13种拦截操作

前言 proxy是ES6新推出的方法,功能很强大。属于元编程,也就是修改js本身的一些东西。可以对数组,对象,函数等引用类型的对象进行一些复杂的操作。 其中,大部分人应该最熟悉的莫过于vue3中使用proxy替换了defineProperty,而且还实现了本身defineProperty不能实现的一些东西。 …

GO——gin中间件和路由

中间件 参考:https://learnku.com/articles/66234 结构 中间件是函数中间件函数被放在调用链上调用链的末尾是路由path对应的函数 执行过程 net/http包调用到gin的serverHTTP 参考:go/pkg/mod/github.com/gin-gonic/ginv1.7.7/gin.go:506 通过path找到…

FlinkSQL【分组聚合-多维分析-性能调优】应用实例分析

FlinkSQL处理如下实时数据需求: 实时聚合不同 类型/账号/发布时间 的各个指标数据,比如:初始化/初始化后删除/初始化后取消/推送/成功/失败 的指标数据。要求实时产出指标数据,数据源是mysql cdc binlog数据。 代码实例 --SET t…

【.net core】yisha框架,bootstrap-table组件增加固定列功能

需要引入 bootstrap-table-fixed-columns.css和bootstrap-table-fixed-columns.js文件 文件代码: bootstrap-table-fixed-columns.css样式文件代码 .fixed-table-header-columns, .fixed-table-body-columns {position: absolute;background-color: #fff;displa…