文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

PostgreSQL中​ExecutePlan函数与ExecSeqScan函数的作用是什么

2024-04-02 19:55

关注

这篇文章主要介绍“PostgreSQL中ExecutePlan函数与ExecSeqScan函数的作用是什么”,在日常操作中,相信很多人在PostgreSQL中ExecutePlan函数与ExecSeqScan函数的作用是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”PostgreSQL中ExecutePlan函数与ExecSeqScan函数的作用是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

ExecutePlan函数处理查询计划,直到检索到指定数量(参数numbertuple)的元组,并沿着指定的方向扫描。ExecSeqScan函数顺序扫描relation,返回下一个符合条件的元组。

一、数据结构

Plan
所有计划节点通过将Plan结构作为第一个字段从Plan结构“派生”。这确保了在将节点转换为计划节点时,一切都能正常工作。(在执行器中以通用方式传递时,节点指针经常被转换为Plan *)


typedef struct Plan
{
    NodeTag     type;//节点类型

    
    Cost        startup_cost;   
    Cost        total_cost;     

    
    double      plan_rows;      
    int         plan_width;     

    
    bool        parallel_aware; 
    bool        parallel_safe;  

    
    int         plan_node_id;   
    List       *targetlist;     
    List       *qual;           
    struct Plan *lefttree;      
    struct Plan *righttree;
    List       *initPlan;       

    
    Bitmapset  *extParam;
    Bitmapset  *allParam;
} Plan;

二、源码解读

ExecutePlan
PortalRunSelect->ExecutorRun->ExecutePlan函数处理查询计划,直到检索到指定数量(参数numbertuple)的元组,并沿着指定的方向扫描.


static void
ExecutePlan(EState *estate,//执行状态
            PlanState *planstate,//计划状态
            bool use_parallel_mode,//是否使用并行模式
            CmdType operation,//操作类型
            bool sendTuples,//是否需要传输元组
            uint64 numberTuples,//元组数量
            ScanDirection direction,//扫描方向
            DestReceiver *dest,//接收的目标端
            bool execute_once)//是否只执行一次
{
    TupleTableSlot *slot;//元组表Slot
    uint64      current_tuple_count;//当前的元组计数

    
    current_tuple_count = 0;

    
    estate->es_direction = direction;

    
    if (!execute_once)
        use_parallel_mode = false;//如需多次执行,则不允许并行执行

    estate->es_use_parallel_mode = use_parallel_mode;
    if (use_parallel_mode)
        EnterParallelMode();//如并行,则进入并行模式

    
    for (;;)
    {
        
        //重置Expr上下文
        ResetPerTupleExprContext(estate);

        
        slot = ExecProcNode(planstate);

        
        if (TupIsNull(slot))
        {
            
            if (!(estate->es_top_eflags & EXEC_FLAG_BACKWARD))
                (void) ExecShutdownNode(planstate);
            break;
        }

        
        if (estate->es_junkFilter != NULL)
            slot = ExecFilterJunk(estate->es_junkFilter, slot);

        
        if (sendTuples)
        {
            
            if (!dest->receiveSlot(slot, dest))
                break;//跳出循环
        }

        
        if (operation == CMD_SELECT)
            (estate->es_processed)++;

        
        current_tuple_count++;
        if (numberTuples && numberTuples == current_tuple_count)
        {
            
            if (!(estate->es_top_eflags & EXEC_FLAG_BACKWARD))
                (void) ExecShutdownNode(planstate);
            break;
        }
    }

    if (use_parallel_mode)
        ExitParallelMode();//退出并行模式
}



#ifndef FRONTEND
static inline TupleTableSlot *
ExecProcNode(PlanState *node)
{
    if (node->chgParam != NULL) 
        ExecReScan(node);       

    return node->ExecProcNode(node);//执行ExecProcNode
}
#endif

ExecSeqScan
ExecSeqScan函数顺序扫描relation,返回下一个符合条件的元组。


static TupleTableSlot *
ExecSeqScan(PlanState *pstate)
{
    SeqScanState *node = castNode(SeqScanState, pstate);//获取SeqScanState

    return ExecScan(&node->ss,
                    (ExecScanAccessMtd) SeqNext,
                    (ExecScanRecheckMtd) SeqRecheck);//执行Scan
}


