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 秒实验
- 把「先删再建」改成「先建新名字、再删旧名字」,看还会不会有失败窗口。
- 给
create加一个版本号字段,改两次之后看历史版本还在不在。 - 把
call的异常改成返回None,看空档期调用会不会静默失败——这更危险。
常见误区
- 以为
ALTER PROCEDURE能改WHERE条件,试了半天只改了注释。 - 以为有
CREATE OR REPLACE PROCEDURE,照着视图的写法敲,报语法错。 - 在高峰期直接
DROP再CREATE,正在跑的调用全断。
自测三问(点开对答案)
1. `ALTER PROCEDURE` 能改什么? —— 只能改特性:注释、`SQL SECURITY`(按谁的身份执行)、`CHARACTERISTIC` 声明。 过程体一行都改不了,改逻辑必须删了重建。 2. 为什么改过程一定有「空档」? —— 因为 MySQL 没有 `CREATE OR REPLACE PROCEDURE`,只能先 `DROP` 再 `CREATE`, 两步之间过程不存在。 3. `SQL SECURITY` 默认是什么,有什么风险? —— 默认 `DEFINER`,按建它的人的身份去查表。所以建它的账号一被删,过程可能整个失效。改完了,最后一篇讲删:删掉之后调用报什么、删之前该查什么。
- ☐ (本篇完,下一篇 14.5 删除存储过程和存储函数)