MySQL • 原始碼分析 • SHUTDOWN過程

姬子玉發表於2017-12-13


摘要:

ORACLE 中的SHUTDOWN MySQL SHUTDOWN LEVEL 暫時只有一種,原始碼中留了 LEVEL 的坑還沒填 在此借用 Oracle 的 SHUTDOWN LEVEL 分析 Oracle SHUTDOWN LEVEL 共有四種:ABORT、IMMEDIATE、NORMAL、TRANSACTIONAL ABORT 立即結束所有SQL 回滾未提交事務 斷開所有使用者連

ORACLE 中的SHUTDOWN

MySQL SHUTDOWN LEVEL 暫時只有一種,原始碼中留了 LEVEL 的坑還沒填

在此借用 Oracle 的 SHUTDOWN LEVEL 分析

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

ABORT
IMMEDIATE
NORMAL
TRANSACTIONAL

MySQL 中的 SHUTDOWN 實際相當於 Oracle 中的 SHUTDOWN IMMEDIATE,重啟例項時無需recovery,但回滾事務的過程可能耗時很長

MySQL SHUTDOWN過程分析

InnoDB shutdown 速度取決於引數 innodb_fast_shutdown

  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() 中

logs_empty_and_mark_files_at_shutdown() 結束後,innobase_shutdown_for_mysql() 再做一些資源清理工作即結束 shutdown 過程

原文連結


相關文章