TupleTableSlot *
ExecScan(ScanState *node,
         ExecScanAccessMtd accessMtd,   
         ExecScanRecheckMtd recheckMtd) //recheck方法
{
    ExprContext *econtext;//表达式上下文
    ExprState  *qual;//表达式状态
    ProjectionInfo *projInfo;//投影信息

    
    qual = node->ps.qual;
    projInfo = node->ps.ps_ProjInfo;
    econtext = node->ps.ps_ExprContext;

    
    //在ExecScanFetch中有中断检查

    
    if (!qual && !projInfo)
    {
        ResetExprContext(econtext);
        return ExecScanFetch(node, accessMtd, recheckMtd);
    }

    
    ResetExprContext(econtext);

    
    for (;;)
    {
        TupleTableSlot *slot;//slot变量

        slot = ExecScanFetch(node, accessMtd, recheckMtd);//获取slot

        
        if (TupIsNull(slot))
        {
            if (projInfo)
                return ExecClearTuple(projInfo->pi_state.resultslot);
            else
                return slot;
        }

        
        econtext->ecxt_scantuple = slot;

        
        if (qual == NULL || ExecQual(qual, econtext))
        {
            
            if (projInfo)
            {
                
                return ExecProject(projInfo);//执行投影操作并返回
            }
            else
            {
                
                return slot;//直接返回
            }
        }
        else
            InstrCountFiltered1(node, 1);//instrument计数

        
        ResetExprContext(econtext);
    }
}



static inline TupleTableSlot *
ExecScanFetch(ScanState *node,
              ExecScanAccessMtd accessMtd,
              ExecScanRecheckMtd recheckMtd)
{
    EState     *estate = node->ps.state;

    CHECK_FOR_INTERRUPTS();//检查中断

    if (estate->es_epqTuple != NULL)//如es_epqTuple不为NULL()
    {
        //es_epqTuple字段用于在READ COMMITTED模式中替换更新后的元组后,重新评估是否满足执行计划的条件quals
        
        Index       scanrelid = ((Scan *) node->ps.plan)->scanrelid;//访问的relid

        if (scanrelid == 0)//relid==0
        {
            TupleTableSlot *slot = node->ss_ScanTupleSlot;

            
            if (!(*recheckMtd) (node, slot))
                ExecClearTuple(slot);   
            return slot;
        }
        else if (estate->es_epqTupleSet[scanrelid - 1])//从estate->es_epqTupleSet数组中获取标志
        {
            TupleTableSlot *slot = node->ss_ScanTupleSlot;//获取slot

            
            //如已返回元组,则清空slot
            if (estate->es_epqScanDone[scanrelid - 1])
                return ExecClearTuple(slot);
            
            //否则,标记没有返回
            estate->es_epqScanDone[scanrelid - 1] = true;

            
            //如test tuple为NULL,则清空slot
            if (estate->es_epqTuple[scanrelid - 1] == NULL)
                return ExecClearTuple(slot);

            
            //在计划节点的scan slot中存储test tuple
            ExecStoreHeapTuple(estate->es_epqTuple[scanrelid - 1],
                               slot, false);

            
            //检查是否满足访问方法条件
            if (!(*recheckMtd) (node, slot))
                ExecClearTuple(slot);   

            return slot;
        }
    }

    
    return (*accessMtd) (node);
}



#ifndef FRONTEND
static inline TupleTableSlot *
ExecProject(ProjectionInfo *projInfo)
{
    ExprContext *econtext = projInfo->pi_exprContext;
    ExprState  *state = &projInfo->pi_state;
    TupleTableSlot *slot = state->resultslot;
    bool        isnull;

    
    ExecClearTuple(slot);

    
    //运行表达式,从最后一列丢弃scalar结果。
    (void) ExecEvalExprSwitchContext(state, econtext, &isnull);

    
    slot->tts_flags &= ~TTS_FLAG_EMPTY;
    slot->tts_nvalid = slot->tts_tupleDescriptor->natts;

    return slot;
}
#endif


#ifndef FRONTEND
static inline bool
ExecQual(ExprState *state, ExprContext *econtext)
{
    Datum       ret;
    bool        isnull;

    
    //如state为NULL,直接返回
    if (state == NULL)
        return true;

    
    //使用函数ExecInitQual验证表达式是否可以编译
    Assert(state->flags & EEO_FLAG_IS_QUAL);

    ret = ExecEvalExprSwitchContext(state, econtext, &isnull);

    
    Assert(!isnull);

    return DatumGetBool(ret);
}
#endif




