MySQL · 源码分析 · SHUTDOWN过程

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介:

ORACLE 中的SHUTDOWN

MySQL SHUTDOWN LEVEL 暂时只有一种,源码中留了 LEVEL 的坑还没填

在此借用 Oracle 的 SHUTDOWN LEVEL 分析

Oracle SHUTDOWN LEVEL 共有四种:ABORT、IMMEDIATE、NORMAL、TRANSACTIONAL

ABORT

  • 立即结束所有SQL
  • 回滚未提交事务
  • 断开所有用户连接
  • 下次启动实例时,需要recovery

IMMEDIATE

  • 允许正在运行的SQL执行完毕
  • 回滚未提交事务
  • 断开所有用户连接

NORMAL

  • 不允许建立新连接
  • 等待当前连接断开
  • 下次启动实例时,不需要recovery

TRANSACTIONAL

  • 等待事务提交或结束
  • 不允许新建连接
  • 事务提交或结束后断开连接

MySQL 中的 SHUTDOWN 实际相当于 Oracle 中的 SHUTDOWN IMMEDIATE,重启实例时无需recovery,但回滚事务的过程可能耗时很长

MySQL SHUTDOWN过程分析

  • mysql_shutdown 发送SHUTDOWN命令
  • dispatch_command() 接受到 COM_SHUTDOWN command,调用kill_mysql()
  • kill_mysql()创建 kill_server_thread
  • kill_server_thread 调用 kill_server()
  • kill_server()
    •  close_connections() 
    • 关闭端口
    • 断开连接
    • 回滚事务(可能耗时很长)
    • unireg_end
    • clean_up innobase_shutdown_for_mysql
    • delete_pid_file

InnoDB shutdown 速度取决于参数 innodb_fast_shutdown

0: 最慢,需等待purge完成,change buffer merge完成
1: default, 不需要等待purge完成和change buffer merge完成

2: 不等待后台删除表完成,row_drop_tables_for_mysql_in_background 不等刷脏页,如果设置了innodb_buffer_pool_dump_at_shutdown,不需要去buffer dump.

case COM_SHUTDOWN: // 接受到SHUTDOWN命令
 {
 if (packet_length < 1)
 { 
 my_error(ER_MALFORMED_PACKET, MYF(0));
 break;
 } 
 status_var_increment(thd->status_var.com_other);
 if (check_global_access(thd,SHUTDOWN_ACL)) // 检查权限 break; /* purecov: inspected */ /* 
 If the client is < 4.1.3, it is going to send us no argument; then
 packet_length is 0, packet[0] is the end 0 of the packet. Note that
 SHUTDOWN_DEFAULT is 0. If client is >= 4.1.3, the shutdown level is in
 packet[0].
 */ enum mysql_enum_shutdown_level level; // 留的坑,default以外的LEVEL都没实现 if (!thd->is_valid_time())
 level= SHUTDOWN_DEFAULT; 
 else 
 level= (enum mysql_enum_shutdown_level) (uchar) packet[0];
 if (level == SHUTDOWN_DEFAULT)
 level= SHUTDOWN_WAIT_ALL_BUFFERS; // soon default will be configurable else if (level != SHUTDOWN_WAIT_ALL_BUFFERS)
 { 
 my_error(ER_NOT_SUPPORTED_YET, MYF(0), "this shutdown level");
 break;
 } 
 DBUG_PRINT("quit",("Got shutdown command for level %u", level));
 general_log_print(thd, command, NullS); // 记录general_log
 my_eof(thd);
 kill_mysql(); // 调用kill_mysql()函数,函数内部创建 kill_server_thread 线程
 error=TRUE;
 break;
 }

kill_server() 先调用 close_connections(),再调用 unireg_end()

