源码分析 · MySQL Event 源码分析

Author: yifei

背景介绍

mysql5.1版本开始引进Event概念。Event,顾名思义,属于时间触发器,相比于triggers的事件触发器来说,Event更加类似与linux crontab计划任务,Event中可以使用单独的SQL语句或调用使用存储过程,在某一特定的时间点,触发相关的SQL语句或存储过程。

使用Event调用SQL语句时,用户需要具有Event权限和执行该SQL的权限。Event权限的设置保存在mysql.user表和mysql.db表的Event_priv字段中。

Eventprocedure配合使用的时候,用户需要具有create routine权限和调用存储过程执行时的excute权限,存储过程调用具体的SQL语句时,需要用户具有执行该SQL的权限。Event可以通过以下几种方式查看其详细信息:

  • 通过SHOW EVENTS命令。
  • 查询information_schema.events表。
  • SHOW CREATE EVENT event_name命令。

本文基于官方mysql-8.0.31版本的代码,对Event的实现进行分析。

源码分析

在使用Event的过程中,可以看到Event的主要功能包括:

  • 创建,删除,修改对应的Event
  • Event的定期调度
  • Event的解析与执行 下面结合源码对以上几个问题进行分析

数据结构

Event的实现架构主要包括Event,Event_schedular,Event_queue,Event_queue_element,Event_db_repository,Event_dd,Event_parse_data等几个类,主要关系如下: UML 当用户输入Event相关语句时,首先会通过Event_parse_data将SQL转换为Event结构,随后在mysql_execute_command中调用Events::create_event创建新的Event事件。Events类是Event功能对外暴露的静态接口类,其中包括一个Event_schedularEvent_queueEvent_queue是包括所有Event事件的队列,Event_schedular是Event线程的后台调度器,负责从Event_queue中拿出对应的Event运行。Event_queue中包括一个由Event_queue_element构成的vector数组,每一个Event_queue_element都是一个Event事件,它继承于Event_basic,包括单个Event事件所需要的所有参数。

Events::create_event的过程中,还会调用Event_db_repository::create_event,把对应的Event事件插入到dd中,用来帮助用户在Information_schema中查看对应的Event。Event_db_repository也是一个接口类,其实际的实现还是在dd_event类中。

执行流程

调度流程

  1. // Event init
  2. |--> Events::init
  3. | |--> opt_event_scheduler == Events::EVENTS_DISABLED // 检查环境变量
  4. | |--> check_access // 检查权限
  5. | |--> event_queue = new Event_queue() // 创建新的 queue
  6. | |--> scheduler = new Event_scheduler(event_queue) // 创建新的调度器
  7. | |--> event_queue->init_queue() // 初始化队列
  8. | |--> load_events_from_db(thd, event_queue) // 从存储层读取对应的Event
  9. // 创建新的Events
  10. |--> Events::create_event
  11. | |--> check_access // 检查权限
  12. | |--> Event_db_repository::create_event // 创建Event的dd信息
  13. | |--> event_queue->create_event // 创建Event_queue_element
  14. | | |--> new_element->compute_next_execution_time(thd); // 计算新的event的执行时间
  15. | | |--> queue.push(new_element); // 把新的event插入到队列中
  16. | | |--> mysql_cond_broadcast(&COND_queue_state); // 通知Event_schedular
  17. | |--> end
  18. // Events_schedular 后台执行
  19. |--> Events::start
  20. | |--> scheduler->start
  21. | | |--> scheduler->run
  22. | | | |--> queue->recalculate_activation_times(thd) // 重新计算所有event的执行时间
  23. | | | |--> while (is_running())
  24. | | | | |--> get_top_for_execution_if_time(thd, &event_name) // 获得最早执行的event
  25. | | | | | |--> while(true)
  26. | | | | | | |--> if (queue.empty())
  27. | | | | | | | |--> cond_wait(thd); // 等待新的event事件插入
  28. | | | | | | |--> endif
  29. | | | | | | |--> top = queue.top()
  30. | | | | |--> res = execute_top // 执行event
  31. | | | |--> end loop

Events::initEvent功能最开始的初始化入口,如果当前系统的环境变量 event_schedularON,就会启动Event_schedular线程,来控制event的执行。此时,这个线程可以通过 show processlist; 命令查看到。

  1. mysql> show processlist;
  2. +----+-----------------+-----------+-------+---------+--------+-----------------------------+------------------+
  3. | Id | User | Host | db | Command | Time | State | Info |
  4. +----+-----------------+-----------+-------+---------+--------+-----------------------------+------------------+
  5. | 1 | event_scheduler | localhost | NULL | Daemon | 3 | Waiting for next activation | NULL |
  6. +----+-----------------+-----------+-------+---------+--------+-----------------------------+------------------+
  7. 1 rows in set (0.00 sec)

event_queue是一个基于std::vector<>的排序队列,mysql在其中实现了event_queue_element的排序算法,是基于时间的,因此从queue上通过get_topevent一定是最新要执行的。如果当前queue是空的,则会等待新event的插入,并唤醒当前的schedular线程。当拿到最新的Event_queue_element之后,会由Event_worker_thread::run 来真正执行当前的event

实际执行

  1. // Event_worker_thread::run
  2. |--> post_init_event_thread(thd); // 初始化当前线程的THD
  3. |--> mysql_thread_set_secondary_engine(false); // 设置不支持secondary_engine
  4. |--> thd->mdl_context.acquire_lock // 获得mdl锁
  5. |--> Event_db_repository::load_named_event // 从dd中读取对应的event的详细信息
  6. |--> job_data.execute
  7. | |--> mysql_reset_thd_for_next_command(thd); // 重新设执行环境
  8. | |--> event_sctx.change_security_context // 修改event线程的权限
  9. | |--> construct_sp_sql(thd, &sp_sql) // 构建存储过程
  10. | |--> thd->set_query(sp_sql.c_ptr_safe(), sp_sql.length()); // 设置query
  11. | |--> parse_sql(thd, &parser_state, m_creation_ctx) // sql parse
  12. | |--> sphead->execute_procedure(thd, &empty_item_list) // 执行存储过程
  13. | |--> end:
  14. | |--> construct_drop_event_sql(thd, &sp_sql, m_schema_name, m_event_name) // 如果event只执行一次,就删除
  15. | |--> event_sctx.restore_security_context // 重新设置权限

Event的真正执行是靠Event_worker_thread::run来实现的,执行过程是通过construct_sp_sql,把用户创建Event时的sql包在一个存储过程中,按照存储过程的方式去执行。这样做的好处是用户可以在event中使用存储过程,方便创建比较复杂的Event。同时,Event执行前会由系统赋予对应的权限,则是因为Event_worker_thread是由event_schedular创建的,没有对应的权限信息。因此mysql的做法时在create event时检查客户是否有对应的权限,在Event_worker_thread真正执行时,已经可以保证当前sql是有权限的了,所以不需要再一次检查。

总结

总的来说,Event的执行流程还是非常清晰的,其流程如下所示:

Event流程图 Event_queue是一个按事件排序的Event队列,当到达Event的执行时间时,Event_schedular会被唤醒,并从从Event_queu中拿出需要执行的Event,创建新的Event_worker_thread来执行对应的Event。当前Event如果是循环执行的话,会再次被插入到Event_queue中,等待下一次调度。

整个实现的过程还包含了很多的细节,本文都没有进行详细的展开,包括dd的写入,主从集群的Event调度等,后续有机会再展开介绍。

原文:http://mysql.taobao.org/monthly/2022/10/03/