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

ALTER PROCEDURE 只能改「特性」:注释、安全类型、确定性声明,改不了过程体。 改逻辑只能 DROP 加 CREATE,中间那一下空档是真的,正在跑的调用会失败。 源码别只存在服务器上——看不见、搜不到、diff 不了,这是过程难维护的根。

📝 本章和前面几章的关系

14.4 讲 ALTER PROCEDURE,很短。但里面有一句话最容易被忽略:它改不了过程体。 上一篇讲怎么查,这一篇讲怎么改,下一篇讲怎么删。 这一篇的重点其实不在语法,在「改的时候线上怎么办」。

一、ALTER 能改的:只有特性

能改的特性 意思
COMMENT 给人看的说明
SQL SECURITY 按创建者还是调用者的身份执行
CHARACTERISTIC 如「结果确定」「只读不写」这类声明

这三样都不是逻辑,只是「元信息」。 想改一条 WHERE 条件?ALTER 办不到,一行都不行。 SQL SECURITY 这一项值得单独说:默认是 DEFINER,也就是「按建它的人的身份去查表」。 所以建它的账号一旦被删,过程可能整个失效——这是运维上真实会踩的坑。

二、改逻辑:删了重建

同一个调用,两版给的结果不一样:v1 返 1 列 2 行,v2 返 2 列 1 行。 这就是「逻辑变了」的证据,而不只是「改了个注释」。

版本 返回几列 几行
v1 1 2
v2 2 1

MySQL 的写法是 DROP PROCEDURE 加 CREATE PROCEDURE 两句。 注意:视图有 CREATE OR REPLACE,过程没有——这一点很多人以为有。 所以改过程天生是一个「两步动作」,中间必然存在空档。

三、中间那一下空档是真的

删掉之后、重建之前调它,拿到的是「不存在」。 MySQL 那边是错误码 1305:PROCEDURE shop.p_list_cat does not exist。

时机 调用结果
删除之前 正常返回
删除之后、重建之前 报「不存在」

线上改过程的风险全在这一瞬间:正在跑的调用会失败。 所以改之前要看有没有人在调,窗口要挑低峰。 更稳的做法是「先建一个新名字,把调用方切过去,再把旧的删掉」—— 多留一个名字,换来「随时能切回来」。

四、源码存哪:别只存在服务器上

存法 好处 坏处
只在服务器上 省事 改过就找不回旧的
存在 SQL 文件里 能进版本库 容易和线上不一致
存在表里 能查能比对 要自己维护

第三种在实践里最常见:建一张 tb_proc_src(name, ver, code, updated), 每次发布把源码写进去,改的时候先查历史版本。 这不是数据库课的内容,但它是「过程为什么难维护」的真正答案: 代码跑在服务器里,看不见、搜不到、diff 不了。 把它当代码管,问题就少一半。

五、动手 Demo:ALTER 改不了逻辑,那就删了重写

这一段先看 ALTER 能改什么(原码),再用复刻版演示「删了重建」:同一个调用、两版结果不同, 中间那段空档也照实跑出来。

import inspect
REG = {}

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

def call(name, *a):
    if name not in REG:
        raise KeyError(u"PROCEDURE %s does not exist" % name)
    return REG[name]["fn"](*a)

# ① v1:只取名字
def v1(cat_id):
    return c.execute(u"SELECT name FROM tb_goods WHERE cat_id = ?",
                     (cat_id,)).fetchall()

create(u"p_list_cat", v1, u"v1: SELECT name ...")
print(call(u"p_list_cat", 1))

# ② 改逻辑:只能删了重建
def v2(cat_id):
    return c.execute(u"SELECT name, price FROM tb_goods "
                      u"WHERE cat_id = ? AND price < 200", (cat_id,)).fetchall()

del REG[u"p_list_cat"]
try:
    call(u"p_list_cat", 1)
except KeyError as e:
    print(e.args[0])
create(u"p_list_cat", v2, u"v2: SELECT name, price ...")
print(call(u"p_list_cat", 1))

实跑输出:

