Interpretation of mysql source code -- network service source code

Posted by aeafisme23 on Wed, 09 Feb 2022 21:44:44 +0100

1, Network communication and service

Network communication is the basic service of MySQL, including other related services derived from it, which constitutes the main way for MySQL client and server to complete interaction. The main functions include:
1. Network initialization and service initialization: including parameters, server and listener.
2. Network interaction module: data sending and receiving and control.
3. Interactive protocol module: including UNIX SOCKET protocol, TCP/IP protocol, pipeline and Share Memory protocol
These three parts basically cover the main contents of network communication and services.

2, Main process

The main processes are basically as follows:

The process of network communication is much simpler than that of Redis. The tasks related to distributed communication are ignored here for the time being.

3, Source code

Still go back to main - > mysqld in the previous article_ Main function:

bool my_init() {
  char *str;
......

//Thread and parameter initialization
  if (my_thread_global_init()) return true;

  if (my_thread_init()) return true;

  /* $HOME is needed early to parse configuration files located in ~/ */
  if ((home_dir = getenv("HOME")) != nullptr)
    home_dir = intern_filename(home_dir_buff, home_dir);

  {
    DBUG_TRACE;
    DBUG_PROCESS(my_progname ? my_progname : "unknown");
#ifdef _WIN32
    my_win_init();
#endif
    MyFileInit();
......
    return false;
  }
}

static void my_win_init() {
  DBUG_TRACE;
......
  win_init_registry();
  win32_init_tcp_ip();

  MyWinfileInit();
}
static bool win32_init_tcp_ip() {
  if (win32_have_tcpip()) {
    WORD wVersionRequested = MAKEWORD(2, 2);
    WSADATA wsaData;
    /* Be a good citizen: maybe another lib has already initialised
            sockets, so dont clobber them unless necessary */
    if (WSAStartup(wVersionRequested, &wsaData)) {
      /* Load failed, maybe because of previously loaded
         incompatible version; try again */
      WSACleanup();
      if (!WSAStartup(wVersionRequested, &wsaData)) have_tcpip = 1;
    } else {
      if (wsaData.wVersion != wVersionRequested) {
        /* Version is no good, try again */
        WSACleanup();
        if (!WSAStartup(wVersionRequested, &wsaData)) have_tcpip = 1;
      } else
        have_tcpip = 1;
    }
  }
  return (0);
}

If you have written about the network development of Windows platform, the call of the last function is very clear. Now I see. Then down:

void notify_connect() {
#ifndef _WIN32
  const char *sockstr = getenv("NOTIFY_SOCKET");
  if (sockstr == nullptr) {
#ifdef WITH_SYSTEMD_DEBUG
    sql_print_warning(
        "NOTIFY_SOCKET not set in environment. sd_notify messages will not be "
        "sent!");
#endif /* WITH_SYSTEMD_DEBUG */
    return;
  }
  size_t sockstrlen = strlen(sockstr);
  size_t sunpathlen = sizeof(sockaddr_un::sun_path) - 1;
  if (sockstrlen > sunpathlen) {
    std::cerr << "Error: NOTIFY_SOCKET too long" << std::endl;
    LogErr(SYSTEM_LEVEL, ER_SYSTEMD_NOTIFY_PATH_TOO_LONG, sockstr, sockstrlen,
           sunpathlen);
    return;
  }
  //UDP communication initialization
  NotifyGlobals::socket = socket(AF_UNIX, SOCK_DGRAM, 0);

  sockaddr_un addr;
  socklen_t addrlen;
  memset(&addr, 0, sizeof(sockaddr_un));
  addr.sun_family = AF_UNIX;
  if (sockstr[0] != '@') {
    strcpy(addr.sun_path, sockstr);
    addrlen = offsetof(struct sockaddr_un, sun_path) + sockstrlen + 1;
  } else {  // Abstract namespace socket
    addr.sun_path[0] = '\0';
    strncpy(&addr.sun_path[1], sockstr + 1, strlen(sockstr) - 1);
    addrlen = offsetof(struct sockaddr_un, sun_path) + sockstrlen;
  }
  int ret = -1;
  do {
    ret = connect(NotifyGlobals::socket,
                  reinterpret_cast<const sockaddr *>(&addr), addrlen);
  } while (ret == -1 && errno == EINTR);
  if (ret == -1) {
    char errbuf[512];
    LogErr(WARNING_LEVEL, ER_SYSTEMD_NOTIFY_CONNECT_FAILED, sockstr,
           my_strerror(errbuf, sizeof(errbuf) - 1, errno));
    NotifyGlobals::socket = -1;
  }
#endif /* not defined _WIN32 */
}

