🛢️ 第17章 事件
📄 一句话概括

DROP EVENT 只有两种写法:带 IF EXISTS 和不带,差别在「可重入」与「报错」。 删完必须验:事件真的不跑了、注册表真的空了——只看一句「删除成功」不够。 批量删要先按前缀筛名单,再逐个删;图省事写成「清空所有」,别的任务一起没。

📝 本章和前面几章的关系

第 17 章最后一篇。教材 17.4 只有删除这一节,17.5 小结和 17.6 实践与练习并进本篇末尾。 删除这件事本身很短,但「删完之后怎么确认」不短——这一篇的重心在后半段。 和第 14 章删过程、第 15 章删触发器是同一套纪律:IF EXISTS 让脚本可重入, 但也会把「名字拼错」一起吞掉。

一、DROP EVENT 的两种写法

教材给了两种:带 IF EXISTS 和不带。

DROP EVENT e_log;
DROP EVENT IF EXISTS e_log;
写法 再来一次的结果
DROP EVENT e_log EVENT e_log does not exist
DROP EVENT IF EXISTS e_log 跳过,不报错

第二次报 EVENT e_log does not exist——这是好事,说明它没假装成功。 加了 IF EXISTS 就变成「跳过」,脚本可以反复跑而不中断。 代价照旧:它会把「名字拼错」也一起吞掉。 这一条和第 15 章删触发器、第 14 章删过程是同一个套路。 IF EXISTS 让脚本可重入,但也让错误静默;删完必须回头查一次。 判断标准很简单:这个脚本会不会被跑第二遍?会,就得带 IF EXISTS; 带了,就必须配一条「删完查一遍」的语句。

二、删完要验:事件真的不跑了

删掉一个事件,要验两件事:它不再执行,而且它真的从注册表里没了。 这两件事不一样——状态是 DISABLE 的事件也不执行,但它还在。

时机 跑了几次 事件数
删之前,推到 00:02 2 1
删之后,推到 00:04 0 0

删之前跑了 2 次,删之后一次都没有,注册表也空了。 这一步必须验:删错名字、删错库、权限不够而「看起来成功」的情况都有。 只看一句「删除成功」不够——很多客户端在语句成功时只打一行「OK」,不告诉你影响了几行、 删的是哪个库里的哪个对象。 所以验的动作要落到数据上:数一下、读一下、跑一下。

三、批量删:拿到名单再动手

要清掉一批带前缀的事件,先按前缀筛出名单,再逐个删。 复刻版里筛到 2 个:e_clean 和 e_clean_old。

名字 结果
e_clean 已删除
e_clean_old 已删除
e_clean_nope EVENT e_clean_nope does not exist

最后一个不存在,报错被接住,脚本继续——这就是「跳过不存在的、删掉该删的」。 再看剩下的:e_stat 和 e_keep 没被碰。 这两行是这一步真正的价值:按前缀筛,别的任务就保住了。 如果图省事写成「清空所有事件」,那两条一起没了,而且也是静默消失。 「先筛名单、再逐个删」这个套路可以推广到所有批量操作。 它的关键不是循环,是那一步「把名单打印出来看一眼」——人眼扫一遍,比任何断言都可靠。

四、本章小结与实践与练习

第 17 章四篇,真正要带走的是五条: 一、事件是「库内的定时任务」,和约束、触发器构成「库自己动」的三件事。 二、两层开关:全局 event_scheduler 默认 OFF,单个事件还有 ENABLE/DISABLE, 两层都得开。 三、ON SCHEDULE 只有 AT 和 EVERY 两种;不给 STARTS 时起点是建事件的时刻。 四、ON COMPLETION 决定跑完留不留;不留就等于「连记录都没有了」,慎用。 五、补不补跑是引擎行为,不是你能要求的;要补就在事件体里查断点。 这一章最该记住的一句是:事件是「看不见的进程」。 过程要有人 CALL,视图要有人 SELECT,触发器至少还挂在某张表上——事件什么都没有, 它只在时间里存在。 所以它出问题的方式也很特别:不报错、不告警,就是「昨天那份统计没生成」。 要防这个,只有两条:一是把事件列进监控(查 SHOW EVENTS 的 LAST_EXECUTED), 二是让事件体自己写日志。 17.6 的练习可以挑这三道: 建一个每分钟跑一次的事件,让它往日志表写一行,跑十分钟后数行数; 给同一批事件加 ENDS,把它设成五分钟之后,观察它自己变 DISABLE; 写一段脚本,按名字前缀列出、确认、批量删,删完再查一遍还剩什么。 三道都动手做过,这一章就算真读完了。

五、动手 Demo:删之前、删之后、批量删,三步都要验

这一段把删除的三件事跑一遍:两种 DROP 写法各试一次、 删之前推一次时钟再删之后再推一次(看跑的次数从 2 变 0)、最后按前缀筛出名单批量删, 末尾故意混一个不存在的名字。

BASE = datetime.datetime(2026, 11, 1, 0, 0, 0)
s = Sched(BASE)
s.create("e_log", every=60)

# ① 两种写法
s.drop("e_log")
try:
    s.drop("e_log")                    # 第二次:EVENT e_log does not exist
except KeyError as e:
    print(e.args[0])

def drop_if(name):                     # 相当于 DROP EVENT IF EXISTS
    try:
        s.drop(name)
        return u"已删除"
    except KeyError:
        return u"跳过(不存在)"

