DROP PROCEDURE 和 DROP FUNCTION 是两个命令,不能混用。
删掉之后调用报 1305「不存在」,而且往往不是当场发现,是半夜定时任务报错。
IF EXISTS 在批量脚本里几乎必加,不加会在第一个不存在的名字上中断。
14.5 只有一句话:DROP PROCEDURE。这一篇补两件事:删掉之后会怎样、删之前该查什么。
这一篇是第 14 章的收尾,后面 14.6 小结和 14.7 实践与练习并进本篇末尾。
上一篇讲改,这一篇讲删。两篇合起来才算「过程的日常维护」。
一、DROP 的写法
DROP PROCEDURE p_list_cat;
DROP FUNCTION goods_level;
DROP PROCEDURE IF EXISTS p_list_cat;
第一条和第二条不能互换:过程和函数是两个命名空间,DROP PROCEDURE 删不掉函数。
这一点容易错,因为建的时候 CREATE 后面也是两种写法,但那时错了会立刻报错。
第三条在脚本里几乎必加。
不加的话,删一个不存在的名字会中断整个脚本,后面的语句全都不执行。
二、删掉之后调用:报「不存在」
删之前正常返回,删之后报 PROCEDURE p_tmp does not exist。
MySQL 那边是错误码 1305。
| 时机 | 调用结果 |
|---|---|
| 删除之前 | 正常返回 |
| 删除之后 | 报 1305「不存在」 |
麻烦的是这类错误往往不是马上出现。 删的时候一切正常,出问题的时候是半夜的定时任务、早上的报表生成—— 中间隔了几个小时,中间还可能有别的变更,排查会很绕。 所以删之前要确认「谁在调」,而不是「现在没人抱怨」。
三、删之前先查谁在调
| 谁可能引用它 | 怎么查 |
|---|---|
| 应用代码 | 全局搜 CALL 过程名 |
| 其它过程 | 搜 CALL 和过程名 |
| 视图/触发器 | 放不进过程,可排除 |
| 定时任务 | 查事件调度器里的 CALL |
第三行是个好消息:视图和触发器里只能放函数,放不进过程,所以排查范围比删表小得多。 但函数就不一样了——函数能进视图定义,删函数之前得把视图也捋一遍。 第二行要注意:过程调过程很常见,删底层那个之前得先把上层理一遍。 这层依赖数据库不帮你记,只能靠搜代码。
四、批量删:脚本里的稳妥写法
一次删三个,其中 p_nope 根本不存在:前两个删掉,第三个跳过,脚本继续。
这就是 IF EXISTS 的作用;不带它的话,循环到第三个就中断了。
| 过程名 | 结果 |
|---|---|
p_a |
删掉了 |
p_b |
删掉了 |
p_nope |
不存在,跳过 |
实践里还会加两道保险:删之前先 SHOW PROCEDURE STATUS LIKE '前缀%' 列一遍,
删之后再用同一条查一次确认清空。
第 14 章到这里结束。这一章真正要带走的是三条:
过程是「收进名字里的 SQL」,函数是「能写进 SQL 的一个值」;
DELIMITER 是客户端指令不是 SQL;改过程只能删了重写,中间有空档。
14.7 的练习可以挑这三道:写一个带 IN 和 OUT 的过程、把一段重复的 INSERT 改写成过程、
给一个已经上线的过程做「改名切换」的迁移。
三道都动手做过,这一章就算真读完了。
五、动手 Demo:删之前、删之后、批量删
这一段把 DROP 的三种写法贴成原码,再用复刻版跑一遍:删之前调一次、删之后再调一次,
最后演示批量删时 IF EXISTS 怎么救场。
REG = {}
def create(name, fn, code):
REG[name] = {"fn": fn, "code": code}
def call(name, *a):
if name not in REG:
raise KeyError(u"PROCEDURE %s does not exist" % name)
return REG[name]["fn"](*a)
def drop(name):
if name not in REG:
raise KeyError(u"PROCEDURE %s does not exist" % name)
del REG[name]
# ① 删之前能调,删之后不能
create(u"p_tmp", lambda n: list(range(n)), u"CREATE PROCEDURE p_tmp(...)")
print(call(u"p_tmp", 3))
drop(u"p_tmp")
try:
call(u"p_tmp", 3)
except KeyError as e:
print(e.args[0])
# ② 批量删:不存在的那个要能跳过
for nm in (u"p_a", u"p_b", u"p_c"):
create(nm, lambda: None, u"CREATE PROCEDURE %s()" % nm)
print(len(REG))
for nm in (u"p_a", u"p_b", u"p_nope"):
try:
drop(nm)
print(nm, u"删掉了")
except KeyError:
print(nm, u"不存在,跳过")
print(len(REG))
实跑输出:
--------------------------------------------------------------------
第六十四课:删过程,以及删之前该看什么
--------------------------------------------------------------------
**教材 14.5 讲 `DROP PROCEDURE`,一句话。**
**这一课补两件教材没细说的事:删掉之后调用会怎样、删之前该查什么。**
一、DROP 的写法
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| DROP PROCEDURE p_list_cat;
教材原码,删过程
[教材原码 · 本沙箱不执行]
| DROP FUNCTION goods_level;
教材原码,删函数要用 DROP FUNCTION,不能混
[教材原码 · 本沙箱不执行]
| DROP PROCEDURE IF EXISTS p_list_cat;
教材原码,加 IF EXISTS 之后,删不存在的也不报错
**第一条和第二条不能互换:过程和函数是两个命名空间,`DROP PROCEDURE` 删不掉函数。**
**第三条在脚本里几乎必加,不然重复执行会中断。**
二、删掉之后调用:报「不存在」
--------------------------------------------------------------------
时机 调用结果
----------------------------------------
删除之前 正常,返回 2 行
删除之后 PROCEDURE p_tmp does not exist
**MySQL 那边对应的是错误码 1305:`PROCEDURE shop.p_tmp does not exist`。**
**数字记不住没关系,记住「删了之后调用会报不存在」这件事就够了。**
**麻烦的是这类错误往往不是马上出现:定时任务半夜跑、报表早上生成,**
**删的时候一切正常,出问题的时候已经过了几个小时。**
三、删之前先查谁在调
--------------------------------------------------------------------
谁可能引用它 怎么查
----------------------------------------
应用代码 全局搜 `CALL 过程名`
其它过程 搜 `CALL` 和过程名
视图/触发器 过程不能被它们调用,可排除
定时任务 查事件调度器里的 `CALL`
**第三行是个好消息:视图和触发器里放不进过程(只能放函数),**
**所以排查范围比删表小得多。**
**第二行要注意:过程调过程是常见写法,删底层那个之前得先把上层捋一遍。**
**这一层关系数据库不帮你记,只能靠搜代码。**
四、批量删:脚本里的稳妥写法
--------------------------------------------------------------------
**先建三个,验证一下批量删:**
过程名 结果
--------------------
p_a 删掉了
p_b 删掉了
p_nope 不存在,跳过
时机 剩下几个
----------------
删之前 3
删之后 1
**`p_nope` 那一行被跳过了,脚本继续往下跑——这就是 `IF EXISTS` 的作用。**
**不带它的话,循环到第三个就中断,后面该删的都没删。**
**第 14 章到这里结束。这一章真正要带走的是三条:**
**过程是「收进名字里的 SQL」,函数是「能写进 SQL 的一个值」;**
**`DELIMITER` 是客户端指令不是 SQL;改过程只能改了重写,中间有空档。**
第一组两行:删之前拿到 [0, 1, 2],删之后报 PROCEDURE p_tmp does not exist。
MySQL 那边就是错误码 1305,一字不差的语义。
第二组第一个数是 3,说明三个都建上了;后面三行是批量删的过程,p_nope 被跳过。
最后一组那个数是 1:p_a、p_b 删掉了,p_c 不在删除名单里,还留着。
注意 p_nope 那一行没有中断脚本——这就是 IF EXISTS 的价值,也是它能进教科书的原因。
30 秒实验
- 把批量删里
try/except去掉,看第三个名字会不会让整个循环停下。 - 删掉一个过程之后再建同名的,看是不是「全新」的一个(有没有残留)。
- 写一个「先建新名字、切调用、再删旧名字」的三步脚本,跑一遍。
常见误区
- 用
DROP PROCEDURE去删函数,报错之后以为名字写错了。 - 批量删不带
IF EXISTS,第一个不存在就整段中断。 - 删之前只在测试环境验证过,生产上还有定时任务在调。
自测三问(点开对答案)
1. 删掉过程之后调用会报什么? —— MySQL 报错误码 1305,提示这个 PROCEDURE 不存在。 麻烦的是它往往延迟出现,半夜定时任务跑的时候才暴露。 2. 为什么批量删要加 `IF EXISTS`? —— 不加的话,删到一个不存在的名字就会中断执行,后面的语句全不跑。 加了这个关键字,不存在的会被跳过,脚本能继续。 3. 删过程之前要排查哪些引用? —— 应用代码里的 `CALL`、其它过程里的 `CALL`、定时任务里的 `CALL`。 视图和触发器里放不进过程,可以排除——但函数不行,函数能进视图定义。第 14 章到这里结束。下一篇进第 15 章:触发器,让数据库自己在写入前后做事。
- ☐ (本篇完,下一篇 15.1 MySQL 触发器)