TupleTableSlot *                
ExecClearTuple(TupleTableSlot *slot)    
{
    
    Assert(slot != NULL);

    
    if (TTS_SHOULDFREE(slot))
    {
        heap_freetuple(slot->tts_tuple);//释放元组
        slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
    }
    if (TTS_SHOULDFREEMIN(slot))
    {
        heap_free_minimal_tuple(slot->tts_mintuple);
        slot->tts_flags &= ~TTS_FLAG_SHOULDFREEMIN;
    }

    slot->tts_tuple = NULL;//设置NULL值
    slot->tts_mintuple = NULL;

    
    if (BufferIsValid(slot->tts_buffer))
        ReleaseBuffer(slot->tts_buffer);//释放缓冲区

    slot->tts_buffer = InvalidBuffer;

    
    slot->tts_flags |= TTS_FLAG_EMPTY;
    slot->tts_nvalid = 0;

    return slot;
}

三、跟踪分析

测试脚本如下

testdb=# explain select dw.*,grjf.grbh,grjf.xm,grjf.ny,grjf.je 
testdb-# from t_dwxx dw,lateral (select gr.grbh,gr.xm,jf.ny,jf.je 
testdb(#                         from t_grxx gr inner join t_jfxx jf 
testdb(#                                        on gr.dwbh = dw.dwbh 
testdb(#                                           and gr.grbh = jf.grbh) grjf
testdb-# order by dw.dwbh;
                                        QUERY PLAN                                        
------------------------------------------------------------------------------------------
 Sort  (cost=20070.93..20320.93 rows=100000 width=47)
   Sort Key: dw.dwbh
   ->  Hash Join  (cost=3754.00..8689.61 rows=100000 width=47)
         Hash Cond: ((gr.dwbh)::text = (dw.dwbh)::text)
         ->  Hash Join  (cost=3465.00..8138.00 rows=100000 width=31)
               Hash Cond: ((jf.grbh)::text = (gr.grbh)::text)
               ->  Seq Scan on t_jfxx jf  (cost=0.00..1637.00 rows=100000 width=20)
               ->  Hash  (cost=1726.00..1726.00 rows=100000 width=16)
                     ->  Seq Scan on t_grxx gr  (cost=0.00..1726.00 rows=100000 width=16)
         ->  Hash  (cost=164.00..164.00 rows=10000 width=20)
               ->  Seq Scan on t_dwxx dw  (cost=0.00..164.00 rows=10000 width=20)
(11 rows)

启动gdb,设置断点,进入ExecutePlan

(gdb) b ExecutePlan
Breakpoint 1 at 0x6db79d: file execMain.c, line 1694.
(gdb) c
Continuing.

Breakpoint 1, ExecutePlan (estate=0x14daf48, planstate=0x14db160, use_parallel_mode=false, operation=CMD_SELECT, 
    sendTuples=true, numberTuples=0, direction=ForwardScanDirection, dest=0x14d9ed0, execute_once=true) at execMain.c:1694
warning: Source file is more recent than executable.
1694        current_tuple_count = 0;

查看输入参数
planstate->type:T_SortState->排序Plan
planstate->ExecProcNode:ExecProcNodeFirst,封装器
planstate->ExecProcNodeReal:ExecSort,实际的函数
use_parallel_mode:false,非并行模式
operation:CMD_SELECT,查询操作
sendTuples:T,需要发送元组给客户端
numberTuples:0,所有元组
direction:ForwardScanDirection
dest:printtup(console客户端)
execute_once:T,只执行一次

(gdb) p *estate
$1 = {type = T_EState, es_direction = ForwardScanDirection, es_snapshot = 0x1493e10, es_crosscheck_snapshot = 0x0, 
  es_range_table = 0x14d7c00, es_plannedstmt = 0x14d9d58, 
  es_sourceText = 0x13eeeb8 "select dw.*,grjf.grbh,grjf.xm,grjf.ny,grjf.je \nfrom t_dwxx dw,lateral (select gr.grbh,gr.xm,jf.ny,jf.je \n", ' ' <repeats 24 times>, "from t_grxx gr inner join t_jfxx jf \n", ' ' <repeats 34 times>..., 
  es_junkFilter = 0x0, es_output_cid = 0, es_result_relations = 0x0, es_num_result_relations = 0, 
  es_result_relation_info = 0x0, es_root_result_relations = 0x0, es_num_root_result_relations = 0, 
  es_tuple_routing_result_relations = 0x0, es_trig_target_relations = 0x0, es_trig_tuple_slot = 0x0, 
  es_trig_oldtup_slot = 0x0, es_trig_newtup_slot = 0x0, es_param_list_info = 0x0, es_param_exec_vals = 0x0, 
  es_queryEnv = 0x0, es_query_cxt = 0x14dae30, es_tupleTable = 0x14dbaf8, es_rowMarks = 0x0, es_processed = 0, 
  es_lastoid = 0, es_top_eflags = 16, es_instrument = 0, es_finished = false, es_exprcontexts = 0x14db550, 
  es_subplanstates = 0x0, es_auxmodifytables = 0x0, es_per_tuple_exprcontext = 0x0, es_epqTuple = 0x0, 
  es_epqTupleSet = 0x0, es_epqScanDone = 0x0, es_use_parallel_mode = false, es_query_dsa = 0x0, es_jit_flags = 0, 
  es_jit = 0x0, es_jit_worker_instr = 0x0}
(gdb) p *planstate
$2 = {type = T_SortState, plan = 0x14d3f90, state = 0x14daf48, ExecProcNode = 0x6e41bb <ExecProcNodeFirst>, 
  ExecProcNodeReal = 0x716144 <ExecSort>, instrument = 0x0, worker_instrument = 0x0, worker_jit_instrument = 0x0, 
  qual = 0x0, lefttree = 0x14db278, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0, 
  ps_ResultTupleSlot = 0x14ec470, ps_ExprContext = 0x0, ps_ProjInfo = 0x0, scandesc = 0x14e9fd0}
(gdb) p *dest
$4 = {receiveSlot = 0x48cc00 <printtup>, rStartup = 0x48c5c1 <printtup_startup>, rShutdown = 0x48d02e <printtup_shutdown>, 
  rDestroy = 0x48d0a7 <printtup_destroy>, mydest = DestRemote}

赋值,准备执行ExecProcNode(ExecSort)

(gdb) n
1699        estate->es_direction = direction;
(gdb) 
1705        if (!execute_once)
(gdb) 
1708        estate->es_use_parallel_mode = use_parallel_mode;
(gdb) 
1709        if (use_parallel_mode)
(gdb) 
1718            ResetPerTupleExprContext(estate);
(gdb) 
1723            slot = ExecProcNode(planstate);
(gdb)

执行ExecProcNode(ExecSort),返回slot

(gdb) 
1729            if (TupIsNull(slot))
(gdb) p *slot
$5 = {type = T_TupleTableSlot, tts_isempty = false, tts_shouldFree = false, tts_shouldFreeMin = false, tts_slow = false, 
  tts_tuple = 0x14ec4b0, tts_tupleDescriptor = 0x14ec058, tts_mcxt = 0x14dae30, tts_buffer = 0, tts_nvalid = 0, 
  tts_values = 0x14ec4d0, tts_isnull = 0x14ec508, tts_mintuple = 0x1a4b078, tts_minhdr = {t_len = 64, t_self = {ip_blkid = {
        bi_hi = 0, bi_lo = 0}, ip_posid = 0}, t_tableOid = 0, t_data = 0x1a4b070}, tts_off = 0, 
  tts_fixedTupleDescriptor = true}

查看slot中的数据
注意:slot中的t_data不是实际的tuple data,而是缓冲区信息,在返回时根据这些信息从缓冲区获取数据返回

(gdb) p *slot
$5 = {type = T_TupleTableSlot, tts_isempty = false, tts_shouldFree = false, tts_shouldFreeMin = false, tts_slow = false, 
  tts_tuple = 0x14ec4b0, tts_tupleDescriptor = 0x14ec058, tts_mcxt = 0x14dae30, tts_buffer = 0, tts_nvalid = 0, 
  tts_values = 0x14ec4d0, tts_isnull = 0x14ec508, tts_mintuple = 0x1a4b078, tts_minhdr = {t_len = 64, t_self = {ip_blkid = {
        bi_hi = 0, bi_lo = 0}, ip_posid = 0}, t_tableOid = 0, t_data = 0x1a4b070}, tts_off = 0, 
  tts_fixedTupleDescriptor = true}
(gdb) p *slot->tts_tuple
$6 = {t_len = 64, t_self = {ip_blkid = {bi_hi = 0, bi_lo = 0}, ip_posid = 0}, t_tableOid = 0, t_data = 0x1a4b070}
(gdb) p *slot->tts_tuple->t_data
$7 = {t_choice = {t_heap = {t_xmin = 21967600, t_xmax = 0, t_field3 = {t_cid = 56, t_xvac = 56}}, t_datum = {
      datum_len_ = 21967600, datum_typmod = 0, datum_typeid = 56}}, t_ctid = {ip_blkid = {bi_hi = 0, bi_lo = 0}, 
    ip_posid = 32639}, t_infomask2 = 7, t_infomask = 2, t_hoff = 24 '\030', t_bits = 0x1a4b087 ""}

判断是否需要过滤属性(不需要)

(gdb) n
1748            if (estate->es_junkFilter != NULL)
(gdb) 
(gdb) p estate->es_junkFilter
$12 = (JunkFilter *) 0x0

修改计数器等信息

(gdb) 
1755            if (sendTuples)
(gdb) 
1762                if (!dest->receiveSlot(slot, dest))
(gdb) 
1771            if (operation == CMD_SELECT)
(gdb) 
1772                (estate->es_processed)++;
(gdb) p estate->es_processed
$9 = 0
(gdb) n
1779            current_tuple_count++;
(gdb) p current_tuple_count
$10 = 0
(gdb) n
1780            if (numberTuples && numberTuples == current_tuple_count)
(gdb) p numberTuples
$11 = 0
(gdb) n
1790        }

继续循环,直接满足条件(全部扫描完毕)未知

(gdb) n
1718            ResetPerTupleExprContext(estate);
(gdb) 
1723            slot = ExecProcNode(planstate);
(gdb) 
1729            if (TupIsNull(slot))
...

ExecutePlan的主体逻辑已介绍完毕,下面简单跟踪分析ExecSeqScan函数
设置断点,进入ExecSeqScan

(gdb) del 1
(gdb) c
Continuing.

Breakpoint 2, ExecSeqScan (pstate=0x14e99a0) at nodeSeqscan.c:127
warning: Source file is more recent than executable.
127     SeqScanState *node = castNode(SeqScanState, pstate);

查看输入参数
plan为SeqScan
ExecProcNode=ExecProcNodeReal,均为函数ExecSeqScan
targetlist为投影列信息

(gdb) p *pstate
$13 = {type = T_SeqScanState, plan = 0x14d5570, state = 0x14daf48, ExecProcNode = 0x714d59 <ExecSeqScan>, 
  ExecProcNodeReal = 0x714d59 <ExecSeqScan>, instrument = 0x0, worker_instrument = 0x0, worker_jit_instrument = 0x0, 
  qual = 0x0, lefttree = 0x0, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0, 
  ps_ResultTupleSlot = 0x14e9c38, ps_ExprContext = 0x14e9ab8, ps_ProjInfo = 0x0, scandesc = 0x7fa45b442ab8}
(gdb) p *pstate->plan
$14 = {type = T_SeqScan, startup_cost = 0, total_cost = 164, plan_rows = 10000, plan_width = 20, parallel_aware = false, 
  parallel_safe = true, plan_node_id = 7, targetlist = 0x14d5438, qual = 0x0, lefttree = 0x0, righttree = 0x0, 
  initPlan = 0x0, extParam = 0x0, allParam = 0x0}

进入ExecScan函数
accessMtd方法为SeqNext
recheckMtd方法为SeqRecheck

(gdb) n
129     return ExecScan(&node->ss,
(gdb) step
ExecScan (node=0x14e99a0, accessMtd=0x714c6d <SeqNext>, recheckMtd=0x714d3d <SeqRecheck>) at execScan.c:132
warning: Source file is more recent than executable.
132     qual = node->ps.qual;

ExecScan->投影信息,为NULL

(gdb) p *projInfo
Cannot access memory at address 0x0

ExecScan->约束条件为NULL

(gdb) p *qual
Cannot access memory at address 0x0

ExecScan->如果既没有要检查的条件qual,也没有要做的投影操作,那么就跳过所有的操作并返回raw scan元组

(gdb) n
142     if (!qual && !projInfo)
(gdb) 
144         ResetExprContext(econtext);
(gdb) n
145         return ExecScanFetch(node, accessMtd, recheckMtd);

ExecScan->进入ExecScanFetch

(gdb) step
ExecScanFetch (node=0x14e99a0, accessMtd=0x714c6d <SeqNext>, recheckMtd=0x714d3d <SeqRecheck>) at execScan.c:39
39      EState     *estate = node->ps.state;

ExecScan->检查中断,判断是否处于EvalPlanQual recheck状态(为NULL,实际不是)

39      EState     *estate = node->ps.state;
(gdb) n
41      CHECK_FOR_INTERRUPTS();
(gdb) 
43      if (estate->es_epqTuple != NULL)
(gdb) p *estate->es_epqTuple
Cannot access memory at address 0x0

ExecScan->调用访问方法SeqNext,返回slot

(gdb) n
95      return (*accessMtd) (node);
(gdb) n
96  }

ExecScan->回到ExecScan&ExecSeqScan,结束调用

(gdb) n
ExecScan (node=0x14e99a0, accessMtd=0x714c6d <SeqNext>, recheckMtd=0x714d3d <SeqRecheck>) at execScan.c:219
219 }
(gdb) 
ExecSeqScan (pstate=0x14e99a0) at nodeSeqscan.c:132
132 }
(gdb)

到此,关于“PostgreSQL中ExecutePlan函数与ExecSeqScan函数的作用是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-数据库
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