Next, the initialization and configuration of a large number of relevant persistent parameters, and then the configuration of relevant functional interfaces controlled by macro definition. Next is authentication initialization:

void mysql_audit_initialize() {
#ifdef HAVE_PSI_INTERFACE
  init_audit_psi_keys();
#endif

  mysql_mutex_init(key_LOCK_audit_mask, &LOCK_audit_mask, MY_MUTEX_INIT_FAST);
  memset(mysql_global_audit_mask, 0, sizeof(mysql_global_audit_mask));
}

bool Srv_session::module_init() {
  if (srv_session_THRs_initialized) return false;
  srv_session_THRs_initialized = true;
  THR_stack_start_address = nullptr;
  THR_srv_session_thread = nullptr;

  server_session_list.init();
  server_session_threads.init();

  return false;
}

Next comes a batch of locks, semaphores and related initialization, including some custom string functions. Set and customize the stack space of the whole application thread.

static void set_ports() {
  char *env;
  if (!mysqld_port &&
      !opt_disable_networking) {  // Get port if not from commandline
    mysqld_port = MYSQL_PORT;

    /*
      if builder specifically requested a default port, use that
      (even if it coincides with our factory default).
      only if they didn't do we check /etc/services (and, failing
      on that, fall back to the factory default of 3306).
      either default can be overridden by the environment variable
      MYSQL_TCP_PORT, which in turn can be overridden with command
      line options.
    */

#if MYSQL_PORT_DEFAULT == 0
    struct servent *serv_ptr;
    if ((serv_ptr = getservbyname("mysql", "tcp")))
      mysqld_port = ntohs((u_short)serv_ptr->s_port); /* purecov: inspected */
#endif
    if ((env = getenv("MYSQL_TCP_PORT")))
      mysqld_port = (uint)atoi(env); /* purecov: inspected */
  }
  if (!mysqld_unix_port) {
#ifdef _WIN32
    mysqld_unix_port = (char *)MYSQL_NAMEDPIPE;
#else
    mysqld_unix_port = MYSQL_UNIX_ADDR;
#endif
    if ((env = getenv("MYSQL_UNIX_PORT")))
      mysqld_unix_port = env; /* purecov: inspected */
  }
}
int delegates_init() {
  alignas(Trans_delegate) static char place_trans_mem[sizeof(Trans_delegate)];
  alignas(Binlog_storage_delegate) static char
      place_storage_mem[sizeof(Binlog_storage_delegate)];
  alignas(Server_state_delegate) static char
      place_state_mem[sizeof(Server_state_delegate)];
  alignas(Binlog_transmit_delegate) static char
      place_transmit_mem[sizeof(Binlog_transmit_delegate)];
  alignas(Binlog_relay_IO_delegate) static char
      place_relay_io_mem[sizeof(Binlog_relay_IO_delegate)];

  transaction_delegate = new (place_trans_mem) Trans_delegate;
  if (!transaction_delegate->is_inited()) {
    LogErr(ERROR_LEVEL, ER_RPL_TRX_DELEGATES_INIT_FAILED);
    return 1;
  }

  binlog_storage_delegate = new (place_storage_mem) Binlog_storage_delegate;
  if (!binlog_storage_delegate->is_inited()) {
    LogErr(ERROR_LEVEL, ER_RPL_BINLOG_STORAGE_DELEGATES_INIT_FAILED);
    return 1;
  }

  server_state_delegate = new (place_state_mem) Server_state_delegate;
  binlog_transmit_delegate = new (place_transmit_mem) Binlog_transmit_delegate;
  if (!binlog_transmit_delegate->is_inited()) {
    LogErr(ERROR_LEVEL, ER_RPL_BINLOG_TRANSMIT_DELEGATES_INIT_FAILED);
    return 1;
  }

  binlog_relay_io_delegate = new (place_relay_io_mem) Binlog_relay_IO_delegate;
  if (!binlog_relay_io_delegate->is_inited()) {
    LogErr(ERROR_LEVEL, ER_RPL_BINLOG_RELAY_DELEGATES_INIT_FAILED);
    return 1;
  }

  return 0;
}

