閱讀1017 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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

最後更新:2017-08-21 09:03:11

  上一篇:go  PgSQL · 應用案例 · HDB for PG特性(數據排盤與任意列高效率過濾)
  下一篇:go  MSSQL· 實現分析 · Extend Event日誌文件的分析方法