--------------------------------------------------------------------
第六十三课:改过程——能改的只有壳,逻辑必须重写
--------------------------------------------------------------------

  **教材 14.4 讲 `ALTER PROCEDURE`。这一节很短,但有一句话最容易被忽略:**
  **它只能改「特性」,不能改过程体。**
  **这一课把「能改什么」和「必须重建什么」分开跑一遍。**

  一、ALTER 能改的:特性
  --------------------------------------------------------------------

  [教材原码 · 本沙箱不执行]
  | ALTER PROCEDURE p_list_cat COMMENT '按类别列商品(v2)';
  教材原码,改注释


  [教材原码 · 本沙箱不执行]
  | ALTER PROCEDURE p_list_cat SQL SECURITY INVOKER;
  教材原码,改安全类型:按调用者身份执行


  能改的特性        意思
  ----------------------------------------------------------------
  `COMMENT`         给人看的说明
  `SQL SECURITY`    `DEFINER` 按创建者身份/`INVOKER` 按调用者身份
  `CHARACTERISTIC`  如 `DETERMINISTIC`、`READS SQL DATA`
  **注意这三样都不是逻辑,只是「元信息」。**
  **想改一条 `WHERE` 条件?`ALTER` 办不到。**

  二、改逻辑:删了重建
  --------------------------------------------------------------------

  版本  返回几列  几行
  --------------------
  v1    1         2
  v2    2         1
  **同一次调用,v1 给 1 列 2 行,v2 给 2 列 1 行。**
  **逻辑变了的证据就在这两个数上。**
  **MySQL 里的写法是 `DROP PROCEDURE` 加 `CREATE PROCEDURE` 两句,**
  **沙箱复刻版同样是「先删再登记」,动作一模一样。**

  三、中间那一下空档是真的
  --------------------------------------------------------------------

  时机                调用结果
  -------------------------------------------------------
  删除之前            正常返回 1 行
  删除之后、重建之前  PROCEDURE p_list_cat does not exist
  **中间态就是这句 `PROCEDURE p_list_cat does not exist`。**
  **线上改过程的风险全在这一瞬间:正在跑的调用会失败。**
  **所以改之前要看有没有人在调,改的窗口要挑低峰。**
  **MySQL 没有 `CREATE OR REPLACE PROCEDURE` 这种写法(视图有,过程没有),**
  **所以这个空档在 MySQL 里也避不开。**

  四、源码存哪:别只存在服务器上
  --------------------------------------------------------------------

  存法             好处        坏处
  ---------------------------------------------
  只在服务器上     省事        改过就找不回旧的
  存在 SQL 文件里  能进版本库  容易和线上不一致
  存在表里         能查能比对  要自己维护
  **第三种在实践里最常见:建一张 `tb_proc_src(name, ver, code, updated)`,**
  **每次发布把源码写进去,改的时候先查历史版本。**
  **这不是数据库课的内容,但它是「过程为什么难维护」的真正答案:**
  **代码跑在服务器里,看不见、搜不到、diff 不了。**

第一组一行:v1 只返回名字,cat_id = 1 有两条。 第二组先删再调,打印出 PROCEDURE p_list_cat does not exist—— 这就是「中间空档」在线上会发生的事。 第三组一行:v2 返回两列、只剩一条(加了 price < 200 的条件)。 同一次调用、两个结果,逻辑变了的证据就在这。 整段的重点不是 Python,是那个空档:DROP 和 CREATE 之间,调用一定会失败。 MySQL 里也一样,因为它没有 CREATE OR REPLACE PROCEDURE。

30 秒实验

  1. 把「先删再建」改成「先建新名字、再删旧名字」,看还会不会有失败窗口。
  2. 给 create 加一个版本号字段,改两次之后看历史版本还在不在。
  3. 把 call 的异常改成返回 None,看空档期调用会不会静默失败——这更危险。

常见误区

  1. 以为 ALTER PROCEDURE 能改 WHERE 条件,试了半天只改了注释。
  2. 以为有 CREATE OR REPLACE PROCEDURE,照着视图的写法敲,报语法错。
  3. 在高峰期直接 DROP 再 CREATE,正在跑的调用全断。
自测三问(点开对答案) 1. `ALTER PROCEDURE` 能改什么? —— 只能改特性:注释、`SQL SECURITY`(按谁的身份执行)、`CHARACTERISTIC` 声明。 过程体一行都改不了,改逻辑必须删了重建。 2. 为什么改过程一定有「空档」? —— 因为 MySQL 没有 `CREATE OR REPLACE PROCEDURE`,只能先 `DROP` 再 `CREATE`, 两步之间过程不存在。 3. `SQL SECURITY` 默认是什么,有什么风险? —— 默认 `DEFINER`,按建它的人的身份去查表。所以建它的账号一被删,过程可能整个失效。

改完了,最后一篇讲删:删掉之后调用报什么、删之前该查什么。

相关笔记