Continue to find network related parts, key cache and SSL encrypted communication:

bool process_key_caches(process_key_cache_t func) {
  I_List_iterator<NAMED_ILINK> it(key_caches);
  NAMED_ILINK *element;

  while ((element = it++)) {
    KEY_CACHE *key_cache = (KEY_CACHE *)element->data;
    func(element->name, key_cache);
  }
  return false;
}

static void init_ssl() {
#if !defined(__sun)
#if defined(HAVE_PSI_MEMORY_INTERFACE)
  static PSI_memory_info all_openssl_memory[] = {
      {&key_memory_openssl, "openssl_malloc", 0, 0,
       "All memory used by openSSL"}};
  mysql_memory_register("mysqld_openssl", all_openssl_memory,
                        (int)array_elements(all_openssl_memory));
#endif /* defined(HAVE_PSI_MEMORY_INTERFACE) */
  int ret = CRYPTO_set_mem_functions(my_openssl_malloc, my_openssl_realloc,
                                     my_openssl_free);
  if (ret == 0)
    LogErr(WARNING_LEVEL, ER_SSL_MEMORY_INSTRUMENTATION_INIT_FAILED,
           "CRYPTO_set_mem_functions");
#endif /* !defined(__sun) */
  ssl_start();
}
void init_max_user_conn(void) {
  hash_user_connections =
      new collation_unordered_map<std::string, unique_ptr_my_free<user_conn>>(
          system_charset_info, key_memory_user_conn);
}

Last but not least:

//Line 1740 defines the relevant listener and receiver
static Connection_acceptor<Mysqld_socket_listener> *mysqld_socket_acceptor =
    nullptr;
#ifdef _WIN32
static Named_pipe_listener *named_pipe_listener = NULL;
Connection_acceptor<Named_pipe_listener> *named_pipe_acceptor = NULL;
Connection_acceptor<Shared_mem_listener> *shared_mem_acceptor = NULL;


#ifdef _WIN32
int win_main(int argc, char **argv)
#else
int mysqld_main(int argc, char **argv)
#endif
{

......
if (init_ssl_communication()) unireg_abort(MYSQLD_ABORT_EXIT);
if (network_init()) unireg_abort(MYSQLD_ABORT_EXIT);

......
#if defined(_WIN32)
  if (mysqld_socket_acceptor != nullptr)
    mysqld_socket_acceptor->check_and_spawn_admin_connection_handler_thread();
  setup_conn_event_handler_threads();
#else
  mysql_mutex_lock(&LOCK_socket_listener_active);
  // Make it possible for the signal handler to kill the listener.
  socket_listener_active = true;
  mysql_mutex_unlock(&LOCK_socket_listener_active);

  if (opt_daemonize) {
    if (nstdout != nullptr) {
      // Show the pid on stdout if deamonizing and connected to tty
      fprintf(nstdout, "mysqld is running as pid %lu\n", current_pid);
      fclose(nstdout);
      nstdout = nullptr;
    }

    mysqld::runtime::signal_parent(pipe_write_fd, 1);
  }

  mysqld_socket_acceptor->check_and_spawn_admin_connection_handler_thread();
  mysqld_socket_acceptor->connection_event_loop();
  ......
}

Look at network initialization:

static bool network_init(void) {
  if (opt_initialize) return false;

#ifdef HAVE_SYS_UN_H
  std::string const unix_sock_name(mysqld_unix_port ? mysqld_unix_port : "");
#else
  std::string const unix_sock_name("");
#endif

  std::list<Bind_address_info> bind_addresses_info;

  if (!opt_disable_networking || unix_sock_name != "") {
    if (my_bind_addr_str != nullptr &&
        check_bind_address_has_valid_value(my_bind_addr_str,
                                           &bind_addresses_info)) {
      LogErr(ERROR_LEVEL, ER_INVALID_VALUE_OF_BIND_ADDRESSES, my_bind_addr_str);
      return true;
    }

    Bind_address_info admin_address_info;
    if (!opt_disable_networking) {
      if (my_admin_bind_addr_str != nullptr &&
          check_admin_address_has_valid_value(my_admin_bind_addr_str,
                                              &admin_address_info)) {
        LogErr(ERROR_LEVEL, ER_INVALID_ADMIN_ADDRESS, my_admin_bind_addr_str);
        return true;
      }
      /*
        Port 0 is interpreted by implementations of TCP protocol
        as a hint to find a first free port value to use and bind to it.
        On the other hand, the option mysqld_admin_port can be assigned
        the value 0 if a user specified a value that is out of allowable
        range of values. Therefore, to avoid a case when an operating
        system binds admin interface to am arbitrary selected port value,
        set it explicitly to the value MYSQL_ADMIN_PORT in case it has value 0.
      */
      if (mysqld_admin_port == 0) mysqld_admin_port = MYSQL_ADMIN_PORT;
    }
    Mysqld_socket_listener *mysqld_socket_listener = new (std::nothrow)
        Mysqld_socket_listener(bind_addresses_info, mysqld_port,
                               admin_address_info, mysqld_admin_port,
                               admin_address_info.address.empty()
                                   ? false
                                   : listen_admin_interface_in_separate_thread,
                               back_log, mysqld_port_timeout, unix_sock_name);
    if (mysqld_socket_listener == nullptr) return true;

    mysqld_socket_acceptor = new (std::nothrow)
        Connection_acceptor<Mysqld_socket_listener>(mysqld_socket_listener);
    if (mysqld_socket_acceptor == nullptr) {
      delete mysqld_socket_listener;
      mysqld_socket_listener = nullptr;
      return true;
    }

    if (mysqld_socket_acceptor->init_connection_acceptor())
      return true;  // mysqld_socket_acceptor would be freed in unireg_abort.

    if (report_port == 0) report_port = mysqld_port;

    if (!opt_disable_networking) assert(report_port != 0);
  }
#ifdef _WIN32
  // Create named pipe
  if (opt_enable_named_pipe) {
    std::string pipe_name = mysqld_unix_port ? mysqld_unix_port : "";

    named_pipe_listener = new (std::nothrow) Named_pipe_listener(&pipe_name);
    if (named_pipe_listener == NULL) return true;

    named_pipe_acceptor = new (std::nothrow)
        Connection_acceptor<Named_pipe_listener>(named_pipe_listener);
    if (named_pipe_acceptor == NULL) {
      delete named_pipe_listener;
      named_pipe_listener = NULL;
      return true;
    }

    if (named_pipe_acceptor->init_connection_acceptor())
      return true;  // named_pipe_acceptor would be freed in unireg_abort.
  }

  // Setup shared_memory acceptor
  if (opt_enable_shared_memory) {
    std::string shared_mem_base_name =
        shared_memory_base_name ? shared_memory_base_name : "";

    Shared_mem_listener *shared_mem_listener =
        new (std::nothrow) Shared_mem_listener(&shared_mem_base_name);
    if (shared_mem_listener == NULL) return true;

    shared_mem_acceptor = new (std::nothrow)
        Connection_acceptor<Shared_mem_listener>(shared_mem_listener);
    if (shared_mem_acceptor == NULL) {
      delete shared_mem_listener;
      shared_mem_listener = NULL;
      return true;
    }

    if (shared_mem_acceptor->init_connection_acceptor())
      return true;  // shared_mem_acceptor would be freed in unireg_abort.
  }
#endif  // _WIN32
  return false;
}

It is divided into the creation of WIN and LINUX platforms. Just look at one of them. The focus is actually SQL / Conn_ For several files in the handler directory, templates are used here, so some jumps fail:

static inline bool spawn_admin_thread(MYSQL_SOCKET admin_socket,
                                      const std::string &network_namespace) {
  initialize_thread_context();

  admin_thread_arg_t *arg_for_admin_socket_thread =
      new (std::nothrow) admin_thread_arg_t(admin_socket, network_namespace);

  if (arg_for_admin_socket_thread == nullptr) return true;

  int ret = mysql_thread_create(
      key_thread_handle_con_admin_sockets, &admin_socket_thread_id,
      &admin_socket_thread_attrib, admin_socket_thread,
      (void *)arg_for_admin_socket_thread);

  (void)my_thread_attr_destroy(&admin_socket_thread_attrib);

  if (ret) {
    LogErr(ERROR_LEVEL, ER_CANT_CREATE_ADMIN_THREAD, errno);
    return true;
  }

  wait_for_admin_thread_started();

  return false;
}

bool Mysqld_socket_listener::check_and_spawn_admin_connection_handler_thread()
    const {
  if (m_use_separate_thread_for_admin) {
    if (spawn_admin_thread(m_admin_interface_listen_socket,
                           m_admin_bind_address.network_namespace))
      return true;
  }
  return false;
}

