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
- clean_up
- close_connections()
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