MySQL · 源码分析 · SHUTDOWN过程

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 过程

时间: 2024-09-20 14:34:51

MySQL · 源码分析 · SHUTDOWN过程的相关文章

MySQL · 源码分析 · InnoDB 异步IO工作流程

之前的一篇内核月报InnoDB IO子系统 中介绍了InnoDB IO子系统中包含的同步IO以及异步IO.本篇文章将从源码层面剖析一下InnoDB IO子系统中,数据页的同步IO以及异步IO请求的具体实现过程. 在MySQL5.6中,InnoDB的异步IO主要是用来处理预读以及对数据文件的写请求的.而对于正常的页面数据读取则是通过同步IO进行的.到底二者在代码层面上的实现过程有什么样的区别? 接下来我们将以Linux native io的执行过程为主线,对IO请求的执行过程进行梳理. 重点数据结

MySQL · 源码分析 · InnoDB LRU List刷脏改进之路

之前的一篇内核月报MySQL · 引擎特性 · InnoDB Buffer Pool 中对InnoDB Buffer pool的整体进行了详细的介绍.文章已经提到了LRU List以及刷脏的工作原理.本篇文章着重从MySQL 5.7源码层面对LRU List刷脏的工作原理,以及Percona针对MySQL LRU Flush的一些性能问题所做的改进,进行一下分析. 在MySQL中,如果当前数据库需要操作的数据集比Buffer pool中的空闲页面大的话,当前Buffer pool中的数据页就必须

MySQL · 源码分析 · Innodb 引擎Redo日志存储格式简介

MySQL有多种日志.不同种类.不同目的的日志会记录在不同的日志文件中,它们可以帮助你找出mysqld内部发生的事情.比如错误日志:用来记录启动.运行或停止mysqld进程时出现的问题:查询日志:记录建立的客户端连接和执行的语句:二进制日志:记录所有更改数据的语句,主要用于逻辑复制:慢日志:记录所有执行时间超过long_query_time秒的所有查询或不使用索引的查询.而对MySQL中最常用的事务引擎innodb,redo日志是保证事务一致性非常重要的.本文结合MySQL版本5.6为分析源码介

MySQL · 源码分析 · MySQL 半同步复制数据一致性分析

简介 MySQL Replication为MySQL用户提供了高可用性和可扩展性解决方案.本文介绍了MySQL Replication的主要发展历程,然后通过三个参数rpl_semi_sync_master_wait_point.sync_binlog.sync_relay_log的配置简要分析了MySQL半同步的数据一致性. MySQL Replication的发展 在2000年,MySQL 3.23.15版本引入了Replication.Replication作为一种准实时同步方式,得到广泛

MySQL · 源码分析 · MySQL BINLOG半同步复制数据安全性分析

半同步复制(semisynchronous replication)MySQL使用广泛的数据复制方案,相比于MySQL内置的异步复制它保证了数据的安 全,本文从主机在Server层提交事务开始一直到主机确认收到备机回复进行一步步解析,来看MySQL的半同步复制是怎么保证数 据安全的.本文基于MySQL 5.6源码,为了简化本文只分析DML的核心的事务处理过程,并假定事务只涉及innodb存储引擎. MySQL的事务提交流程 在MySQL中事务的提交Server层最后会调用函数ha_commit_

Nginx学习笔记(六) 源码分析&amp;启动过程

涉及到的基本函数 源码: 1 /* 2 * Copyright (C) Igor Sysoev 3 * Copyright (C) Nginx, Inc. 4 */ 5 6 7 #include <ngx_config.h> 8 #include <ngx_core.h> 9 #include <nginx.h> 10 11 12 static ngx_int_t ngx_add_inherited_sockets(ngx_cycle_t *cycle); 13 sta

MySQL · 源码分析 · mysql认证阶段漫游

client发起一个连接请求, 到拿到server返回的ok包之间, 走三次握手, 交换了[不可告人]的验证信息, 这期间mysql如何完成校验工作? 过程(三次握手) 信息是如何加密的 client: hash_stage1 = sha1(password) hash_stage2 = sha1(hash_stage1) reply = sha1(scramble, hash_stage2) ^ hash_stage1 server: (逻辑位于sql/password.c:check_scr

MySQL · 源码分析 · 一条insert语句的执行过程

本文只分析了insert语句执行的主路径,和路径上部分关键函数,很多细节没有深入,留给读者继续分析 create table t1(id int); insert into t1 values(1) 略过建立连接,从 mysql_parse() 开始分析 void mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state) { /* ...... */ /* 检查query_cache,如果结果存在于c

MySQL · 源码分析 · Tokudb序列化和反序列化过程

序列化和写盘 Tokudb数据节点写盘主要是由后台线程异步完成的: checkpoint线程:把cachetable(innodb术语buffer pool)中所有脏页写回 evictor线程:释放内存,如果victim节点是dirty的,需要先将数据写回. 数据在磁盘上是序列化过的,序列化的过程就是把一个数据结构转换成字节流. 写数据包括两个阶段: 序列化:把结构化数据转成字节流 压缩:对序列化好的数据进行压缩 tokudb序列化和压缩单位是partition,对于internal节点,就是把