bool Mysqld_socket_listener::setup_listener() {
  /*
    It's matter to add a socket for admin connection listener firstly,
    before listening sockets for other connection types be added.
    It is done in order to check availability of new incoming connection
    on admin interface with higher priority than on other interfaces..
  */
  if (!m_admin_bind_address.address.empty()) {
    TCP_socket tcp_socket(m_admin_bind_address.address,
                          m_admin_bind_address.network_namespace,
                          m_admin_tcp_port, m_backlog, m_port_timeout);

    MYSQL_SOCKET mysql_socket = tcp_socket.get_listener_socket();
    if (mysql_socket.fd == INVALID_SOCKET) return true;

    m_admin_interface_listen_socket = mysql_socket;

    if (!m_use_separate_thread_for_admin) {
      m_socket_vector.emplace_back(mysql_socket, Socket_type::TCP_SOCKET,
                                   &m_admin_bind_address.network_namespace,
                                   Socket_interface_type::ADMIN_INTERFACE);
    }
  }

  // Setup tcp socket listener
  if (m_tcp_port) {
    for (const auto &bind_address_info : m_bind_addresses) {
      TCP_socket tcp_socket(bind_address_info.address,
                            bind_address_info.network_namespace, m_tcp_port,
                            m_backlog, m_port_timeout);

      MYSQL_SOCKET mysql_socket = tcp_socket.get_listener_socket();
      if (mysql_socket.fd == INVALID_SOCKET) return true;
      m_socket_vector.emplace_back(mysql_socket, Socket_type::TCP_SOCKET,
                                   &bind_address_info.network_namespace,
                                   Socket_interface_type::DEFAULT_INTERFACE);
    }
  }
#if defined(HAVE_SYS_UN_H)
  // Setup unix socket listener
  if (m_unix_sockname != "") {
    Unix_socket unix_socket(&m_unix_sockname, m_backlog);

    MYSQL_SOCKET mysql_socket = unix_socket.get_listener_socket();
    if (mysql_socket.fd == INVALID_SOCKET) return true;
    Listen_socket s(mysql_socket, Socket_type::UNIX_SOCKET);
    m_socket_vector.push_back(s);
    m_unlink_sockname = true;
  }
#endif /* HAVE_SYS_UN_H */

  setup_connection_events(m_socket_vector);

  return false;
}

const Listen_socket *Mysqld_socket_listener::get_listen_socket() const {
/*
  In case admin interface was set up, then first check whether an admin socket
  ready to accept a new connection. Doing this way provides higher priority
  to admin interface over other listeners.
*/
#ifdef HAVE_POLL
  uint start_index = 0;
  if (!m_admin_bind_address.address.empty() &&
      !m_use_separate_thread_for_admin) {
    if (m_poll_info.m_fds[0].revents & POLLIN) {
      return &m_socket_vector[0];
    } else
      start_index = 1;
  }

  for (uint i = start_index; i < m_socket_vector.size(); ++i) {
    if (m_poll_info.m_fds[i].revents & POLLIN) {
      return &m_socket_vector[i];
    }
  }

#else  // HAVE_POLL
  if (!m_admin_bind_address.address.empty() &&
      !m_use_separate_thread_for_admin &&
      FD_ISSET(mysql_socket_getfd(m_admin_interface_listen_socket),
               &m_select_info.m_read_fds)) {
    return &m_socket_vector[0];
  }

  for (const auto &socket_element : m_socket_vector) {
    if (FD_ISSET(mysql_socket_getfd(socket_element.m_socket),
                 &m_select_info.m_read_fds)) {
      return &socket_element;
    }
  }

#endif  // HAVE_POLL
  return nullptr;
  ;
}

