🛢️ 第14章 存储过程与存储函数
📄 一句话概括

沙箱的系统表里没有「过程」这一类对象,所以查不到是正常的。 MySQL 有三种查法:SHOW STATUS 看清单、SHOW CREATE 看源码、 information_schema 写脚本。 第三种最适合进脚本:它是普通查询,能加 WHERE、能 JOIN、能导出。

📝 本章和前面几章的关系

14.3 讲两种查法,教材只给了 SHOW STATUS 和 SHOW CREATE。这一篇补上第三种:数据字典。 上一篇讲怎么调,这一篇讲怎么查,下一篇讲怎么改。 三篇的顺序其实就是日常维护的顺序:用、查、改。

一、沙箱的系统表里没有这一类对象

查 sqlite_master 按类型分组,只有一条 table 记录。 没有 procedure、没有 function——引擎里根本没有这类对象,自然也查不到。

对象类型 数量
table 1
index 0(主键就是 rowid)
view 0

MySQL 那边这些对象存在数据字典里(老版本是 mysql.proc 表), 外面能看到的是 information_schema.ROUTINES 这张系统视图。 「存在数据字典里」这件事很重要:它意味着过程和表一样,是「可以被查询的对象」, 而不是「只在内存里的代码」。所以它能被备份、被导出、被比对。

二、MySQL 的三种查法

SHOW PROCEDURE STATUS WHERE Db = 'shop';
SHOW CREATE PROCEDURE p_list_cat;

第一种给清单:名字、类型、创建时间、注释。第二种给源码。 两个配合起来,正好是「先找到、再看清」。

SELECT ROUTINE_NAME, ROUTINE_TYPE, CREATED
FROM information_schema.ROUTINES
WHERE ROUTINE_SCHEMA = 'shop';
查法 能看到什么
SHOW PROCEDURE STATUS 清单:名字、类型、时间、注释
SHOW CREATE PROCEDURE 源码 + 建它时的环境
information_schema 可筛选,能写进脚本

第三种最适合进脚本:它是普通查询,能加 WHERE、能 JOIN、能导出成表格。 前两种是给人在命令行里看的,格式固定,不好二次处理。

三、复刻版的过程清单

沙箱这边用一张小表当「数据字典」,存名字、参数个数、注释。 这张表就是 SHOW PROCEDURE STATUS 的等价物。

过程名 参数个数 注释
p_list_cat 1 按类别列商品
p_stock 1 查库存

为什么要费劲记这些东西?因为「过程在哪、谁建的、干什么的」是维护时的第一问。 真实环境里这些信息由数据字典维护,我们可以直接查。

四、查参数与源码

参数名和源码都能查出来:参数从函数签名里读,源码在建的时候存下来。 对应 MySQL 的 SHOW CREATE PROCEDURE——它同时给源码和参数。

过程名 参数 源码开头
p_list_cat cat_id CREATE PROCEDURE ...
p_stock gid CREATE PROCEDURE ...

这里有个实践上的提醒:源码最好自己另存一份。 因为 SHOW CREATE 出来的不一定是你当初写的那份——sql_mode、字符集都会被记下来, 而且一旦有人在服务器上改过,你本地那份就是旧的了。 所以不少团队会把过程源码也放进版本库,跟应用代码一起走发布流程。 这一条在下一篇「改过程」里还会用到。

五、查「正在跑的过程」

SHOW PROCESSLIST;
SELECT ID, TIME, STATE, INFO FROM information_schema.PROCESSLIST
WHERE INFO LIKE '%p_list_cat%';

过程跑起来之后,PROCESSLIST 里看到的是「它里面正在执行的那条 SQL」,不会是过程名。 这一点常让人以为过程没在执行——其实是在执行,只是显示的是里面那句。 所以想筛「谁在跑我的过程」,得按里面那句 SQL 的特征去 LIKE, 或者提前在过程里加注释标记。 沙箱是单进程内嵌的,没有「连接列表」这个概念,这一节只能看原码。

六、动手 Demo:查清单、查源码、查参数

这一段先证「沙箱的系统表里没有过程」,再把三种 MySQL 查法贴成原码,最后用一张小表 + 函数签名, 把「清单」和「源码」这两件事在沙箱里做出来。

import sqlite3, inspect
c = sqlite3.connect(u":memory:")

# ① 沙箱的系统表里没有过程这一类
print(c.execute(u"SELECT type, COUNT(*) FROM sqlite_master "
                u"GROUP BY type").fetchall())

# ② 拿一张小表当「数据字典」
REG = {}

def create(name, fn, code, comment=u""):
    REG[name] = {"fn": fn, "code": code, "comment": comment,
                 "params": list(inspect.signature(fn).parameters)}

def list_by_cat(cat_id):
    return c.execute(u"SELECT id, name FROM tb_goods WHERE cat_id = ?",
                     (cat_id,)).fetchall()

create(u"p_list_cat", list_by_cat,
       u"CREATE PROCEDURE p_list_cat(IN c INT) BEGIN ... END",
       comment=u"按类别列商品")

# ③ 查清单
for k, v in REG.items():
    print(k, len(v["params"]), v["comment"])

# ④ 查参数与源码
for k, v in REG.items():
    print(k, v["params"], v["code"][:34])

实跑输出:

--------------------------------------------------------------------
第六十二课:想看一个过程长什么样,得先知道它在哪
--------------------------------------------------------------------

  **教材 14.3 讲两种查法:`SHOW STATUS` 看列表、`SHOW CREATE` 看源码。**
  **沙箱连过程都没有,这两种查法自然无从谈起。**
  **这一课换个角度:先证「沙箱查不到」,再讲清 MySQL 那边到底存在哪。**

  一、沙箱的系统表里没有这一类对象
  --------------------------------------------------------------------

  对象类型  数量
  --------------
  table     1
  **只有一条 `table` 记录。连索引都没有——主键就是 rowid,不单独占一条。**
  **没有 procedure、没有 function 这两类——因为引擎里根本没有这类对象。**
  **MySQL 那边它们存在 `mysql.proc`(老版本)或数据字典里,**
  **外面能看到的是 `information_schema.ROUTINES` 这张视图。**

  二、MySQL 的三种查法
  --------------------------------------------------------------------

  [教材原码 · 本沙箱不执行]
  | SHOW PROCEDURE STATUS WHERE Db = 'shop';
  教材原码,列出库里的过程:名字、类型、创建时间、注释


  [教材原码 · 本沙箱不执行]
  | SHOW CREATE PROCEDURE p_list_cat;
  教材原码,看源码:sql_mode、字符集、完整的 CREATE 语句


  [教材原码 · 本沙箱不执行]
  | SELECT ROUTINE_NAME, ROUTINE_TYPE, CREATED
  | FROM information_schema.ROUTINES
  | WHERE ROUTINE_SCHEMA = 'shop';
  教材原码,第三种查法:直接查数据字典


  查法                     能看到什么
  -----------------------------------------------------
  `SHOW PROCEDURE STATUS`  清单:名字、类型、时间、注释
  `SHOW CREATE PROCEDURE`  源码 + 建它时的环境
  `information_schema`     可筛选,能写进脚本
  **第三种最适合写进脚本:它是普通查询,能加 `WHERE`、能 `JOIN`、能导出。**
  **前两种是给人在命令行里看的。**

  三、复刻版的「过程清单」
  --------------------------------------------------------------------

  过程名      参数个数  注释
  ----------------------------------
  p_list_cat  1         按类别列商品
  p_stock     1         查库存
  **这张表就是 `SHOW PROCEDURE STATUS` 的等价物:名字、参数个数、注释。**
  **真实环境里这些东西存在数据字典里,我们可以直接查。**

  四、查参数与源码
  --------------------------------------------------------------------

  过程名      参数    源码开头
  -------------------------------------------------------
  p_list_cat  cat_id  CREATE PROCEDURE p_list_cat(IN c I…
  p_stock     gid     CREATE PROCEDURE p_stock(IN gid IN…
  **参数名是从函数签名里 `inspect` 出来的,源码是建的时候存下来的。**
  **对应 MySQL 的 `SHOW CREATE PROCEDURE`:既有源码,也有参数。**
  **这里有个实践上的提醒:源码最好自己另存一份(文件或表里)。**
  **因为 `SHOW CREATE` 出来的不一定是你当初写的那份——`sql_mode`、字符集都会被记下来,**
  **而且一旦在服务器上改过,你本地那份就是旧的了。**

  五、查「正在跑的过程」
  --------------------------------------------------------------------

  [教材原码 · 本沙箱不执行]
  | SHOW PROCESSLIST;
  教材原码,看当前有哪些连接在执行什么语句


  [教材原码 · 本沙箱不执行]
  | SELECT ID, TIME, STATE, INFO FROM information_schema.PROCESSLIST
  | WHERE INFO LIKE '%p_list_cat%';
  教材原码,筛出正在跑某个过程的连接

  **过程跑起来之后,`PROCESSLIST` 里看到的是「里面正在执行的那条 SQL」,**
  **不会是过程名——这一点常让人以为没在执行。**
  **沙箱是单进程内嵌的,没有「连接列表」这个概念,这一节只能看原码。**

第一组只有一条 table 记录,没有 procedure、没有 function。 这就是「引擎里没有这类对象」最直接的证据。 第二组是复刻版的数据字典:一个字典存「函数本体、源码、注释、参数名」。 它对应 information_schema.ROUTINES,只是换了个存放的地方。 第三组打出名字、参数个数、注释——这三项正好是 SHOW PROCEDURE STATUS 的列。 第四组打出参数名和源码开头,对应 SHOW CREATE PROCEDURE。 参数是从函数签名里读的,源码是建的时候存下来的。

30 秒实验

  1. 再建一个「过程」,看清单里多一行、参数个数对不对。
  2. 把某个过程的源码字段清空,看第四组打印出什么。
  3. 给字典加一个「创建时间」字段,模拟 CREATED 那一列。

常见误区

  1. 在沙箱里找 SHOW PROCEDURE STATUS,找不到就以为是自己写错了。
  2. 以为 PROCESSLIST 里能看到过程名,其实显示的是里面那条 SQL。
  3. 只靠 SHOW CREATE 找源码,改过一轮之后就分不清哪个是当前版本。
自测三问(点开对答案) 1. 为什么说「过程是可以被查询的对象」? —— 因为它存在数据字典里,能像表一样被列出、被导出、被备份,而不是只活在内存里的代码。 `information_schema.ROUTINES` 就是它的查询入口。 2. 三种查法里,哪一种最适合写进脚本? —— `information_schema.ROUTINES`。 它是普通查询,能加 `WHERE`、能 `JOIN`、能导出,前两种是给人看的固定格式。 3. `PROCESSLIST` 里为什么搜不到过程名? —— 因为显示的是过程内部正在执行的那条 SQL。想筛出来得按那条 SQL 的特征去 `LIKE`。

查清了,下一篇讲怎么改:ALTER 只能改壳,逻辑必须删了重写。

相关笔记