MySQL 8.0 数据库连接相关源码介绍

连接的建立

主要是在handle_connection函数中实现。

// 对应的源码文件的位置
// ./sql/conn_handler/connection_handler_per_thread.cc
/**
  Thread handler for a connection

  @param arg   Connection object (Channel_info)

  This function (normally) does the following:
  - Initialize thread
  - Initialize THD to be used with this thread
  - Authenticate user
  - Execute all queries sent on the connection
  - Take connection down
  - End thread  / Handle next connection using thread from thread cache
*/

extern "C" {
static void *handle_connection(void *arg) {
  Global_THD_manager *thd_manager = Global_THD_manager::get_instance();
  Connection_handler_manager *handler_manager =
      Connection_handler_manager::get_instance();
  Channel_info *channel_info = static_cast<Channel_info *>(arg);
  bool pthread_reused [[maybe_unused]] = false;

初始化连接

  for (;;) {
    THD *thd = init_new_thd(channel_info);

创建一个线程

// ./sql/conn_handler/connection_handler_per_thread.cc
/**
  Construct and initialize a THD object for a new connection.

  @param channel_info  Channel_info object representing the new connection.
                       Will be destroyed by this function.

  @retval NULL   Initialization failed.
  @retval !NULL  Pointer to new THD object for the new connection.
*/

static THD *init_new_thd(Channel_info *channel_info) {
  THD *thd = channel_info->create_thd();
  if (thd == nullptr) {
    channel_info->send_error_and_close_channel(ER_OUT_OF_RESOURCES, 0, false);
    delete channel_info;
    return nullptr;
  }

函数的具体实现

// ./sql/conn_handler/socket_connection.cc
///////////////////////////////////////////////////////////////////////////
// Channel_info_tcpip_socket implementation
///////////////////////////////////////////////////////////////////////////

/**
  This class abstracts the info. about TCP/IP socket mode of communication with
  the server.
*/
class Channel_info_tcpip_socket : public Channel_info {
  // connect socket object
  MYSQL_SOCKET m_connect_sock;
  /*
    Flag specifying whether a connection is admin connection or
    ordinary connection.
  */
  bool m_is_admin_conn;
#ifdef HAVE_SETNS
  /*
    Network namespace associated with the socket.
  */
  std::string m_network_namespace;
#endif

 protected:
  Vio *create_and_init_vio() const override {
    Vio *vio = mysql_socket_vio_new(m_connect_sock, VIO_TYPE_TCPIP, 0);
#ifdef USE_PPOLL_IN_VIO
    if (vio != nullptr) {
      vio->thread_id.reset();
      vio->signal_mask = mysqld_signal_mask;
    }
#endif

#ifdef HAVE_SETNS
    strncpy(vio->network_namespace, m_network_namespace.c_str(),
            sizeof(vio->network_namespace) - 1);
    vio->network_namespace[sizeof(vio->network_namespace) - 1] = '\0';
#endif

    return vio;
  }

 public:
  /**
    Constructor that sets the connect socket.

    @param connect_socket set connect socket descriptor.
    @param is_admin_conn  flag specifying whether a connection is admin
                          connection.
  */
  Channel_info_tcpip_socket(MYSQL_SOCKET connect_socket, bool is_admin_conn)
      : m_connect_sock(connect_socket), m_is_admin_conn(is_admin_conn) {}

  THD *create_thd() override {
    THD *thd = Channel_info::create_thd();

    if (thd != nullptr) {
      thd->set_admin_connection(m_is_admin_conn);
      init_net_server_extension(thd);
    }
    return thd;
  }

接下来是有vio的创建和初始化。

​​VIO(Virtual I/O)​​ 是一个​​对网络通信底层进行封装的抽象层​​。它主要用于处理MySQL客户端与服务器之间,以及服务器内部的各种网络I/O操作。

// ./sql/conn_handler/channel_info.cc
THD *Channel_info::create_thd() {
  DBUG_EXECUTE_IF("simulate_resource_failure", return nullptr;);

  Vio *vio_tmp = create_and_init_vio();
  if (vio_tmp == nullptr) return nullptr;

  THD *thd = new (std::nothrow) THD;
  if (thd == nullptr) {
    vio_delete(vio_tmp);
    return nullptr;
  }

  thd->get_protocol_classic()->init_net(vio_tmp);

  return thd;
}

创建socket

    MYSQL_SOCKET socket = thd->get_protocol_classic()->get_vio()->mysql_socket;
    mysql_socket_set_thread_owner(socket);
    thd_manager->add_thd(thd);

如果连接活跃,进入循环,执行对应的DB命令

    if (thd_prepare_connection(thd))
      handler_manager->inc_aborted_connects();
    else {
      while (thd_connection_alive(thd)) {
        if (do_command(thd)) break;
      }
      end_connection(thd);
    }

关闭连接

    close_connection(thd, 0, false, false);

在accept_connection函数上打断点

[root@ecs-test-node2 mysql-8.0.42]# gdb -p 287948
GNU gdb (GDB) Rocky Linux 8.2-20.el8.0.1

(gdb) break accept_connection
Breakpoint 1 at 0x3847962: file /data/mysql-8.0.42/sql/conn_handler/socket_connection.cc, line 914.
(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000003847962 in accept_connection(MYSQL_SOCKET, MYSQL_SOCKET*) 
                                                   at /data/mysql-8.0.42/sql/conn_handler/socket_connection.cc:914

在另外一个终端中,开启一个连接会话

[root@ecs-test-node2 ~]# mysql -h 192.168.0.100 -P 3315 -u lily -p -A

Enter password:

查看栈的执行路径

(gdb) continue
Continuing.

Thread 1 "mysqld" hit Breakpoint 1, accept_connection (listen_sock=..., connect_sock=0x7ffde9ae2960)
    at /data/mysql-8.0.42/sql/conn_handler/socket_connection.cc:914
914	  for (uint retry = 0; retry < MAX_ACCEPT_RETRY; retry++) {
(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000003847962 in accept_connection(MYSQL_SOCKET, MYSQL_SOCKET*) 
                                                   at /data/mysql-8.0.42/sql/conn_handler/socket_connection.cc:914
	breakpoint already hit 1 time
(gdb) backtrace
#0  accept_connection (listen_sock=..., connect_sock=0x7ffde9ae2960)
    at /data/mysql-8.0.42/sql/conn_handler/socket_connection.cc:914
#1  0x0000000003848c75 in Mysqld_socket_listener::listen_for_connection_event (this=0x7f4aa2dff6d0)
    at /data/mysql-8.0.42/sql/conn_handler/socket_connection.cc:1398
#2  0x000000000345a6a8 in Connection_acceptor<Mysqld_socket_listener>::connection_event_loop (this=0xcdadd60)
    at /data/mysql-8.0.42/sql/conn_handler/connection_acceptor.h:65
#3  0x000000000344bd24 in mysqld_main (argc=72, argv=0xa9e5f10) at /data/mysql-8.0.42/sql/mysqld.cc:8287
#4  0x0000000003437c86 in main (argc=11, argv=0x7ffde9ae3328) at /data/mysql-8.0.42/sql/main.cc:26

首先,会执行主函数

// sql/main.cc
int main(int argc, char **argv) { return mysqld_main(argc, argv); }

主函数会返回mysqld_admin函数

进入连接事件循环

// sql/mysqld.cc
#ifdef _WIN32
int win_main(int argc, char **argv)
#else
int mysqld_main(int argc, char **argv)
#endif
{
...
mysqld_socket_acceptor->connection_event_loop();

连接事件循环函数的具体定义

// sql/conn_handler/connection_acceptor.h
/**
  This class presents a generic interface to initialize and run
  a connection event loop for different types of listeners and
  a callback functor to call on the connection event from the
  listener that listens for connection. Listener type should
  be a class providing methods setup_listener, listen_for_
  connection_event and close_listener. The Connection event
  callback functor object would on receiving connection event
  from the client to process the connection.
*/
template <typename Listener>
class Connection_acceptor {
  Listener *m_listener;

 public:
  Connection_acceptor(Listener *listener) : m_listener(listener) {}

  ~Connection_acceptor() { delete m_listener; }

  /**
    Initialize a connection acceptor.

    @retval   return true if initialization failed, else false.
  */
  bool init_connection_acceptor() { return m_listener->setup_listener(); }

  /**
    Connection acceptor loop to accept connections from clients.
  */
  void connection_event_loop() {
    Connection_handler_manager *mgr =
        Connection_handler_manager::get_instance();
    while (!connection_events_loop_aborted()) {
      Channel_info *channel_info = m_listener->listen_for_connection_event();
      if (channel_info != nullptr) mgr->process_new_connection(channel_info);
    }
  }

监听连接事件

// sql/conn_handler/socket_connection.cc
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 (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;
  }

接受连接

// sql/conn_handler/socket_connection.cc
/**
  Accept a new connection on a ready listening socket.

  @param listen_sock  Listening socket ready to accept a new connection
  @param [out] connect_sock  Socket corresponding to a new accepted connection

  @return operation result
    @retval true on error
    @retval false on success
*/
static bool accept_connection(MYSQL_SOCKET listen_sock,
                              MYSQL_SOCKET *connect_sock) {
  struct sockaddr_storage c_addr;
  for (uint retry = 0; retry < MAX_ACCEPT_RETRY; retry++) {
    socket_len_t length = sizeof(struct sockaddr_storage);
    *connect_sock =
        mysql_socket_accept(key_socket_client_connection, listen_sock,
                            (struct sockaddr *)(&c_addr), &length);
    if (mysql_socket_getfd(*connect_sock) != INVALID_SOCKET ||
        (socket_errno != SOCKET_EINTR && socket_errno != SOCKET_EAGAIN))
      break;
  }

use master go if exists(select*from sysdatabases where name='bbsDB') drop database bbsDB create database bbsDB on primary ( name='bbsDB_data', filename='D:\project\bbsDB_data.mdf', size=10mb ) log on ( name='bbsDB_log', filename='D:\project\bbsDB_log.ldf', size=10mb ) go use bbsDB go create table bank ( customerName char(8) not null,--顾客姓名 cardID char(10) not null,--卡号 currentMoney Money not null,--当前余额 ) go create table transInfo ( cardID char(10) not null,--卡号 transType char(4) not null,--交易类型 transMoney money not null,--交易金额 transDate datetime not null,--交易时间 ) go alter table bank add constraint ck_currentMoney check(currentMoney>=1) alter table transInfo add constraint df_transDate default(getDate())for transDate,constraint ck_transType check(transType in('存入','支取')) go insert into bank(customerName,cardID,currentMoney)values('张三','100010001',2000) --insert into bank(customerName,cardID,currentMoney)values('李四','100010002',1) print '------取款前的余额------' select*from bank go begin transaction declare @errorSum int declare @myMoney Money set @myMoney=1000 --取款金额 set @errorSum=0 --取款 insert into transInfo(cardID,transType,transMoney)values('100010001','支取', @myMoney) set @errorSum=@errorSum+@@error update bank set currentMoney=currentMoney-@myMoney where cardID='100010001' set @errorSum=@errorSum+@@error print'------取款事务过程中余额和交易信息------' select*from bank select*from transInfo if @errorSum<>0 begin print'交易失败,回滚事务' rollback transaction end else begin print '交易成功,提交事务' commit transaction end go print'-------取款事务结束后的余额和交易信息------' select*from bank select*from transInfo go go begin transaction declare @errorSum int declare @myMoney Money set @myMoney=5000 --存入金额 set @errorSum=0 --存入 insert into transInfo(cardID,transType,transMoney)values('100010001','存入',@myMoney) set @errorSum=@errorSum+@@error update bank set currentMoney=currentMoney+@myMoney where cardID='100010001' set @errorSum=@errorSum+@@error print'------存款事务过程中余额和交易信息------' select*from bank select*from transInfo if @errorSum<>0 begin print'交易失败,回滚事务' rollback transaction end else begin print '交易成功,提交事务' commit transaction end go print'-------存款事务结束后的余额和交易信息------' select*from bank select*from transInfo go
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值