print(drop_if("e_log"))                # 跳过(不存在)

# ② 删完要验:跑的次数和注册表一起看
s.create("e_run", every=60)
print(len(s.tick(BASE + datetime.timedelta(minutes=2))), len(s.ev))   # 2 1
s.drop("e_run")
print(len(s.tick(BASE + datetime.timedelta(minutes=4))), len(s.ev))   # 0 0

# ③ 批量删:先筛名单,再逐个删
for nm in ("e_clean", "e_clean_old", "e_stat", "e_keep"):
    s.create(nm, every=3600)
names = sorted(k for k in s.ev if k.startswith("e_clean"))
print("筛到", len(names), names)
for nm in names + ["e_clean_nope"]:
    print(nm, drop_if(nm))
print("剩下", sorted(s.ev))            # ['e_keep', 'e_stat']

实跑输出:

--------------------------------------------------------------------
删除事件:删完记得回头看一眼
--------------------------------------------------------------------


  DROP EVENT 的两种写法
  --------------------------------------------------------------------

  [教材原码 · 本沙箱不执行]
  | DROP EVENT e_log;
  | DROP EVENT IF EXISTS e_log;


  写法                结果
  ----------------------------------------------
  `DROP EVENT e_log`  删掉了
  再来一次            EVENT e_log does not exist

**第二次报 `EVENT e_log does not exist`——这是好事,说明它没假装成功。**
**加了 `IF EXISTS` 就变成「跳过」,脚本可以反复跑而不中断。**
**代价照旧:它会把「名字拼错」也一起吞掉。**
**这一条和第 15 章删触发器、第 14 章删过程是同一个套路:**
**`IF EXISTS` 让脚本可重入,但也让错误静默;删完必须回头查一次。**

  删完要验:事件真的不跑了
  --------------------------------------------------------------------

  时机              跑了几次  事件数
  ----------------------------------
  删之前推到 00:02  2         1
  删之后推到 00:04  0         0

**删之前跑了 2 次,删之后一次都没有,注册表也空了。**
**这一步必须验:删错名字、删错库、权限不够而「看起来成功」的情况都有,只看一句「删除成功」不够。**

  批量删:拿到名单再动手
  --------------------------------------------------------------------
**要清掉一批带前缀的事件,先按前缀筛出名单:**
  筛到 2 个:e_clean、e_clean_old

  名字          结果
  -----------------------------------------------
  e_clean       已删除
  e_clean_old   已删除
  e_clean_nope  EVENT e_clean_nope does not exist

**最后一个不存在,报错被接住,脚本继续——这就是「跳过不存在的、删掉该删的」。**
**再看剩下的:`e_stat` 和 `e_keep` 没被碰。**

  剩下的事件
  ----------
  e_keep
  e_stat

**这两行是这一步真正的价值:按前缀筛,别的任务就保住了。**
**如果图省事写成「清空所有事件」,那两条一起没了,而且也是静默消失。**

第①步那个报错文本是原样的:EVENT e_log does not exist。 注意它说的是 does not exist,不是 already dropped——库只告诉你「现在没有」, 不告诉你「是不是你删的」。 第②步两组数要一起读:2 1 和 0 0。 前一组是「跑了两次、库里还有一个」,后一组是「一次没跑、库里空了」。 只看「跑的次数」会漏掉「对象还在」这种情况,只看「事件数」会漏掉「状态停用但没删」。 第③步最后那行 ['e_keep', 'e_stat'] 是整段的目的:批量删不等于清空, 删的必须只是筛出来的那一批。 漏掉这一步,一次「清理」就可能把定时统计、定时备份一起带走。

30 秒实验

  1. 把第②步的顺序改成「先删、再推时钟」,看两次打印分别是什么, 说明为什么这样验不出「它真的不跑了」。
  2. 给批量删加一条保护:如果筛出来的名单超过 5 个就中止,打印名单让人确认。
  3. 把 e_clean 换成 e_ 前缀再跑一次,看会删掉哪些,再想想这个前缀该不该这么写。

常见误区

  1. 以为 DROP EVENT 之后事件就不存在了、什么都不用管。删错名字、删错库都可能「看起来成功」, 删完必须查一遍。
  2. 以为 IF EXISTS 是纯好处。它会把「名字拼错」也一起吞掉,脚本不中断但事情没做成。
  3. 批量删时图省事写成「清空所有事件」。按前缀筛出名单再删,是保住别的任务的唯一办法。
自测三问(点开对答案) 1. `DROP EVENT` 带不带 `IF EXISTS`,差别在哪? —— 不带:事件不存在时报错,脚本中断。带:跳过,脚本继续。 前者适合「必须删掉」的场景,后者适合「可重复执行的部署脚本」。 但后者会把名字拼错也吞掉,必须配一条删后复查。 2. 验证一个事件真被删掉了,要验哪两件事? —— 一是它不再执行(推一次时钟,跑的次数为 0),二是它从注册表里没了(事件数为 0)。 只验一件不够:状态 `DISABLE` 的事件也不执行,但它还在。 3. 批量删之前为什么要先把名单打印出来? —— 因为「按前缀筛」这一步本身就可能筛错。 人眼扫一遍名单,比任何断言都可靠——尤其是前缀写短了一位的时候。

第 17 章到这里结束。下一篇进入第 18 章:MySQL 用户管理——从「谁能连上来」讲起。

相关笔记