static void __cdecl kill_server(int sig_ptr) {
	......
	close_connections();
 	if (sig != MYSQL_KILL_SIGNAL &&
 sig != 0) 
 unireg_abort(1); /* purecov: inspected */ else unireg_end();

结束线程的主要逻辑在 mysqld.cc:close_connections() 中

 static void close_connections(void)

 	......
 
 /* 下面这段代码结束监听端口 */ /* Abort listening to new connections */
 DBUG_PRINT("quit",("Closing sockets"));
 if (!opt_disable_networking )
 {
 if (mysql_socket_getfd(base_ip_sock) != INVALID_SOCKET)
 {
 (void) mysql_socket_shutdown(base_ip_sock, SHUT_RDWR);
 (void) mysql_socket_close(base_ip_sock);
 base_ip_sock= MYSQL_INVALID_SOCKET;
 }
 if (mysql_socket_getfd(extra_ip_sock) != INVALID_SOCKET)
 {
 (void) mysql_socket_shutdown(extra_ip_sock, SHUT_RDWR);
 (void) mysql_socket_close(extra_ip_sock);
 extra_ip_sock= MYSQL_INVALID_SOCKET;
 }
 }
 
 	......

 /* 第一遍遍历线程列表 */
 sql_print_information("Giving %d client threads a chance to die gracefully",
 static_cast<int>(get_thread_count()));

 mysql_mutex_lock(&LOCK_thread_count);
 
 Thread_iterator it= global_thread_list->begin();
 for (; it != global_thread_list->end(); ++it)
 {
 THD *tmp= *it;
 DBUG_PRINT("quit",("Informing thread %ld that it's time to die",
 tmp->thread_id));
 /* We skip slave threads & scheduler on this first loop through. */ /* 跳过 slave 相关线程,到 end_server() 函数内处理 */ if (tmp->slave_thread) 
 continue;
 if (tmp->get_command() == COM_BINLOG_DUMP ||
 tmp->get_command() == COM_BINLOG_DUMP_GTID)
 {
 ++dump_thread_count;
 continue;
 }
 
 /* 先标记为 KILL 给连接一个自我了断的机会 */
 tmp->killed= THD::KILL_CONNECTION;
 
 ......
 
 }
 mysql_mutex_unlock(&LOCK_thread_count);

 Events::deinit();

 sql_print_information("Shutting down slave threads");
 /* 此处断开 slave 相关线程 */
 end_slave();
 
 /* 第二遍遍历线程列表 */ if (dump_thread_count)
 { 
 /*
 Replication dump thread should be terminated after the clients are
 terminated. Wait for few more seconds for other sessions to end.
 */ while (get_thread_count() > dump_thread_count && dump_thread_kill_retries)
 {
 sleep(1);
 dump_thread_kill_retries--;
 }
 mysql_mutex_lock(&LOCK_thread_count);
 for (it= global_thread_list->begin(); it != global_thread_list->end(); ++it)
 {
 THD *tmp= *it;
 DBUG_PRINT("quit",("Informing dump thread %ld that it's time to die",
 tmp->thread_id));
 if (tmp->get_command() == COM_BINLOG_DUMP ||
 tmp->get_command() == COM_BINLOG_DUMP_GTID)
 {
 	/* 关闭DUMP线程 */
 tmp->killed= THD::KILL_CONNECTION;
 
 ......
 
 }
 }
 mysql_mutex_unlock(&LOCK_thread_count);
 }
 
 ......
 
 /* 第三遍遍历线程列表 */ for (it= global_thread_list->begin(); it != global_thread_list->end(); ++it)
 {
 THD *tmp= *it;
 if (tmp->vio_ok())
 {
 if (log_warnings)
 sql_print_warning(ER_DEFAULT(ER_FORCING_CLOSE),my_progname,
 tmp->thread_id,
 (tmp->main_security_ctx.user ?
 tmp->main_security_ctx.user : ""));
 /* 关闭连接,不等待语句结束,但是要回滚未提交线程 */
 close_connection(tmp);
 }
 }
 

close_connection() 中调用 THD::disconnect() 断开连接 连接断开后开始回滚事务

bool do_command(THD *thd)
{
	......
	packet_length= my_net_read(net); // thd->disconnect() 后此处直接返回
	...... 
}

void do_handle_one_connection(THD *thd_arg)
{
	......
	while (thd_is_connection_alive(thd))
	{
 		if (do_command(thd)) //do_command 返回 error,跳出循环
 			break;
	}
 end_connection(thd);
 
end_thread:
 close_connection(thd);
 /* 此处调用one_thread_per_connection_end() */
 if (MYSQL_CALLBACK_ELSE(thd->scheduler, end_thread, (thd, 1), 0))
 return; // Probably no-threads


	......
}

事务回滚调用链

trans_rollback(THD*) ()
THD::cleanup() ()
THD::release_resources() ()
one_thread_per_connection_end(THD*, bool) ()
do_handle_one_connection(THD*) ()
handle_one_connection ()

unireg_end 调用 clean_up()

void clean_up(bool print_message) {
	/* 这里是一些释放内存和锁的操作 */	
 	......
 	
 	/*
 		这里调用 innobase_shutdown_for_mysql
 		purge all			(innodb_fast_shutdown = 0)
 		merge change buffer	(innodb_fast_shutdown = 0)
 		flush dirty page	(innodb_fast_shutdown = 0,1)
 		flush log buffer
 		都在这里面做 
 	*/
 plugin_shutdown();
 
 /* 这里是一些释放内存和锁的操作 */
 ......
 
 /* 
 	删除 pid 文件,删除后 mysqld_safe不会重启 mysqld,
 	不然会认为 mysqld crash,尝试重启
 */
 delete_pid_file(MYF(0));
 
 /* 这里是一些释放内存和锁的操作 */
 ......

innodb shutdown 分析

innodb shutdown 的主要操作在 logs_empty_and_mark_files_at_shutdown() 中

  • 等待后台线程结束 srv_error_monitor_thread
    • srv_lock_timeout_thread
    • srv_monitor_thread
    • buf_dump_thread
    • dict_stats_thread
  • 等待所有事物结束 trx_sys_any_active_transactions
  • 等待后台线程结束
    •  worker threads: srv_worker_thread
    • master thread: srv_master_thread
    • purge thread: srv_purge_coordinator_thread
  • 等待 buf_flush_lru_manager_thread 结束
  • 等待 buf_flush_page_cleaner_thread 结束
  • 等待 Pending checkpoint_writes, Pending log flush writes 结束
  • 等待 buffer pool pending io 结束
  • if (innodb_fast_shutdown == 2)
    • flush log buffer 后 return
  • log_make_checkpoint_at 
    • flush buffer pool
    • write checkpoint
  • 将 lsn 落盘 fil_write_flushed_lsn_to_data_files()
  • 关闭所有文件

logs_empty_and_mark_files_at_shutdown() 结束后,innobase_shutdown_for_mysql() 再做一些资源清理工作即结束 shutdown 过程

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
存储 SQL 监控
MySQL · 源码分析 · 8.0 原子DDL的实现过程续
之前的一篇月报MySQL · 源码分析 · 原子DDL的实现过程对MySQL8.0的原子DDL的背景以及使用的一些关键数据结构进行了阐述,同时也以CREATE TABLE为例介绍了Server层和Storage层统一系统表后如何创建一张新表进行了介绍。
1926 0
|
SQL Oracle 关系型数据库
MySQL · 源码分析 · Derived table代码分析
在具体介绍MySQL的derived table之前,先介绍一下子查询的概念。在MySQL中,包含2种类型的子查询:From字句中的子查询,例如select * from (select * from t1) tt;tt是一个抽象的表概念,内部就是一个子查询,在PG的概念中叫做sublink,MySQL则叫做derived table、view其他位置的子查询,如投影列中、条件中、having中,
318 0
MySQL · 源码分析 · Derived table代码分析
|
Prometheus 监控 Cloud Native
mysql exporter源码分析
通过对MySQL Exporter整体进行分析,实现一个自定义的demo收集,并进行采集的整合
485 0
|
关系型数据库 MySQL
MySQL · 源码分析 · Subquery代码分析
在上一篇介绍derived table的文章中,开头已经介绍了MySQL中子查询的基本概念,具体详见:https://ata.alibaba-inc.com/articles/221930?spm=ata.25287382.0.0.78a241676LAHnE这篇文章将重点介绍条件/投影中的子查询在MySQL中的处理,例如SELECT t1.c2, (select sum(t2.c1) from 
557 0
|
SQL NoSQL 关系型数据库
MySQL · 源码分析 · MySQL Range (Min-Max Tree)结构分析
概述条件查询被广泛的使用在SQL查询中,复杂条件是否能在执行过程中被优化,比如恒为true或者false的条件,可以合并的条件。另外,由于索引是MySQL访问数据的基本方式,已经追求更快的访问方式,SARGable这个概念已经被我们遗忘了,因为他已经成为默认必要的方法(Search ARGument ABLE)。MySQL如何组织复杂条件并计算各个Ranges所影响到的对应可以使用的索引的代价和使
650 0
|
SQL 关系型数据库 MySQL
MySQL 子查询优化源码分析
# 子查询定义 在一个完整的查询语句中包含的子查询块被称为子查询。通常情况下,我们可以将出现在SELECT、WHERE和HAVING语法中的子查询块称为嵌套子查询,出现在FROM语法后的子查询块称为内联视图或派生表。 本篇文章将会结合源码介绍在MySQL中针对子查询的几种优化策略。 # 子查询在执行计划中的表示 ![temp.jpg](https://ata2-img.oss-cn
302 0
MySQL 子查询优化源码分析
|
关系型数据库 索引
MySQL · 源码分析 · 聚合函数(Aggregate Function)的实现过程
--- title: MySQL · 源码分析 · 聚合函数(Aggregate Function)的实现过程 author: 道客 --- ## 总览 聚合函数(Aggregate Function)顾名思义,就是将一组数据进行统一计算,常常用于分析型数据库中,当然在应用中是非常重要不可或缺的函数计算方式。比如我们常见的COUNT/AVG/SUM/MIN/MAX等等。本文主要分析下
1814 0
|
关系型数据库 MySQL 数据库
MySQL · 源码分析 · Innodb缓冲池刷脏的多线程实现
简介 为了提高性能,大多数的数据库在操作数据时都不会直接读写磁盘,而是中间经过缓冲池,将要写入磁盘的数据先写入到缓冲池里,然后在某个时刻后台线程把修改的数据刷写到磁盘上。MySQL的InnoDB引擎也使用缓冲池来缓存从磁盘读取或修改的数据页,如果当前数据库需要操作的数据集比缓冲池中的空闲页面大的话,当前缓冲池中的数据页就必须进行脏页淘汰,以便腾出足够的空闲页面供当前的查询使用。
1435 0
|
MySQL 关系型数据库 索引
MySQL · 源码分析 · binlog crash recovery
前言 本文主要介绍binlog crash recovery 的过程 假设用户使用 InnoDB 引擎,sync_binlog=1 使用 MySQL 5.7.20 版本进行分析 crash recovery 过程中,binlog 需要保证: 所有已提交事务的binlog已存在 所有未提交...
2488 0
|
SQL 监控 MySQL
MySQL · 源码分析 · change master to
重要数据结构 Rpl_info 的基类,保存了一些错误信息,如 IO/SQL thread last error class Slave_reporting_capability { // 获取last error Error const& last_error() const ...
1664 0