【PostgreSQL内核学习 —— (WindowAgg(三))】
WindowAgg
- set_subquery_pathlist 部分函数解读
- check_and_push_window_quals 函数
- find_window_run_conditions 函数
- 执行案例
- 总结
- 计划器模块(set_plan_refs函数)
- set_windowagg_runcondition_references 函数
- 执行案例
- fix_windowagg_condition_expr 函数
- fix_windowagg_condition_expr_mutator 函数
- 执行案例
声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 postgresql-15.0 的开源代码和《PostgresSQL数据库内核分析》一书
在【PostgreSQL内核学习 —— (WindowAgg(一))】中,我们介绍了窗口函数以及窗口聚合的核心计算过程。其次,在【PostgreSQL内核学习 —— (WindowAgg(二))】一文中介绍了WindowAgg
算子的具体实现逻辑。本文将进一步来学习一下WindowAgg
中的条件下推的优化逻辑。
set_subquery_pathlist 部分函数解读
/** 如果子查询关系上有附加的限制条件,考虑将它们推送到子查询中,作为子查询的 WHERE 或 HAVING 过滤条件。* 这种转化很有用,因为它可能帮助我们为子查询生成更好的执行计划,避免首先评估所有子查询的输出行再过滤。** 有几种情况是不能推送限制条件的。涉及子查询的限制条件由 subquery_is_pushdown_safe() 检查。* 对单个条件的检查由 qual_is_pushdown_safe() 进行。此外,我们不希望推送伪常量(pseudoconstant)条件,* 这种情况下,最好将控制节点放在子查询上方。** 未被推送下去的条件会作为 SubqueryScan 节点的 qpquals 被评估。** XXX 是否有一些情况我们应该决定不推送可推送的条件,因为它可能导致更差的执行计划?*/
if (rel->baserestrictinfo != NIL && // 如果有限制条件并且可以安全推送subquery_is_pushdown_safe(subquery, subquery, &safetyInfo)) // 检查子查询是否支持限制条件的推送
{// 可以考虑推送单个条件List *upperrestrictlist = NIL; // 存储未推送的限制条件ListCell *l;// 遍历所有的限制条件foreach(l, rel->baserestrictinfo){RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); // 当前限制条件Node *clause = (Node *) rinfo->clause; // 限制条件的表达式if (rinfo->pseudoconstant) // 如果是伪常量条件{upperrestrictlist = lappend(upperrestrictlist, rinfo); // 不推送,保留在父查询中continue;}// 检查当前条件是否可以推送到子查询中switch (qual_is_pushdown_safe(subquery, rti, rinfo, &safetyInfo)){case PUSHDOWN_SAFE:// 条件安全可以推送subquery_push_qual(subquery, rte, rti, clause); // 将条件推送到子查询break;case PUSHDOWN_WINDOWCLAUSE_RUNCOND:/** 如果条件涉及窗口函数,并且不能推送到子查询中,* 检查条件是否可以作为 WindowAgg 的运行条件。*/if (!subquery->hasWindowFuncs || // 子查询没有窗口函数,或者该条件不是合适的窗口运行条件check_and_push_window_quals(subquery, rte, rti, clause, &run_cond_attrs)){// 子查询没有窗口函数,或者条件不适合窗口运行条件,或者该条件是合适的窗口条件,但需要保留在上层查询upperrestrictlist = lappend(upperrestrictlist, rinfo);}break;case PUSHDOWN_UNSAFE:// 条件不安全,不能推送upperrestrictlist = lappend(upperrestrictlist, rinfo); // 保留在父查询中break;}}// 更新查询的限制条件rel->baserestrictinfo = upperrestrictlist;/* 不必重新计算 baserestrict_min_security */
}
代码逻辑总结:
这段代码的核心任务是评估是否能够将某些限制条件推送到子查询中,以便优化执行计划。推送的条件会直接成为子查询的过滤条件,从而避免父查询在处理子查询结果时进行重复过滤。具体流程如下:
-
判断是否可以推送条件:
- 先检查子查询是否允许推送限制条件(
subquery_is_pushdown_safe
)。 - 如果子查询允许推送,则会遍历所有附加在外部查询的限制条件(
rel->baserestrictinfo
)。
- 先检查子查询是否允许推送限制条件(
-
处理每个限制条件:
- 如果限制条件是伪常量,它就不能被推送下去,因为它不依赖于实际数据,可以直接在外层查询中处理。
- 对于其他条件,使用
qual_is_pushdown_safe
来判断该条件是否可以安全地推送到子查询中。
-
推送限制条件:
- 如果条件安全推送,使用
subquery_push_qual
将条件推送到子查询。 - 如果条件涉及窗口函数并且不能推送,它可能会被保留在外层查询中(例如作为窗口聚合的运行条件)。
- 对于无法推送的条件,它们将被保留在父查询中。
- 如果条件安全推送,使用
我们重点聚焦函数 check_and_push_window_quals
,该函数在查询优化中起着至关重要的作用,特别是在涉及窗口函数的情况下。为了更好地理解这个函数的作用和它在整个查询优化中的地位,下面将从它的功能、参数、调用背景和优化目标等方面进行详细分析。
check_and_push_window_quals 函数
check_and_push_window_quals
函数的作用是在查询优化过程中判断某个过滤条件(clause
)是否可以下推到窗口聚合节点(WindowAgg
)的运行条件(runCondition
)。如果条件可以下推,执行时可以跳过一些不必要的工作,进而优化查询的性能。
check_and_push_window_quals
函数检查给定的条件是否可以作为窗口函数的“运行条件”(runCondition
)被下推至子查询中的窗口聚合节点。如果可以下推,那么这些条件会被推到窗口聚合的执行过程中,用来优化执行过程,避免不必要的计算。
具体来说,它首先确认条件是一个操作符表达式(OpExpr
)且符合一定的条件,例如操作符必须是严格的(即在条件成立时可以安全地停止评估窗口函数)。然后,代码会检查操作符的左右两边是否涉及到子查询中的窗口函数,如果涉及到,就会尝试通过find_window_run_conditions
函数检查该条件是否适合作为运行条件。最终,如果条件符合要求,它会将该条件下推并返回是否保留原始条件。如果条件不适合下推,或者无法找到合适的窗口函数,代码会保留原始条件,确保执行计划的正确性。函数源码如下所示:(路径:postgresql-15.10\src\backend\optimizer\path\allpaths.c
)
参数作用总结
- subquery:提供关于子查询的详细信息,尤其是目标列信息,帮助解析窗口函数。
- rte:表示当前的表项,帮助确定条件是否可以与窗口函数关联。
- rti:用于标识rte在子查询中的位置,帮助定位表项。
- clause:是需要检查是否可以下推的过滤条件,通常是操作符表达式。
- run_cond_attrs:记录下推成功的窗口函数条件的目标列索引,优化窗口聚合操作。
static bool
check_and_push_window_quals(Query *subquery, RangeTblEntry *rte, Index rti,Node *clause, Bitmapset **run_cond_attrs)
{OpExpr *opexpr = (OpExpr *) clause; // 将输入的条件转换为操作符表达式 (OpExpr)bool keep_original = true; // 标记是否保留原始的过滤条件Var *var1; // 定义变量,用于存储操作符表达式左侧的变量Var *var2; // 定义变量,用于存储操作符表达式右侧的变量/* 我们只能处理有两个操作数的OpExpr */if (!IsA(opexpr, OpExpr)) // 检查输入条件是否为操作符表达式return true; // 如果不是操作符表达式,则返回true,无法下推该条件if (list_length(opexpr->args) != 2) // 确保操作符表达式有两个操作数return true; // 如果操作符的操作数不是两个,则返回true,无法下推/** 当前,这个优化仅限于严格的OpExpr。原因是,在执行时,一旦运行条件变为假,* 我们停止计算窗口函数。为了避免留下过时的窗口函数结果值,我们将它们设置为NULL。* 只有严格的OpExpr才能确保在WindowAgg节点中正确地过滤掉带有NULL值的元组。*/set_opfuncid(opexpr); // 设置操作符的函数IDif (!func_strict(opexpr->opfuncid)) // 检查操作符是否为严格运算符return true; // 如果操作符不是严格运算符,返回true,无法下推/** 检查是否有引用子查询中窗口函数的变量。* 如果找到,我们将调用find_window_run_conditions()来检查该'opexpr'是否可以* 作为运行条件的一部分。*//* 检查操作符表达式的左侧 */var1 = linitial(opexpr->args); // 获取操作符表达式的左侧操作数if (IsA(var1, Var) && var1->varattno > 0) // 检查左侧操作数是否是窗口函数引用的变量{TargetEntry *tle = list_nth(subquery->targetList, var1->varattno - 1); // 获取对应的目标列WindowFunc *wfunc = (WindowFunc *) tle->expr; // 获取窗口函数/* 使用find_window_run_conditions检查该条件是否可以作为窗口聚合的运行条件 */if (find_window_run_conditions(subquery, rte, rti, tle->resno, wfunc,opexpr, true, &keep_original,run_cond_attrs))return keep_original; // 如果可以作为运行条件,返回是否保留原始条件}/* 检查操作符表达式的右侧 */var2 = lsecond(opexpr->args); // 获取操作符表达式的右侧操作数if (IsA(var2, Var) && var2->varattno > 0) // 检查右侧操作数是否是窗口函数引用的变量{TargetEntry *tle = list_nth(subquery->targetList, var2->varattno - 1); // 获取对应的目标列WindowFunc *wfunc = (WindowFunc *) tle->expr; // 获取窗口函数/* 使用find_window_run_conditions检查该条件是否可以作为窗口聚合的运行条件 */if (find_window_run_conditions(subquery, rte, rti, tle->resno, wfunc,opexpr, false, &keep_original,run_cond_attrs))return keep_original; // 如果可以作为运行条件,返回是否保留原始条件}return true; // 如果没有条件可以下推,返回true,保留原始条件
}
具体案例说明
假设我们有以下SQL查询:
SELECT department,AVG(salary) OVER (PARTITION BY department)
FROM employees
WHERE salary > 50000;
这里,employees
是一个包含多个字段的表,包括department
和salary
。该查询通过AVG(salary)
计算每个department
的平均薪资,并且只考虑salary > 50000
的记录。我们现在要理解check_and_push_window_quals
函数在此查询中的执行逻辑。
- 初始条件
我们需要将查询中的WHERE
条件(即salary > 50000
)尝试下推到窗口函数(AVG(salary) OVER (PARTITION BY department))
中,来优化查询性能。如果条件可以下推,PostgreSQL
将避免在窗口函数计算过程中返回不符合条件的行,从而减少不必要的计算。
- 函数执行逻辑
-
步骤1:检查clause类型
clause
是salary > 50000
,它是一个操作符表达式(OpExpr
),因此check_and_push_window_quals
会继续处理。
-
步骤2:确认操作符类型
- 该
clause
会被解析为一个OpExpr
,我们通过set_opfuncid
和func_strict
检查操作符是否严格(strict
)。在此例中,>
是一个严格操作符,因此可以进行进一步的优化。
- 该
-
步骤3:处理操作符的两侧(左侧和右侧)
clause
是一个二元操作符表达式:salary > 50000
。- 左侧操作数:
salary
是一个Var
类型的字段,它是一个表中的列。PostgreSQL
通过subquery->targetList
查找salary
的目标列(Var
)。 - 右侧操作数:常量
50000
是一个常量,不需要进一步处理。
- 左侧操作数:
-
步骤4:检查是否有窗口函数
salary
作为窗口函数的输入列之一,因此我们需要查看是否有窗口函数依赖于它。如果该列被窗口函数引用,check_and_push_window_quals
会进一步检查是否可以将条件salary > 50000
下推到窗口函数。- 在这种情况下,
AVG(salary) OVER (PARTITION BY department)
正是一个窗口函数,salary
列被窗口函数使用,因此我们调用find_window_run_conditions
来检查是否可以将salary > 50000
作为窗口函数的运行条件(run condition
)。
-
步骤5:调用find_window_run_conditions
- 通过
find_window_run_conditions
,我们检查该条件是否可以作为AVG(salary)
窗口函数的运行条件(run condition
)。如果可以,那么PostgreSQL
将优化该查询,窗口函数只计算符合salary > 50000
条件的行,而不必返回所有记录,再进行筛选。 - 如果该条件可以下推,
run_cond_attrs
会被更新,标记出哪些目标列(如salary
)需要作为窗口函数的运行条件。
- 通过
-
步骤6:返回结果
- 如果
salary > 50000
条件被成功下推到窗口函数中,check_and_push_window_quals
函数将返回false
,表示可以忽略原始的WHERE
条件,因为它已经被包含在窗口函数的运行条件中。 - 否则,
true
会被返回,表示需要保留原始的WHERE
条件,并且窗口函数的执行依然基于完整的查询结果。
- 如果
find_window_run_conditions 函数
find_window_run_conditions
函数的作用是确定是否可以利用操作符表达式(OpExpr
)来短路窗口函数的执行,以提高查询效率。具体来说,它检查窗口函数的单调性,如果窗口函数的输出是单调递增或递减的,则可以利用这个特性在外层查询中利用WHERE
子句的过滤条件来提前停止窗口函数的计算。例如,如果一个ROW_NUMBER()
窗口函数输出的值是递增的,并且外部查询要求只返回ROW_NUMBER() <= 10
的结果,那么窗口函数的计算可以在ROW_NUMBER()
达到11时停止,从而避免对后续行进行不必要的计算。函数源码如下所示:(路径:postgresql-15.10\src\backend\optimizer\path\allpaths.c
)
/** find_window_run_conditions* 确定 'wfunc' 是否真的是一个窗口函数,并调用其支持函数来确定该函数的单调性属性。* 然后检查 'opexpr' 是否能用于短路执行。如果可以,它将帮助跳过不必要的计算。*/
static bool
find_window_run_conditions(Query *subquery, RangeTblEntry *rte, Index rti,AttrNumber attno, WindowFunc *wfunc, OpExpr *opexpr,bool wfunc_left, bool *keep_original,Bitmapset **run_cond_attrs)
{Oid prosupport; // 存储窗口函数的支持函数的 OIDExpr *otherexpr; // 存储操作符表达式中的另一个表达式SupportRequestWFuncMonotonic req; // 存储单调性请求SupportRequestWFuncMonotonic *res; // 存储单调性结果WindowClause *wclause; // 存储窗口子句List *opinfos; // 存储操作符的详细信息列表OpExpr *runopexpr; // 存储要用作运行条件的操作符表达式Oid runoperator; // 存储运行条件的操作符ListCell *lc; // 用于遍历操作符信息的列表单元*keep_original = true; // 默认为保留原始条件// 如果窗口函数是一个类型转换表达式(RelabelType),递归到窗口函数本身while (IsA(wfunc, RelabelType))wfunc = (WindowFunc *) ((RelabelType *) wfunc)->arg;// 如果不是窗口函数,则返回 falseif (!IsA(wfunc, WindowFunc))return false;// 如果窗口函数中包含子查询,则无法优化,返回 falseif (contain_subplans((Node *) wfunc))return false;// 获取窗口函数的支持函数 OIDprosupport = get_func_support(wfunc->winfnoid);// 如果窗口函数没有支持函数,返回 falseif (!OidIsValid(prosupport))return false;// 获取操作符表达式的另一侧的表达式(左侧或右侧)if (wfunc_left)otherexpr = lsecond(opexpr->args); // 获取右侧表达式elseotherexpr = linitial(opexpr->args); // 获取左侧表达式// 要比较的值在窗口分区的评估过程中必须保持不变if (!is_pseudo_constant_clause((Node *) otherexpr))return false;// 获取与窗口函数关联的窗口子句wclause = (WindowClause *) list_nth(subquery->windowClause, wfunc->winref - 1);// 设置单调性请求参数req.type = T_SupportRequestWFuncMonotonic;req.window_func = wfunc;req.window_clause = wclause;// 调用窗口函数的支持函数来获取单调性属性res = (SupportRequestWFuncMonotonic *) DatumGetPointer(OidFunctionCall1(prosupport, PointerGetDatum(&req)));// 如果窗口函数的单调性不是单调递增或单调递减,返回 falseif (res == NULL || res->monotonic == MONOTONICFUNC_NONE)return false;runopexpr = NULL; // 初始化运行条件表达式为空runoperator = InvalidOid; // 初始化运行条件操作符为空opinfos = get_op_btree_interpretation(opexpr->opno); // 获取操作符的详细信息// 遍历操作符信息foreach(lc, opinfos){OpBtreeInterpretation *opinfo = (OpBtreeInterpretation *) lfirst(lc);int strategy = opinfo->strategy; // 获取操作符策略// 处理 < / <= 操作符if (strategy == BTLessStrategyNumber || strategy == BTLessEqualStrategyNumber){// 如果是单调递增函数,支持 <wfunc> op <pseudoconst>// 如果是单调递减函数,支持 <pseudoconst> op <wfunc>if ((wfunc_left && (res->monotonic & MONOTONICFUNC_INCREASING)) ||(!wfunc_left && (res->monotonic & MONOTONICFUNC_DECREASING))){*keep_original = false; // 不再保留原始条件runopexpr = opexpr; // 设置运行条件表达式为当前的操作符表达式runoperator = opexpr->opno; // 设置运行条件操作符}break; // 结束遍历}// 处理 > / >= 操作符else if (strategy == BTGreaterStrategyNumber || strategy == BTGreaterEqualStrategyNumber){// 单调递减函数支持 <wfunc> op <pseudoconst>// 单调递增函数支持 <pseudoconst> op <wfunc>if ((wfunc_left && (res->monotonic & MONOTONICFUNC_DECREASING)) ||(!wfunc_left && (res->monotonic & MONOTONICFUNC_INCREASING))){*keep_original = false;runopexpr = opexpr;runoperator = opexpr->opno;}break;}// 处理 = 操作符else if (strategy == BTEqualStrategyNumber){int16 newstrategy;// 如果函数既是单调递增的又是单调递减的,窗口函数的返回值在每次计算时是相同的// 在这种情况下,直接使用原始的操作符表达式作为运行条件if ((res->monotonic & MONOTONICFUNC_BOTH) == MONOTONICFUNC_BOTH){*keep_original = false;runopexpr = opexpr;runoperator = opexpr->opno;break;}// 单调递增的情况下,创建 <wfunc> <= <value> 或 <value> >= <wfunc> 的条件// 单调递减的情况下,创建 <wfunc> >= <value> 或 <value> <= <wfunc> 的条件if (res->monotonic & MONOTONICFUNC_INCREASING)newstrategy = wfunc_left ? BTLessEqualStrategyNumber : BTGreaterEqualStrategyNumber;elsenewstrategy = wfunc_left ? BTGreaterEqualStrategyNumber : BTLessEqualStrategyNumber;// 保留原始的等号条件*keep_original = true;runopexpr = opexpr;// 确定用于运行条件的操作符runoperator = get_opfamily_member(opinfo->opfamily_id, opinfo->oplefttype,opinfo->oprighttype, newstrategy);break;}}// 如果找到了有效的运行条件表达式if (runopexpr != NULL){Expr *newexpr;// 构建运行条件表达式,保持窗口函数在原位置if (wfunc_left)newexpr = make_opclause(runoperator, runopexpr->opresulttype,runopexpr->opretset, (Expr *) wfunc,otherexpr, runopexpr->opcollid,runopexpr->inputcollid);elsenewexpr = make_opclause(runoperator, runopexpr->opresulttype,runopexpr->opretset, otherexpr, (Expr *) wfunc,runopexpr->opcollid, runopexpr->inputcollid);// 将新创建的运行条件添加到窗口子句的 runCondition 中wclause->runCondition = lappend(wclause->runCondition, newexpr);// 记录该属性已经用于运行条件*run_cond_attrs = bms_add_member(*run_cond_attrs, attno - FirstLowInvalidHeapAttributeNumber);return true;}// 如果没有找到支持的操作符,返回 falsereturn false;
}
该函数的主要执行步骤包括:
- 判断窗口函数的单调性属性(递增、递减等)。
- 根据单调性属性决定是否可以应用短路条件(比如
<
或<=
)。- 生成适当的条件表达式,将其添加到窗口子句的
runCondition
中。- 更新已处理的属性集,以确保这些条件在查询执行时会被应用。
执行案例
为了详细解释 find_window_run_conditions
函数的作用,我们可以通过一个具体的例子来说明。假设有一个查询,其中包含一个窗口函数 row_number()
,并且该窗口函数用于对行号进行排序。假设有一个窗口函数的查询结构如下:
SELECT row_number() OVER (ORDER BY salary DESC) AS rn, name, salary
FROM employees
WHERE row_number() <= 10;
在这个查询中,我们使用了 row_number()
作为窗口函数,并且在外部查询的 WHERE
子句中使用了 row_number() <= 10
来过滤结果。我们的目标是利用 find_window_run_conditions
函数来判断在什么情况下可以通过对窗口函数的运行条件进行优化,避免无谓的计算。
具体执行过程(逐行解释)
假设我们调用 find_window_run_conditions
函数时,传入的参数如下:
- subquery: 表示包含窗口函数的子查询,即
SELECT row_number() OVER (ORDER BY salary DESC) AS rn, ...
。 - rte: 查询中的关系表条目(
employees
表)。 - rti: 该表在查询范围表中的索引(例如,
employees
表的索引)。 - attno: 表示我们感兴趣的属性(例如,
row_number()
函数返回的列rn
)。 - wfunc: 窗口函数结构体,包含了
row_number()
函数的信息(例如,函数ID
、窗口参考、分区、排序等)。 - opexpr: 表示操作符表达式,
row_number() <= 10
中的<=
操作符。 - wfunc_left: 指示窗口函数
row_number()
在操作符的左侧。 - keep_original: 用于指示是否保留原始的操作符条件。
- run_cond_attrs: 用于记录哪些属性(列)将用于优化条件。
逐行解释代码执行过程
- 初始化
*keep_original = true
*keep_original = true;
这行代码初始化了 keep_original
标志为 true
。这是因为默认情况下我们保留原始的操作符条件(row_number() <= 10)
。
- 处理窗口函数的类型
while (IsA(wfunc, RelabelType))wfunc = (WindowFunc *) ((RelabelType *) wfunc)->arg;
如果 wfunc
是 RelabelType
类型(这通常发生在类型转换的情况下),我们将递归地访问它的实际参数。假设这里 wfunc
是 WindowFunc
类型,因此这段代码不会执行。
- 检查
wfunc
是否是窗口函数
if (!IsA(wfunc, WindowFunc))return false;
这行代码检查 wfunc
是否是窗口函数类型。如果不是窗口函数,直接返回 false
。在我们的例子中,row_number()
窗口函数符合条件,因此继续执行。
- 检查窗口函数中是否包含子查询
if (contain_subplans((Node *) wfunc))return false;
窗口函数中如果包含子查询(例如嵌套查询),则无法优化。因此,检查 wfunc
中是否包含子查询。如果包含子查询,返回 false
。在本例中,row_number()
不包含子查询,所以继续执行。
- 获取窗口函数的支持函数
prosupport = get_func_support(wfunc->winfnoid);
这行代码获取窗口函数的支持函数。每个窗口函数可能有一个“支持函数”,用来检查该函数的单调性(即是否是递增或递减的)。对于 row_number()
函数,它应该是递增的。
注:以
row_number()
窗口函数为例,通常这个函数会在分区内生成递增的行号。因此,窗口函数的支持函数可能会返回一个标志,表示该窗口函数的结果是递增的(单调递增)。
- 检查是否存在有效的支持函数
if (!OidIsValid(prosupport))return false;
如果窗口函数没有有效的支持函数,返回 false
。如果 row_number()
的支持函数有效,继续执行。
- 获取操作符表达式的另一边的表达式
if (wfunc_left)otherexpr = lsecond(opexpr->args);
elseotherexpr = linitial(opexpr->args);
这行代码根据 wfunc_left
标志来确定操作符表达式 opexpr
的另一边的表达式。如果 wfunc_left
为 true
,则窗口函数在操作符的左边,otherexpr
是操作符的右边。如果 wfunc_left
为 false
,则窗口函数在右边,otherexpr
是操作符的左边。假设 wfunc_left
为 true
,otherexpr
将是常数 10
,因为 opexpr
中是 row_number() <= 10
。
- 检查
otherexpr
是否是伪常量
if (!is_pseudo_constant_clause((Node *) otherexpr))return false;
这行代码检查 otherexpr
是否是伪常量,即它的值在窗口分区的评估过程中不会发生变化。对于 row_number() <= 10
中的 10
,它是一个常量,因此通过此检查。
- 获取窗口函数的窗口子句
wclause = (WindowClause *) list_nth(subquery->windowClause, wfunc->winref - 1);
通过 wfunc->winref
获取与窗口函数 row_number()
相关联的窗口子句。这一行确保我们访问到包含窗口函数定义的正确窗口子句。
- 构建支持请求并调用支持函数
req.type = T_SupportRequestWFuncMonotonic;
req.window_func = wfunc;
req.window_clause = wclause;
创建支持请求结构体,指定窗口函数和窗口子句,准备调用支持函数。
res = (SupportRequestWFuncMonotonic *)DatumGetPointer(OidFunctionCall1(prosupport, PointerGetDatum(&req)));
调用支持函数来检查窗口函数的单调性。如果 row_number()
是递增的,则返回 MONOTONICFUNC_INCREASING
。
- 检查是否支持单调性
if (res == NULL || res->monotonic == MONOTONICFUNC_NONE)return false;
如果支持函数返回为空或窗口函数不具有单调性,则返回 false
。对于 row_number()
,它是递增的,因此通过此检查。
- 分析操作符的策略
runopexpr = NULL;
runoperator = InvalidOid;
opinfos = get_op_btree_interpretation(opexpr->opno);
获取操作符表达式的相关操作符信息,准备分析操作符策略。
foreach(lc, opinfos)
{OpBtreeInterpretation *opinfo = (OpBtreeInterpretation *) lfirst(lc);int strategy = opinfo->strategy;
遍历操作符的 B-tree
解释,检查操作符的策略类型(例如 <, <=, =
等)。
- 检查并处理操作符
<
和<=
if (strategy == BTLessStrategyNumber || strategy == BTLessEqualStrategyNumber)
{if ((wfunc_left && (res->monotonic & MONOTONICFUNC_INCREASING)) ||(!wfunc_left && (res->monotonic & MONOTONICFUNC_DECREASING))){*keep_original = false;runopexpr = opexpr;runoperator = opexpr->opno;}break;
}
对于 <=
操作符,如果窗口函数是递增的,且窗口函数位于操作符的左侧,则可以利用该条件来优化计算,避免不必要的处理。此时,keep_original
被设置为 false
,并且 runopexpr
和 runoperator
被设置为当前的操作符表达式。
-
其他操作符处理
处理>, >=, =
等操作符的逻辑与上述类似。针对不同的操作符策略,检查是否可以根据窗口函数的单调性来优化。 -
构建新的运行条件表达式并添加到窗口子句
if (runopexpr != NULL)
{Expr *newexpr;if (wfunc_left)newexpr = make_opclause(runoperator, runopexpr->opresulttype, runopexpr->opretset, (Expr *) wfunc, otherexpr, runopexpr->opcollid);elsenewexpr = make_opclause(runoperator, runopexpr->opresulttype, runopexpr->opretset, otherexpr, (Expr *) wfunc, runopexpr->opcollid);
}
最后,基于优化后的条件构建新的表达式并将其加入到窗口子句中,供查询执行时使用。
总结
通过上述详细的例子和逐行分析,find_window_run_conditions
函数的作用是基于窗口函数的单调性和操作符表达式(如 <=
)来决定是否可以优化窗口函数的计算,从而避免不必要的计算。通过合理的优化,查询的执行效率得到提升。
计划器模块(set_plan_refs函数)
这段代码是 PostgreSQL
计划器(Planner
)中的 WindowAgg
处理逻辑,负责调整窗口函数执行节点的运行条件,确保 WindowFuncs
(窗口函数)在 runCondition
计算时,能够正确引用已经计算出的值,而不是重复计算。同时,它还修正了窗口帧的偏移量表达式 (startOffset
和 endOffset
),以及 runCondition
和 runConditionOrig
的变量索引,使其适应优化后的执行计划,确保查询执行效率和正确性。
case T_WindowAgg: // 处理 WindowAgg 类型的计划节点
{WindowAgg *wplan = (WindowAgg *) plan; // 将 plan 转换为 WindowAgg 类型,以便访问其成员变量/** 调整 WindowAgg 的运行条件 (`runCondition`):* 1. 原始 `runCondition` 可能包含对 WindowFuncs(窗口函数)的直接引用,* 但在执行时,WindowFunc 的计算结果会存储在 scan slot(扫描槽)中。* 2. 这个调整确保 `runCondition` 直接引用 scan slot 中的结果,而不是重新计算 WindowFunc。*/wplan->runCondition = set_windowagg_runcondition_references(root,wplan->runCondition,(Plan *) wplan);// 处理 WindowAgg 节点的变量引用,确保其与上层查询计划兼容set_upper_references(root, plan, rtoffset);/** 处理窗口帧的起始 (`startOffset`) 和结束 (`endOffset`) 偏移量:* 1. `startOffset` 和 `endOffset` 可能是表达式,如 `RANGE BETWEEN INTERVAL '1 day' PRECEDING AND CURRENT ROW`。* 2. 由于它们不会引用子计划中的变量,因此可以直接使用 `fix_scan_expr` 进行调整,确保正确引用优化后的执行计划。*/wplan->startOffset = fix_scan_expr(root, wplan->startOffset, rtoffset, 1);wplan->endOffset = fix_scan_expr(root, wplan->endOffset, rtoffset, 1);/** 修正 `runCondition` 运行条件中的变量索引:* 1. `runCondition` 可能包含对查询目标列表 (target list) 的引用。* 2. `fix_scan_list` 通过 `rtoffset` 修正这些引用,以适应优化后的计划。*/wplan->runCondition = fix_scan_list(root,wplan->runCondition,rtoffset,NUM_EXEC_TLIST(plan));/** 修正 `runConditionOrig`(原始运行条件)的变量索引:* 1. `runConditionOrig` 是未优化前的 `runCondition` 版本,通常用于调试或执行回退。* 2. 这里同样调用 `fix_scan_list` 进行调整,使其适应优化后的计划。*/wplan->runConditionOrig = fix_scan_list(root,wplan->runConditionOrig,rtoffset,NUM_EXEC_TLIST(plan));
}
功能描述
-
调整
runCondition
引用- 确保
runCondition
运行时引用的是scan slot
中存储的WindowFunc
计算结果,而不是重新计算WindowFunc
,提高执行效率。
- 确保
-
修正
startOffset
和endOffset
WindowAgg
可能包含窗口帧偏移量 (frame offset expressions
),例如RANGE BETWEEN
语句中的时间偏移值。- 由于这些表达式不会包含子计划中的变量,因此使用
fix_scan_expr
进行修正。
-
处理
runCondition
和runConditionOrig
变量索引fix_scan_list
确保runCondition
和runConditionOrig
中的变量索引与优化后的查询计划一致,避免执行时变量解析错误。
set_windowagg_runcondition_references 函数
set_windowagg_runcondition_references
主要用于调整 WindowAgg
计划节点中的 runCondition
,确保 runCondition
内部的 WindowFunc
(窗口函数)不会被重复计算,而是转换为指向 plan->targetlist
(计划目标列表)中相应 WindowFunc
计算结果的 Var
(变量引用)。
这样做的目的是优化执行效率,避免 WindowFunc
在 runCondition
评估时重复计算,改为直接引用 plan->targetlist
中已经计算出的结果。函数源码如下所示:(路径:postgresql-15.10\src\backend\optimizer\plan\setrefs.c
)
/** set_windowagg_runcondition_references* 将 'runcondition' 运行条件中的 WindowFunc 引用替换为 Var,* 使其指向 'plan' 目标列表 (targetlist) 中的相应 WindowFunc 计算结果。** 这样可以避免 WindowFunc 在 'runCondition' 评估时重复计算,* 改为直接引用 'plan->targetlist',从而优化执行效率。*/
static List *
set_windowagg_runcondition_references(PlannerInfo *root,List *runcondition,Plan *plan)
{List *newlist; // 存储转换后的 runCondition 列表indexed_tlist *itlist; // 存储 'plan->targetlist' 的索引信息// 构建目标列表索引,方便后续查找 targetlist 中的变量itlist = build_tlist_index(plan->targetlist);// 处理 runCondition,将 WindowFunc 引用替换为指向 targetlist 变量的 Varnewlist = fix_windowagg_condition_expr(root, runcondition, itlist);// 释放目标列表索引内存pfree(itlist);// 返回转换后的 runConditionreturn newlist;
}
执行案例
我们通过一个具体的 SQL
查询,详细展示 set_windowagg_runcondition_references
的作用,并结合执行计划分析它如何优化 WindowAgg
节点中的 runCondition
计算方式。
- SQL 查询示例
假设我们有一个sales
表,记录了销售数据:
CREATE TABLE sales (id SERIAL PRIMARY KEY,region TEXT,amount NUMERIC
);
假设表中有如下数据:
id | region | amount |
---|---|---|
1 | East | 100 |
2 | West | 200 |
3 | East | 150 |
4 | West | 250 |
5 | East | 300 |
现在我们执行以下查询,计算每个区域内的 row_number
,并过滤掉 row_number <= 2
的记录:
SELECT id, region, amount, row_number() OVER (PARTITION BY region ORDER BY amount DESC) AS rn
FROM sales
HAVING row_number() > 2;
- PostgreSQL 查询执行计划
在HAVING
子句中,我们直接使用row_number()
,这可能导致PostgreSQL
在执行时多次计算row_number()
,引发性能问题。
初始执行计划(未优化)
WindowAgg (cost=XX..XX rows=XX width=XX)-> Sort (cost=XX..XX)Sort Key: region, amount DESC-> Seq Scan on sales (cost=XX..XX)
在此情况下:
row_number()
作为WindowFunc
需要在HAVING
过滤时重新计算,增加了计算成本。
set_windowagg_runcondition_references
作用
在set_windowagg_runcondition_references
运行后,PostgreSQL
会优化WindowAgg
节点:
- 原始的
runCondition
:
HAVING row_number() > 2
- 转换后的
runCondition
:
HAVING rn > 2
rn
直接引用targetlist(plan->targetlist)
中row_number()
的结果,避免重复计算。
优化后的执行计划
WindowAgg (cost=XX..XX rows=XX width=XX)-> Sort (cost=XX..XX)Sort Key: region, amount DESC-> Seq Scan on sales (cost=XX..XX)Filter: (rn > 2)
WindowAgg
只计算row_number()
一次,结果存入targetlist
。HAVING
直接引用rn(Var)
,避免row_number()
重新执行。
- 代码执行流程解析
当 set_windowagg_runcondition_references
执行时:
-
构建
targetlist
索引
build_tlist_index(plan->targetlist)
解析plan->targetlist
,构建一个映射,使row_number()
结果可以通过Var
引用。 -
转换
runCondition
fix_windowagg_condition_expr(root, runcondition, itlist)
遍历HAVING row_number() > 2
,将row_number()
引用替换为Var
。 -
释放
itlist
索引
pfree(itlist)
释放内存。 -
返回优化后的
runCondition
返回HAVING rn > 2
,避免row_number()
额外计算。
- 结论
在未优化的情况下,HAVING row_number() > 2
可能导致 row_number()
在 WindowAgg
计算后再次被评估。
通过 set_windowagg_runcondition_references
,PostgreSQL
只计算 row_number()
一次,然后在 HAVING
过滤时直接引用 targetlist
结果,提高查询效率。
fix_windowagg_condition_expr 函数
该函数 fix_windowagg_condition_expr
主要用于优化 WindowAgg
运行条件 (runCondition)
,避免 WindowFunc
重复计算。
它将 runcondition
中的 WindowFunc
替换为 Var
,以便引用 subplan_itlist
(即 plan->targetlist
),减少计算开销。
在 HAVING
或其他涉及 WindowFunc
计算的表达式中,这种优化能够避免重复计算窗口函数,提高查询执行效率。
🔹函数参数
参数名 | 类型 | 说明 |
---|---|---|
root | PlannerInfo * | 查询优化器的全局上下文信息 |
runcondition | List * | 需要优化的 runCondition (即 HAVING 条件等) |
subplan_itlist | indexed_tlist * | targetlist(plan->targetlist) 的索引表 |
📌 目的
该函数的目标是优化 WindowAgg
运行条件,使 runCondition
直接引用 plan->targetlist
,避免 WindowFunc
重新计算。
/** fix_windowagg_condition_expr* 转换 `runcondition` 中的 `WindowFunc` 引用,* 将其替换为指向 `subplan_itlist` 中对应 `WindowFunc` 结果的 `Var`。* 这样可以避免 `WindowFunc` 在 `HAVING` 等条件中被重复计算,提高查询执行效率。*/
static List *
fix_windowagg_condition_expr(PlannerInfo *root,List *runcondition,indexed_tlist *subplan_itlist)
{/* 定义 `fix_windowagg_cond_context` 结构体变量 `context`,用于存储转换过程中的上下文信息 */fix_windowagg_cond_context context;/* 将 `PlannerInfo` 指针 `root` 传递给 `context`,确保在转换过程中能够访问查询优化器的上下文信息 */context.root = root;/* 存储 `subplan_itlist`(即 `plan->targetlist` 的索引列表),用于查找 `WindowFunc` 对应的 `Var` */context.subplan_itlist = subplan_itlist;/* 初始化 `newvarno` 为 `0`(该字段目前未使用,可能用于扩展功能) */context.newvarno = 0;/* 调用 `fix_windowagg_condition_expr_mutator` 进行转换 *//* 该函数会递归遍历 `runCondition`,查找 `WindowFunc`,并替换为 `Var` */return (List *) fix_windowagg_condition_expr_mutator((Node *) runcondition,&context);
}
fix_windowagg_condition_expr_mutator 函数
该函数的作用是递归遍历并替换查询树中的 WindowFunc
节点,将其替换为 targetlist
中相应的 Var
,以便在 runCondition
中高效引用而不必重新计算 WindowFunc
。具体功能如下:
- 遍历树结构:递归遍历整个表达式树。
- 替换
WindowFunc
:当遇到WindowFunc
节点时,将其替换为指向targetlist
中对应位置的Var
,避免重新计算WindowFunc
。 - 查找目标列表中的 Var:通过
search_indexed_tlist_for_non_var
函数,找到对应的Var
。 - 报错处理:如果没有找到相应的
Var
,会抛出错误,说明WindowFunc
无法在目标列表中找到。
/** fix_windowagg_condition_expr_mutator* 变换器函数,用于将 `WindowFunc` 替换为对应的 `Var`,该 `Var` 引用在 `targetlist` 中的 `WindowFunc`。* 此操作帮助优化查询,避免在条件中重复计算窗口函数。*/
static Node *
fix_windowagg_condition_expr_mutator(Node *node,fix_windowagg_cond_context *context)
{/* 如果当前节点为 NULL,直接返回 NULL */if (node == NULL)return NULL;/* 如果当前节点是 WindowFunc 类型的节点 */if (IsA(node, WindowFunc)){/* 定义一个 Var 类型的指针 `newvar`,用于存储替换后的变量 */Var *newvar;/* 从 `subplan_itlist` 中搜索与 `WindowFunc` 对应的 `Var` */newvar = search_indexed_tlist_for_non_var((Expr *) node,context->subplan_itlist,context->newvarno);/* 如果找到了对应的 Var,则返回这个 Var */if (newvar)return (Node *) newvar;/* 如果没有找到对应的 Var,则抛出错误,表示未能在目标列表中找到 WindowFunc */elog(ERROR, "WindowFunc not found in subplan target lists");}/* 对当前节点的子树进行递归处理,遍历树结构 */return expression_tree_mutator(node,fix_windowagg_condition_expr_mutator,(void *) context);
}
执行案例
假设我们有如下 SQL
查询:
SELECT id, region, amount, row_number() OVER (PARTITION BY region ORDER BY amount DESC) AS rn
FROM sales
HAVING row_number() > 2;
在查询优化过程中,WindowFunc
(如 row_number()
)会出现在 HAVING
子句中。为了避免在 HAVING
子句中重复计算窗口函数,fix_windowagg_condition_expr_mutator
函数会将 WindowFunc
替换为 Var
,并使得 HAVING
子句直接引用 targetlist
中已经计算好的结果。
关键函数解释
search_indexed_tlist_for_non_var
:此函数用于从subplan_itlist
中查找WindowFunc
对应的Var
。它通过对比表达式的结构和目标列表,找到对应的变量引用。expression_tree_mutator
:这是PostgreSQL
中常用的树遍历函数,用于递归处理表达式树的每个节点,并应用特定的变换操作。
相关文章:
【PostgreSQL内核学习 —— (WindowAgg(三))】
WindowAgg set_subquery_pathlist 部分函数解读check_and_push_window_quals 函数find_window_run_conditions 函数执行案例总结 计划器模块(set_plan_refs函数)set_windowagg_runcondition_references 函数执行案例 fix_windowagg_condition_expr 函数f…...
案例1.spark和flink分别实现作业配置动态更新案例
目录 目录 一、背景 二、解决 1.方法1:spark broadcast广播变量 a. 思路 b. 案例 ① 需求 ② 数据 ③ 代码 2.方法2:flink RichSourceFunction a. 思路 b. 案例 ① 需求 ② 数据 ③ 代码 ④ 测试验证 测试1 测试2 测试3 一、背景 在实时作业(如 Spark Str…...
一键掌握多平台短视频矩阵营销/源码部署
短视频矩阵系统的介绍与应用 随着数字化营销策略的不断演进,传统的短视频矩阵操作方法可能已显陈旧。为此,一款全新的短视频矩阵系统应运而生,它通过整合多个社交媒体账户、创建多样化的任务、运用先进的智能视频编辑工具、实现多平台内容的…...
如何利用maven更优雅的打包
最近在客户现场部署项目,有两套环境,无法连接互联网,两套环境之间也是完全隔离,于是问题就来了,每次都要远程到公司电脑改完代码,打包,通过网盘(如果没有会员,上传下载慢…...
Win11非虚拟机安装ISE14.7
官网下载6.18GB 的 Full Installer for Windows 7/XP/Server解压后运行安装程序不勾选Enable WebTalk to send software, IP ...安装程序卡死在ISE:Configure WebTalk,此时打开任务管理器,在详情中找到xwebtalk,右键结束任务。安装程序继续进…...
大彩讲堂:掌握虚拟屏调试的方法
一、适合范围 适合全系列大彩协议串口屏产品 二、开发环境版本 1. VisualTFT软件版本:V3.0.0.1037及以上的版本,版本查看方式: (1) 打开VisualTFT软件启动页面如图2-1所示,右上角显示的软件版本号; 图2-1 软件版本 (…...
k8sollama部署deepseek-R1模型,内网无坑
这是目录 linux下载ollama模型文件下载到本地,打包迁移到k8s等无网络环境使用下载打包ollama镜像非k8s环境使用k8s部署访问方式非ollama运行deepseek模型linux下载ollama 下载后可存放其他服务器 curl -L https://ollama.com/download/ollama-linux-amd64.tgz -o ollama-linu…...
2025职业发展规划
2025职业发展规划 我是一名大公司的高级移动应用开发技术专家,目前参与了鸿蒙App开发,对鸿蒙的TS语言也有所了解。现在需要制定2025年的职业发展规划,包括学习内容和方向,并以思维导图的形式呈现。我需要梳理出合适的发展路径。首…...
VDN 微服务架构搭建篇(三)基于 Nacos 的 Spring Cloud Gateway 动态路由管理
VDN 微服务架构搭建篇(三):基于 Nacos 的 Spring Cloud Gateway 动态路由管理 在微服务架构中,网关 是整个系统的入口,负责 流量管理、请求路由、安全控制等关键功能。 Spring Cloud Gateway 作为 Spring 生态官方推荐…...
(3)yaml语法
yaml语法 YAML 是 “YAML Ain’t a Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。 通俗的来说yaml…...
SpringAI系列 - 使用LangGPT编写高质量的Prompt
目录 一、LangGPT —— 人人都可编写高质量 Prompt二、快速上手2.1 诗人 三、Role 模板3.1 Role 模板3.2 Role 模板使用步骤3.3 更多例子 四、高级用法4.1 变量4.2 命令4.3 Reminder4.4 条件语句4.5 Json or Yaml 方便程序开发 一、LangGPT —— 人人都可编写高质量 Prompt La…...
Linux提权--John碰撞密码提权
John the Ripper(简称 John)是一个常用的密码破解工具,可以通过暴力破解、字典攻击、规则攻击等方式,尝试猜解用户密码。密码的弱度是提权攻击中的一个重要因素,如果某个用户的密码非常简单或是默认密码࿰…...
系分成长指南
持续改进的核心理念:持续发现问题并改进,通过反馈和反馈循环优化工作流程。 如何制定反馈渠道:通过线上表格填写问卷、内部会议记录、即时消息等方式。 如何保持动力:设定具体目标、使用 KPI 测量进展、奖励机制、建立支持体系。 …...
5 计算机网络
5 计算机网络 5.1 OSI/RM七层模型 5.2 TCP/IP协议簇 5.2.1:常见协议基础 一、 TCP是可靠的,效率低的; 1.HTTP协议端口默认80,HTTPSSL之后成为HTTPS协议默认端口443。 2.对于0~1023一般是默认的公共端口不需要注册,1024以后的则需…...
绿联NAS安装cpolar内网穿透工具实现无公网IP远程访问教程
文章目录 前言1. 开启ssh服务2. ssh连接3. 安装cpolar内网穿透4. 配置绿联NAS公网地址 前言 本文主要介绍如何在绿联NAS中使用ssh远程连接后,使用一行代码快速安装cpolar内网穿透工具,轻松实现随时随地远程访问本地内网中的绿联NAS,无需公网…...
Temperature、Top-P、Top-K、Frequency Penalty详解
在生成式AI(比如ChatGPT)中,Temperature、Top-P、Top-K、Frequency Penalty 这些参数用于控制文本生成的多样性、随机性和重复度,它们的作用如下: 1. Temperature(温度) 作用:控制输…...
2.6作业
1.思维导图 2.代码解释 struct A{double a; }; struct B{char b[8]; };int main(int argc,const char *argv[]) {struct A x;struct B y;x.a 3.14;y *(struct B*)&x;printf("y.b %lf\n",*(double *)y.b);return 0; } 注释: 1. 定义struct A类型变…...
面试笔记-多线程篇
为什么不直接调用run方法而是调用start方法? start方法会先创建一条线程,再用创建出的新线程去执行对应的run方法,这样才是起到多线程效果,如果直接调用run方法,则只是在原线程执行。 线程的sleep方法和wait方法的区别…...
stacking 框架
stacking stacking介绍 Stacking是个多层的多模型集合方法。每一层都可包括多个模型,下一层利用上一层模型的结果进行学习。可以只使用一层,然后用元学习器融合,也可以多层融合。 单层融合 多层融合 如上图所示,Stacking结构中…...
面向对象编程简介
面向对象编程(OOP)是一种编程范式,强调通过“对象”来设计软件。对象是数据和功能的封装,使得程序更易于理解和维护。本文将介绍面向对象的基本概念、特性以及其在软件开发中的重要性。 1. 面向对象的基本概念 1.1 对象 对象是…...
【ArcGIS_Python】使用arcpy脚本将shape数据转换为三维白膜数据
说明: 该专栏之前的文章中python脚本使用的是ArcMap10.6自带的arcpy(好几年前的文章),从本篇开始使用的是ArcGIS Pro 3.3.2版本自带的arcpy,需要注意不同版本对应的arcpy函数是存在差异的 数据准备:准备一…...
云计算——AWS Solutions Architect – Associate(saa)1、什么是云,AWS介绍
什么是云? 什么是云? 云计算(cloud computing)是基于互联网的相关服务的增加、使用和交付模式,通常涉及通过互联网来提供动态易护展且经常是虚拟化的资源。云是网络、互联网的一种比喻说法。 简单理解为:云是 共享资源,按需付费࿰…...
快手ip属地是定位吗?怎么改
在当今数字化时代,随着网络平台的不断发展,用户隐私和数据安全成为了公众关注的焦点。各大社交媒体平台纷纷推出的“IP属地”功能,无疑为网络环境增添了一抹新的色彩。其中,快手的IP属地显示功能尤为引人注目。那么,快…...
graylog初体验
最近graylog比较火,部署了一个来测试下,看下后续能不能代替目前占用资源比较多的elk,目前未对graylog性能进行深入测试,只是简单体验了下,graylog的UI比较简陋,但是在报警以及权限方面优于ELK,整…...
MySQL实战-解决方案
1. MySQL 主从集群同步延迟问题的解决方案 在主从复制架构中,主库执行写操作后,将更新事件写入 Binlog,从库通过 I/O 线程将 Binlog 数据同步到本地的 Relay Log,再由 SQL 线程解析并执行,从而保持数据一致性。然而&a…...
使用 CSS 实现透明效果
在 CSS 中,实现透明效果有几种方法,具体使用哪种方法取决于具体需求。以下是一些常见的方法: 使用 opacity 属性: opacity 属性可以设置整个元素的透明度,包括其所有的子元素。 .transparent { opacity: 0.5; /* 0 表…...
LabVIEW2025中文版软件安装包、工具包、安装教程下载
下载链接:LabVIEW及工具包大全-三易电子工作室http://blog.eeecontrol.com/labview6666 《LabVIEW2025安装图文教程》 1、解压后,双击install.exe安装 2、选中“我接受上述2条许可协议”,点击下一步 3、点击下一步,安装NI Packa…...
2025.2.5——五、[网鼎杯 2020 青龙组]AreUSerialz
题目来源:BUUCTF [网鼎杯 2020 青龙组]AreUSerialz 一、打开靶机,整理信息 直接得到一串php代码,根据题目可以看到还有序列化 二、解题思路 step 1:代码审计 <?phpinclude("flag.php");highlight_file(__FILE__…...
Oracle Life DBA的一天
/***************************************************************************************************************** Navicat Premium Data Transfer Source File : Oracle Life DBA的一天.sql Source Server Type : Oracle Source Server Version : 190…...
手写MVVM框架-实现简单v-bind
v-bind 有两种情况: 1.绑定的是一个简单的属性 <div :class"customClass">简单v-bind</div> 2.绑定的元素上面有表达式 <div :class"{customClass: a 1 > 2}">简单v-bind</div> 这一章我们先说第一种情况&…...
【力扣】240.搜索二维矩阵 II
题目 我的代码 class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {for(int i0;i<matrix.size();i){for(int j0;j<matrix[0].size();j){if(targetmatrix[i][j]){return true;}else if(target<matrix[i][j]){brea…...
PlanLLM: 首个支持开放词汇与封闭集任务的跨模态视频程序规划框架
2025年1月7号,由杨德杰、赵子敬、刘洋联合提出PlanLLM,一种基于可微调大型语言模型(LLM)的跨模态联合学习框架,用于解决视频程序规划任务。通过引入LLM增强规划模块和互信息最大化模块,PlanLLM突破了现有方…...
使用服务器部署DeepSeek-R1模型【详细版】
文章目录 引言deepseek-r1IDE或者终端工具算力平台体验deepseek-r1模型总结 引言 在现代的机器学习和深度学习应用中,模型部署和服务化是每个开发者面临的重要任务。无论是用于智能推荐、自然语言处理还是图像识别,如何高效、稳定地将深度学习模型部署到…...
TCP三次握手、四次挥手过程及原理
TCP 协议简述 TCP 提供面向有连接的通信传输,面向有连接是指在传送数据之前必须先建立连接,数据传送完成后要释放连接。 无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。在TCP/IP协议中,TCP协议提供可靠的连接…...
AWS App2Container
AWS App2Container 是一个由 Amazon Web Services (AWS) 提供的工具,它帮助用户将现有的传统应用程序(特别是运行在虚拟机或物理服务器上的应用)转化为容器化的应用,从而可以在 AWS 上更方便地部署、管理和扩展。具体来说…...
《一》深入了解软件测试工具 JMeter-自我介绍
深入了解软件测试工具 JMeter 在当今的数字化时代,软件已经渗透到我们生活的方方面面,从日常使用的手机应用到复杂的企业级系统,软件的质量和性能直接影响着用户体验和业务的成功。而软件测试作为保障软件质量的关键环节,其中的性…...
(算法竞赛)图论+DFS深搜——图的dfs遍历1
题目描述 给定一个无向图,包含 n 个顶点(编号为 1 到 n)和 e 条边。要求从顶点 1 开始进行深度优先搜索(DFS),并按照访问顺序输出遍历结果。注意:当存在多个邻接点时,优先访问编号较…...
二级C语言题解:十进制转其他进制、非素数求和、重复数统计
目录 一、程序填空📝 --- 十进制转其他进制 题目📃 分析🧐 二、程序修改🛠️ --- 非素数求和 题目📃 分析🧐 三、程序设计💻 --- 重复数统计 题目📃 分析🧐 前言…...
快速搭建GPU环境 | docker、k8s中使用gpu
目录 一、裸机部署安装 GPU Driver安装 CUDA Toolkit测试 二、Docker 环境安装 nvidia-container-toolkit配置使用该 runtime 三、 k8s 环境安装 device-plugin安装 GPU 监控 一、裸机部署 裸机中要使用上 GPU 需要安装以下组件: GPU DriverCUDA Toolkit 二者的关…...
基于docker搭建Kafka集群,使用KRaft方式搭建,摒弃Zookeeper
KAFKA基于docker使用KRaft进行集群搭建 环境:已成功搭建kafka服务 可点击链接跳转至安装kafka-3.8.0版本 并启用SASL认证 教程 使用基于Zookeeper方式搭建集群教程 kafka-3.8.0版本 并启用SASL认证 教程 搭建kafka-ui可视化工具 192.168.2.91 192.168.2.92 192…...
分库分表详解
分库分表确实有垂直切分和水平切分两种,针对给出的描述,以下是对这两种切分方式的详细分析和验证: 垂直切分 描述:将表按照功能模块、关系密切程度划分出来,部署到不同的库上。 分析:垂直切分主要是根据…...
【重生之学习C语言----水仙花篇】
目录 编辑 ----------------------------------------begin-------------------------------------- 一、什么是水仙花数? 二、问题分析 确定数字的位数:计算输入数字的位数 n。 分离每一位数字:例如将 153 分离为 1、5、3。 计算各…...
云端IDE如何重定义开发体验
豆包 MarsCode 是一个集成了AI功能的编程助手和云端IDE,旨在提高开发效率和质量。它支持多种编程语言和IDE,提供智能代码补全、代码解释、单元测试生成和问题修复等功能,同时具备AI对话视图和开发工具。 豆包 MarsCode 豆包 MarsCode 编程助…...
模拟实现string类
目录 一.构造与析构函数 二.基础小功能的实现 1.clear 2.c_str 3外部对私有的查看 三.实现string的迭代器 四.string的增删查改 1.push_back尾插 1.1reserve扩容 1.2尾插 3.运算符重载 4.insert在任意位置插入 5.erase删除 5.1npos的处理 5.2函数的实现 6.find查…...
使用一个大语言模型对另一个大语言模型进行“调教”
使用一个大语言模型对另一个大语言模型进行“调教”(通常称为微调或适配),是一种常见的技术手段,用于让目标模型更好地适应特定的任务、领域或风格。以下是基于搜索结果整理的详细步骤和方法: 1.准备工作 安装必要的…...
十二、Docker Compose 部署 SpringCloudAlibaba 微服务
一、部署基础服务 0、项目部署结构 项目目录结构如下: /home/zhzl_hebei/ ├── docker-compose.yml └── geochance-auth/└── Dockerfile└── geochance-auth.jar └── geochance-system/└── Dockerfile└── geochance-system.jar └── geochance-gateway/…...
深入浅出:旋转变位编码(RoPE)在现代大语言模型中的应用
在现代大语言模型(LLMs)中,位置编码是一个至关重要的组件。无论是 Meta 的 LLaMA 还是 Google 的 PaLM,这些模型都依赖于位置编码来捕捉序列中元素的顺序信息。而旋转变位编码(RoPE) 作为一种创新的位置编码…...
C# 使用ADO.NET访问数据全解析
.NET学习资料 .NET学习资料 .NET学习资料 在 C# 的应用开发中,数据访问是极为关键的部分。ADO.NET作为.NET 框架下用于数据访问的核心技术,能够帮助开发者便捷地与各类数据源进行交互。本文将深入剖析ADO.NET,带你掌握使用 C# 通过ADO.NET访…...
趣解单词,实现快速记忆
英文单词 love,是“爱”的意思: love v./n.爱;喜欢;热爱;爱情;心爱的人 那什么是爱呢?love,首字母为l,是一根绳子,ve-通f,love通life࿰…...
TEMU电池法规:CE-battery法规指令主要检测以下这三样,汞、镉、铅
TEMU电池法规:CE-battery法规的指令主要检测以下这三样,汞、镉、铅有害物质: CE-Battery认证是依据欧盟新电池法规EU 2023/1542指令进行的电池安全检测报告。该认证针对所有类别的电池,包括内置或添加到产品中的电池,…...