Channel_info *Mysqld_socket_listener::listen_for_connection_event() {
#ifdef HAVE_POLL
  int retval = poll(&m_poll_info.m_fds[0], m_socket_vector.size(), -1);
#else
  m_select_info.m_read_fds = m_select_info.m_client_fds;
  int retval = select((int)m_select_info.m_max_used_connection,
                      &m_select_info.m_read_fds, 0, 0, 0);
#endif

  if (retval < 0 && socket_errno != SOCKET_EINTR) {
    /*
      select(2)/poll(2) failed on the listening port.
      There is not much details to report about the client,
      increment the server global status variable.
    */
    ++connection_errors_query_block;
    if (!select_errors++ && !connection_events_loop_aborted())
      LogErr(ERROR_LEVEL, ER_CONN_SOCKET_SELECT_FAILED, socket_errno);
  }

  if (retval < 0 || connection_events_loop_aborted()) return nullptr;

  /* Is this a new connection request ? */
  const Listen_socket *listen_socket = get_listen_socket();
  /*
    When poll/select returns control flow then at least one ready server socket
    must exist. Check that get_ready_socket() returns a valid socket.
  */
  assert(listen_socket != nullptr);
  MYSQL_SOCKET connect_sock;
#ifdef HAVE_SETNS
  /*
    If a network namespace is specified for a listening socket then set this
    network namespace as active before call to accept().
    It is not clear from manuals whether a socket returned by a call to
    accept() borrows a network namespace from a server socket used for
    accepting a new connection. For that reason, assign a network namespace
    explicitly before calling accept().
  */
  std::string network_namespace_for_listening_socket;
  if (listen_socket->m_socket_type == Socket_type::TCP_SOCKET) {
    network_namespace_for_listening_socket =
        (listen_socket->m_network_namespace != nullptr
             ? *listen_socket->m_network_namespace
             : std::string(""));
    if (!network_namespace_for_listening_socket.empty() &&
        set_network_namespace(network_namespace_for_listening_socket))
      return nullptr;
  }
#endif
  if (accept_connection(listen_socket->m_socket, &connect_sock)) {
#ifdef HAVE_SETNS
    if (!network_namespace_for_listening_socket.empty())
      (void)restore_original_network_namespace();
#endif
    return nullptr;
  }

#ifdef HAVE_SETNS
  if (!network_namespace_for_listening_socket.empty() &&
      restore_original_network_namespace())
    return nullptr;
#endif

#ifdef HAVE_LIBWRAP
  if ((listen_socket->m_socket_type == Socket_type::TCP_SOCKET) &&
      check_connection_refused_by_tcp_wrapper(connect_sock)) {
    return nullptr;
  }
#endif  // HAVE_LIBWRAP

  Channel_info *channel_info = nullptr;
  if (listen_socket->m_socket_type == Socket_type::UNIX_SOCKET)
    channel_info = new (std::nothrow) Channel_info_local_socket(connect_sock);
  else
    channel_info = new (std::nothrow) Channel_info_tcpip_socket(
        connect_sock, (listen_socket->m_socket_interface ==
                       Socket_interface_type::ADMIN_INTERFACE));
  if (channel_info == nullptr) {
    (void)mysql_socket_shutdown(connect_sock, SHUT_RDWR);
    (void)mysql_socket_close(connect_sock);
    connection_errors_internal++;
    return nullptr;
  }

#ifdef HAVE_SETNS
  if (listen_socket->m_socket_type == Socket_type::TCP_SOCKET &&
      !network_namespace_for_listening_socket.empty())
    static_cast<Channel_info_tcpip_socket *>(channel_info)
        ->set_network_namespace(network_namespace_for_listening_socket);
#endif
  return channel_info;
}

Without a good source code viewing tool, it is a little inconvenient to look at the code and wastes a lot of time. In fact, in the end, you will find that all the work has returned to the simplest network communication. Of course, some abstract classes are used for abstraction, and the four communication methods mentioned at first are formed into four classes to realize. This is also a good way, but compared with the previous Redis, the encapsulation is much simpler.
Under this folder, the communication is encapsulated as follows:
channel_info: the connection channel manager is actually the related management of the client
connection_acceptor: network communication receiver, which is responsible for receiving connection messages
connection_handler: connection handle, client handling mechanism
connection_handler_impl: interface of connection management mechanism
connection_handler_manager: Connection Manager
connection_handler_*_thread: thread processor required for connection
Before the source code, there is no secret.

4, Summary

The importance of network communication in MySql is not as important as that in Redis. After all, as a relational database, it can not bear the direct pressure of tens of millions. It is more the application of data storage and analysis. However, with the development of MySql, it is unknown whether it will go further on the distributed road or go another way in the future. After all, change is eternal and the future is unpredictable.
However, as an infrastructure, network communication can not be avoided in any case, which is the same in any scenario. Learning should be strong and weak, and distinguish between primary and secondary.

Topics: MySQL