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.
  1. case COM_SHUTDOWN: // 接受到SHUTDOWN命令
  2. {
  3. if (packet_length < 1)
  4. {
  5. my_error(ER_MALFORMED_PACKET, MYF(0));
  6. break;
  7. }
  8. status_var_increment(thd->status_var.com_other);
  9. if (check_global_access(thd,SHUTDOWN_ACL)) // 检查权限
  10. break; /* purecov: inspected */
  11. /*
  12. If the client is < 4.1.3, it is going to send us no argument; then
  13. packet_length is 0, packet[0] is the end 0 of the packet. Note that
  14. SHUTDOWN_DEFAULT is 0. If client is >= 4.1.3, the shutdown level is in
  15. packet[0].
  16. */
  17. enum mysql_enum_shutdown_level level; // 留的坑,default以外的LEVEL都没实现
  18. if (!thd->is_valid_time())
  19. level= SHUTDOWN_DEFAULT;
  20. else
  21. level= (enum mysql_enum_shutdown_level) (uchar) packet[0];
  22. if (level == SHUTDOWN_DEFAULT)
  23. level= SHUTDOWN_WAIT_ALL_BUFFERS; // soon default will be configurable
  24. else if (level != SHUTDOWN_WAIT_ALL_BUFFERS)
  25. {
  26. my_error(ER_NOT_SUPPORTED_YET, MYF(0), "this shutdown level");
  27. break;
  28. }
  29. DBUG_PRINT("quit",("Got shutdown command for level %u", level));
  30. general_log_print(thd, command, NullS); // 记录general_log
  31. my_eof(thd);
  32. kill_mysql(); // 调用kill_mysql()函数,函数内部创建 kill_server_thread 线程
  33. error=TRUE;
  34. break;
  35. }

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

  1. static void __cdecl kill_server(int sig_ptr)
  2. {
  3. ......
  4. close_connections();
  5. if (sig != MYSQL_KILL_SIGNAL &&
  6. sig != 0)
  7. unireg_abort(1); /* purecov: inspected */
  8. else
  9. unireg_end();

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

  1. static void close_connections(void)
  2. ......
  3. /* 下面这段代码结束监听端口 */
  4. /* Abort listening to new connections */
  5. DBUG_PRINT("quit",("Closing sockets"));
  6. if (!opt_disable_networking )
  7. {
  8. if (mysql_socket_getfd(base_ip_sock) != INVALID_SOCKET)
  9. {
  10. (void) mysql_socket_shutdown(base_ip_sock, SHUT_RDWR);
  11. (void) mysql_socket_close(base_ip_sock);
  12. base_ip_sock= MYSQL_INVALID_SOCKET;
  13. }
  14. if (mysql_socket_getfd(extra_ip_sock) != INVALID_SOCKET)
  15. {
  16. (void) mysql_socket_shutdown(extra_ip_sock, SHUT_RDWR);
  17. (void) mysql_socket_close(extra_ip_sock);
  18. extra_ip_sock= MYSQL_INVALID_SOCKET;
  19. }
  20. }
  21. ......
  22. /* 第一遍遍历线程列表 */
  23. sql_print_information("Giving %d client threads a chance to die gracefully",
  24. static_cast<int>(get_thread_count()));
  25. mysql_mutex_lock(&LOCK_thread_count);
  26. Thread_iterator it= global_thread_list->begin();
  27. for (; it != global_thread_list->end(); ++it)
  28. {
  29. THD *tmp= *it;
  30. DBUG_PRINT("quit",("Informing thread %ld that it's time to die",
  31. tmp->thread_id));
  32. /* We skip slave threads & scheduler on this first loop through. */
  33. /* 跳过 slave 相关线程,到 end_server() 函数内处理 */
  34. if (tmp->slave_thread)
  35. continue;
  36. if (tmp->get_command() == COM_BINLOG_DUMP ||
  37. tmp->get_command() == COM_BINLOG_DUMP_GTID)
  38. {
  39. ++dump_thread_count;
  40. continue;
  41. }
  42. /* 先标记为 KILL 给连接一个自我了断的机会 */
  43. tmp->killed= THD::KILL_CONNECTION;
  44. ......
  45. }
  46. mysql_mutex_unlock(&LOCK_thread_count);
  47. Events::deinit();
  48. sql_print_information("Shutting down slave threads");
  49. /* 此处断开 slave 相关线程 */
  50. end_slave();
  51. /* 第二遍遍历线程列表 */
  52. if (dump_thread_count)
  53. {
  54. /*
  55. Replication dump thread should be terminated after the clients are
  56. terminated. Wait for few more seconds for other sessions to end.
  57. */
  58. while (get_thread_count() > dump_thread_count && dump_thread_kill_retries)
  59. {
  60. sleep(1);
  61. dump_thread_kill_retries--;
  62. }
  63. mysql_mutex_lock(&LOCK_thread_count);
  64. for (it= global_thread_list->begin(); it != global_thread_list->end(); ++it)
  65. {
  66. THD *tmp= *it;
  67. DBUG_PRINT("quit",("Informing dump thread %ld that it's time to die",
  68. tmp->thread_id));
  69. if (tmp->get_command() == COM_BINLOG_DUMP ||
  70. tmp->get_command() == COM_BINLOG_DUMP_GTID)
  71. {
  72. /* 关闭DUMP线程 */
  73. tmp->killed= THD::KILL_CONNECTION;
  74. ......
  75. }
  76. }
  77. mysql_mutex_unlock(&LOCK_thread_count);
  78. }
  79. ......
  80. /* 第三遍遍历线程列表 */
  81. for (it= global_thread_list->begin(); it != global_thread_list->end(); ++it)
  82. {
  83. THD *tmp= *it;
  84. if (tmp->vio_ok())
  85. {
  86. if (log_warnings)
  87. sql_print_warning(ER_DEFAULT(ER_FORCING_CLOSE),my_progname,
  88. tmp->thread_id,
  89. (tmp->main_security_ctx.user ?
  90. tmp->main_security_ctx.user : ""));
  91. /* 关闭连接,不等待语句结束,但是要回滚未提交线程 */
  92. close_connection(tmp);
  93. }
  94. }

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

  1. bool do_command(THD *thd)
  2. {
  3. ......
  4. packet_length= my_net_read(net); // thd->disconnect() 后此处直接返回
  5. ......
  6. }
  7. void do_handle_one_connection(THD *thd_arg)
  8. {
  9. ......
  10. while (thd_is_connection_alive(thd))
  11. {
  12. if (do_command(thd)) //do_command 返回 error,跳出循环
  13. break;
  14. }
  15. end_connection(thd);
  16. end_thread:
  17. close_connection(thd);
  18. /* 此处调用one_thread_per_connection_end() */
  19. if (MYSQL_CALLBACK_ELSE(thd->scheduler, end_thread, (thd, 1), 0))
  20. return; // Probably no-threads
  21. ......
  22. }

事务回滚调用链

  1. trans_rollback(THD*) ()
  2. THD::cleanup() ()
  3. THD::release_resources() ()
  4. one_thread_per_connection_end(THD*, bool) ()
  5. do_handle_one_connection(THD*) ()
  6. handle_one_connection ()

unireg_end 调用 clean_up()

  1. void clean_up(bool print_message)
  2. {
  3. /* 这里是一些释放内存和锁的操作 */
  4. ......
  5. /*
  6. 这里调用 innobase_shutdown_for_mysql
  7. purge all (innodb_fast_shutdown = 0)
  8. merge change buffer (innodb_fast_shutdown = 0)
  9. flush dirty page (innodb_fast_shutdown = 0,1)
  10. flush log buffer
  11. 都在这里面做
  12. */
  13. plugin_shutdown();
  14. /* 这里是一些释放内存和锁的操作 */
  15. ......
  16. /*
  17. 删除 pid 文件,删除后 mysqld_safe不会重启 mysqld,
  18. 不然会认为 mysqld crash,尝试重启
  19. */
  20. delete_pid_file(MYF(0));
  21. /* 这里是一些释放内存和锁的操作 */
  22. ......

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