• mysql 存储过程的实现原理


    一、描述
      存储过程是一组可以完成特定功能的SQL语句集,经编译后存储在数据库中
      statement语句(DDL、DML、导出及管理语句等)、异常处理、流程控制
    二、创建存储过程
      系统做语句分析,如果没有出现词、语法等的错误则存入系统表mysql.proc中
    三、调用存储过程
    1、编译
      存储过程体中的语句集会被解析为一组指令集
      存储过程的执行就是从该组指令集的第一个指令开始顺序执行
      系统对存储过程的解析是以语句或结构为单位的,该部分处理在YACC中完成
      注意:语句和指令之间并非一对一的对应关系

      
    2、优化
      优化的目标为去除无用指令、使用捷径更新跳转路径和压缩指令集
      优化针对流程控制语句和结构
      (1) 遍历所有指令,标记会跳转到的指令opt_mark(...)
      从第1条指令开始遍历;遍历规则依各指令中指示下一条执行指令的位置(m_ip+1、m_dest、m_cont_dest)进行深度优先遍历
      追踪连续跳转至最后一个指令位置(已有跳转标记、自指向和闭环)opt_shortcut_jump(...)

      
      (2) 压缩指令及顺序更新
      去除不会被使用的指令(无跳转标记);后续指令前移,并更新位置信息(m_ip)opt_move(...);更新跳转指令中的跳转位置信息set_destination(...)
    3、执行
      当调用存储过程的命令(CALL...)发送到服务端时,该语句会被解析并触发对系统表mysql.proc的读取以及对存储过程语句集的解析,在该解析过程中会创建sp_head对象以及sp_instr各子类的对象,生成指令集并进行优化(如果此存储过程在当前session中已被调用过,则解析后的相关信息会被存储在缓存中,此时可略过db_load_routine()中各步骤,直接使用)
      在执行指令集之前,需将传入的参数进行对应项的改写与填充,并根据指令类型的不同进行不同的处理,并最终由net_end_statement()将结果集发送回客户端
    4、基本流程示意

      
    四、类图
    1、类间关系
      Class sp_head,定义了存储过程相关变量、结构以及公用的方法和接口
      指令共16种,每个指令对应一个Class sp_instr的子类并通过接口处理执行
      Class sp_instr_set_trigger_field和Class sp_instr_freturn分别对应触发器和自定义函数

      
    2、策略模式
      sp_instr抽象类提供策略执行接口execute()和exec_core(),m_ip为指令本身的序号
      sp_instr_opt_meta::m_dest为跳转序号
      16个子类实现执行算法,设置相关序号
      sp_head::m_instr维护策略实例集
      sp_head::execute()设置运行时具体策略的选用
    五、指令集
      打开动态游标、部分declare语句、SET语句、LEAVE语句、LOOP结构、IF结构、CASE    结构、REPEAT结构和WHILE结构会被拆分为多条指令
      其余和statement语句则不会被拆分为多条指令,即自身就对应一条指令
      下面对这些语句做一下简单说明:
    1、DECLARE handler_type HANDLER FOR condition_value[,...] sp_statement;
      此语句会被拆分为3部分:
      1)记录handler信息指令,对应 sp_instr_hpush_jump子类,指定跳转位置
      2)sp_statement 代表处理器指令,是用户自定义的语句部分
      3)根据handler类型指定跳转位置,对应 sp_instr_hreturn子类
    2、DECLARE var_name[,...] type [DEFAULT value];
      此语句随定义var_name的个数被拆分成若干赋值指令,对应sp_instr_set子类
    3、DECLARE <游标名称> CURSOR FOR <SELECT 语句>;
      定义静态游标指令,对应sp_instr_cpush子类,负责记录cursor信息
    4、SET var_name = expr [, var_name = expr];
      此语句随定义var_name的个数被拆分成若干赋值指令。若var_name为存储过程的局部变量,则指令对应sp_instr_set子类
      若var_name为系统或用户级变量,则指令对应sp_instr_stmt子类
    5、OPEN <游标名称>;
      打开静态游标指令,对应sp_instr_copen子类,负责执行<SELECT 语句>
    6、DECLARE <游标名称> REF CURSOR;
      定义动态游标指令,对应sp_instr_cpush子类,负责记录cursor信息
    7、OPEN <游标名称> FOR <SELECT 语句>;
      打开动态游标,此语句会被拆分为2部分:
      1)定义动态游标指令,对应sp_instr_cpush子类,负责记录cursor信息
      2)打开动态游标指令,对应sp_instr_copen子类,负责执行<SELECT 语句>
    8、FETCH <游标名称> INTO <局部变量>;
      获取结果指令,对应sp_instr_cfetch子类,负责从游标中获取数据
    9、CLOSE <游标名称>;
      关闭游标指令,对应sp_instr_cclose子类
    10、IF <判断条件> THEN <执行体> END IF;
      此语句会被拆分为3部分:
      1)条件判断指令,对应 sp_instr_jump_if_not子类,指定跳转位置
      2)<执行体> 代表指令集,包是用户自定义的语句部分
      3)分支跳转指令,对应 sp_instr_jump子类,指定跳转位置
    11、IF <判断条件> THEN <执行体1> ELSE <执行体2> END IF;
      此语句会被拆分为4部分:
      1)条件判断指令,对应 sp_instr_jump_if_not子类,指定跳转位置
      2)<执行体1> 代表指令集,是用户自定义的语句部分
      3)分支跳转指令,对应 sp_instr_jump子类,指定跳转位置
      4)<执行体2> 代表指令集,是用户自定义的语句部分
    12、ITERATE<标签名>;
      分支跳转指令,对应 sp_instr_jump子类,指定跳转位置
    13、LEAVE <标签名>;
      分支跳转指令,对应 sp_instr_jump子类,指定跳转位置
      如果在跳出当前的BEGIN...END语句块中存在handler,则会在sp_instr_jump子类之前增加删除handler记录的指令,对应sp_instr_hpop子类
      如果在跳出当前的BEGIN...END语句块中存在cursor,则会在sp_instr_jump子类之前增加删除cursor记录的指令,对应sp_instr_cpop子类
    14、LOOP <执行体> END LOOP;
      此语句会被拆分为2部分:
      1)<执行体>代表指令集,是用户自定义的语句部分
      2)分支跳转指令,对应 sp_instr_jump子类,指定跳转位置
    15、CASE WHEN <条件1> THEN <执行语句1> ELSE <执行语句X> END CASE;
      CASE结构体中可有若干WHEN...THEN...复合语句,此处只对单一情况做说明,此时语句会被拆分为4部分:
      1)条件判断指令,对应 sp_instr_jump_if_not子类,指定跳转位置
      2)<执行语句1>代表指令集,是用户自定义的语句部分
      3)分支跳转指令,对应 sp_instr_jump子类,指定跳转位置
      4)<执行语句X>代表指令集,是用户自定义的语句部分
      对于语句中不存在ELSE <执行语句X>,则第4)部分变为错误信息记录指令,对应    sp_instr_error子类
    16、CASE <判定条件> WHEN <值1> THEN <执行语句1> ELSE <执行语句X> END CASE ;
      CASE结构体中可有若干WHEN...THEN...复合语句,此处只对单一情况做说明,此时语句会被拆分为5部分:
      1)表达式计算指令,对应sp_instr_set_case_expr子类,计算<判定条件>
      2)<值1>条件判断指令,对应 sp_instr_jump_if_not子类,指定跳转位置
      3)<执行语句1>代表指令集,是用户自定义的语句部分
      4)分支跳转指令,对应 sp_instr_jump子类,指定跳转位置
      5)<执行语句 X>代表指令集,是用户自定义的语句部分
      对于语句中不存在ELSE <执行语句X>,则第5)部分变为错误信息记录指令,对应sp_instr_error子类
    17、REPEAT <执行体> UNTIL<退出条件> END REPEAT;
      此循环结构被拆分为2部分:
      1)<执行体>代表指令集,是用户自定义的语句部分
      2)条件判断指令,对应 sp_instr_jump_if_not子类,指定跳转位置
    18、WHILE <执行条件> DO <执行体> END WHILE;
      此循环结构被拆分为3部分:
      1)条件判断指令,对应 sp_instr_jump_if_not子类,指定跳转位置
      2)<执行体>代表指令集,是用户自定义的语句部分
      3)分支跳转指令,对应 sp_instr_jump子类,指定跳转位置
    19、[begin_label:] BEGIN [statement_list] END [end_label]
      [statement_list]代表指令集,是用户自定义的语句部分
      如果在当前BEGIN...END语句块中存在handler,则会在语句末尾增加删除handler记录的指令,对应sp_instr_hpop子类
      如果在当前BEGIN...END语句块中存在cursor,则会在语句末尾增加删除cursor记录的指令,对应sp_instr_cpop子类
    六、实例

    存储过程语句集

    指令集

    create procedure proc_scursor()

     

    begin

     

    declare done, tmp int default(0);

    sp_instr_set

     

    sp_instr_set

    declare cur cursor for select * from test.t;

    sp_instr_cpush

    declare continue handler for sqlstate '02000' set done = 1;

    sp_instr_hpush_jump

     

    sp_instr_set

     

    sp_instr_hreturn

    open cur;

    sp_instr_copen

    repeat

     

    fetch cur into tmp;

    sp_instr_cfetch

    if not done then

    sp_instr_jump_if_not

    select tmp;

    sp_instr_stmt

    end if;

     

    until done end repeat;

    sp_instr_jump_if_not

    close cur;

    sp_instr_cclose

     

    sp_instr_hpop

    end

    sp_instr_cpop

  • 相关阅读:
    2017年第八蓝桥杯C/C++ A组国赛 —— 第二题:生命游戏
    451. 根据字符出现频率排序
    剑指 Offer 40. 最小的k个数
    list使用详解
    STL---priority_queue
    1046. 最后一块石头的重量
    739. 每日温度
    921. 使括号有效的最少添加
    STL----stack
    173. 二叉搜索树迭代器
  • 原文地址:https://www.cnblogs.com/liuxinliang/p/5571338.html
Copyright © 2020-2023  润新知