MySQL 소스코드 분석

MySQL 소스코드 분석

7가지 Major Directories

  1. BUILD
  2. client
  3. Docs
  4. myisam
  5. mysys - toolbox directory. e.g. sort, changing charactoer set…
  6. sql - parser, handler, statement… functions.
  7. vio - virtual I/O, vio routines are wrappers for the various network I/O calls that happen with different protocols.

Path

  • /sql/mysqld.cc
	int main(int argc, char **argv)
  {
    _cust_check_startup();
    (void) thr_setconcurrency(concurrency);
    init_ssl();
    server_init();                             // 'bind' + 'listen'
    init_server_components();
    start_signal_handler();
    acl_init((THD *)0, opt_noacl);
    init_slave();
    create_shutdown_thread();
    create_maintenance_thread();
    handle_connections_sockets(0);             // next path
    DBUG_PRINT("quit",("Exiting main thread"));
    exit(0);
  }

main 함수 부분. mysql 시작

	void handle_connections_sockets (arg __attribute__((unused))
  {
     if (ip_sock != INVALID_SOCKET)
     {
       FD_SET(ip_sock,&clientFDs);
       DBUG_PRINT("general",("Waiting for connections."));
       while (!abort_loop)
       {
         new_sock = accept(sock, my_reinterpret_cast(struct sockaddr*)
           (&cAddr),             &length);
         thd= new THD;
         if (sock == unix_sock)
         thd->host=(char*) localhost;
         create_new_thread(thd);            // next path
         }

main thread가 새로 들어오는 클라이언트의 리퀘스트를 항상 듣고있다. 새로운 리퀘스트를 받으면, 다른 클라이언트와 독립적으로 구분되는 자원을 할당 해 준다.

  void create_new_thread(THD *thd)
  {
    pthread_mutex_lock(&LOCK_thread_count);
    pthread_create(&thd->real_id,&connection_attrib,
        handle_one_connection,                        // next path
        (void*) thd));
    pthread_mutex_unlock(&LOCK_thread_count);
  }

새로운 스레드 생성됨.

  • /sql/sql_parse.cc
	void handle_one_connection(THD *thd)
  {
    init_sql_alloc(&thd->mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
    while (!net->error && net->vio != 0 && !thd->killed)
    {
      if (do_command(thd))            // next path
        break;
    }
    close_connection(net);
    end_thread(thd,1);
    packet=(char*) net->read_pos;

bool do_command(THD *thd)
{
  net_new_transaction(net);
  packet_length=my_net_read(net); // gets a packet from the client
  packet=(char*) net->read_pos;
  command = (enum enum_server_command) (uchar) packet[0];
  dispatch_command(command,thd, packet+1, (uint) packet_length); // next path
}

패킷을 클라이언트로 받아온다.

bool dispatch_command(enum enum_server_command command, THD *thd,
       char* packet, uint packet_length)
{
  switch (command) {
    case COM_INIT_DB:          ...
    case COM_REGISTER_SLAVE:   ...
    case COM_TABLE_DUMP:       ...
    case COM_CHANGE_USER:      ...
    case COM_EXECUTE:
         mysql_stmt_execute(thd,packet);  // next path
    case COM_LONG_DATA:        ...
    case COM_PREPARE:
         mysql_stmt_prepare(thd, packet, packet_length);  // **
    /* and so on for 18 other cases */
    default:
     send_error(thd, ER_UNKNOWN_COM_ERROR);
     break;
    }

** Prepare:

Allocate a new statement, keep it in ‘thd->prepared statements’ pool Return to client the total number of parameters and result-set metadata information (if any)”

위 코드는 굉장히 긴 switch 문장의 일부분임. 여러 갈림길 중. mysql_stmt_execute path를 살펴본다.

  void mysql_stmt_execute(THD *thd, char *packet)
  {
    if (!(stmt=find_prepared_statement(thd, stmt_id, "execute")))
    {
      send_error(thd);
      DBUG_VOID_RETURN;
    }
    init_stmt_execute(stmt);
    mysql_execute_command(thd);           // next path
  }

excutes statate 관련.

  void mysql_execute_command(THD *thd)
       switch (lex->sql_command) {
       case SQLCOM_SELECT: ...
       case SQLCOM_SHOW_ERRORS: ...
       case SQLCOM_CREATE_TABLE: ...
       case SQLCOM_UPDATE: ...
       case SQLCOM_INSERT: ...                   // next path
       case SQLCOM_DELETE: ...
       case SQLCOM_DROP_TABLE: ...
       }

insert를 예로 들어보자.

case SQLCOM_INSERT:
{
  my_bool update=(lex->value_list.elements ? UPDATE_ACL : 0);
  ulong privilege= (lex->duplicates == DUP_REPLACE ?
                    INSERT_ACL | DELETE_ACL : INSERT_ACL | update);
  if (check_access(thd,privilege,tables->db,&tables->grant.privilege))
    goto error;
  if (grant_option && check_grant(thd,privilege,tables))
    goto error;
  if (select_lex->item_list.elements != lex->value_list.elements)
  {
    send_error(thd,ER_WRONG_VALUE_COUNT);
    DBUG_VOID_RETURN;
  }
  res = mysql_insert(thd,tables,lex->field_list,lex->many_values,
                     select_lex->item_list, lex->value_list,
                     (update ? DUP_UPDATE : lex->duplicates)); // next path
  if (thd->net.report_error)
    res= -1;
  break;
}

insert시 해당 테이블에 적절한 권한이 있는지 등을 체크한다. 이것은 side path이고 mysql_insert를 따라 내려가 보면,

 int mysql_insert(THD *thd,TABLE_LIST *table_list, List<Item> &fields,
        List<List_item> &values_list,enum_duplicates duplic)
  {
    table = open_ltable(thd,table_list,lock_type);
    if (check_insert_fields(thd,table,fields,*values,1) ||
      setup_tables(table_list) ||
      setup_fields(thd,table_list,*values,0,0,0))
      goto abort;
    fill_record(table->field,*values);
    error=write_record(table,&info);                 // next path
    query_cache_invalidate3(thd, table_list, 1);
    if (transactional_table)
      error=ha_autocommit_or_rollback(thd,error);
    query_cache_invalidate3(thd, table_list, 1);
    mysql_unlock_tables(thd, thd->lock);
    }

테이블 오픈, insert fail 체크 등의 에러 체크, 레코드 버퍼 체우기, 레코드에 값 쓰기, 쿼리캐시 invalidate 시키기 등을 수행한다.

  int write_record(TABLE *table,COPY_INFO *info)
  {
    table->file->write_row(table->record[0];           // next path
  }

write_row함수는 handler에 따라 여러 곳에 정의되어 있음.

int ha_myisam::write_row(byte * buf)
{
  statistic_increment(ha_write_count,&LOCK_status);
   /* If we have a timestamp column, update it to the current time */
   if (table->time_stamp)
    update_timestamp(buf+table->time_stamp-1);
   /*
  If we have an auto_increment column and we are writing a changed row
    or a new row, then update the auto_increment value in the record.
  */
  if (table->next_number_field && buf == table->record[0])
    update_auto_increment();
  return mi_write(file,buf);     // next path
}
int mi_write(MI_INFO *info, byte *record)
{
  _mi_readinfo(info,F_WRLCK,1);
  _mi_mark_file_changed(info);
  /* Calculate and check all unique constraints */
  for (i=0 ; i < share->state.header.uniques ; i++)
  {
    mi_check_unique(info,share->uniqueinfo+i,record,
      mi_unique_hash(share->uniqueinfo+i,record),
      HA_OFFSET_ERROR);
  }

  ... to be continued in next snippet
}

아직 여기까지는 테이블이나 파일, 인덱스 키값에 대한 언급이 없다.

 ... continued from previous snippet

  /* Write all keys to indextree */
  for (i=0 ; i < share->base.keys ; i++)
  {
    share->keyinfo[i].ck_insert(info,i,buff,
      _mi_make_key(info,i,buff,record,filepos)
  }
  (*share->write_record)(info,record);
  if (share->base.auto_key)
    update_auto_increment(info,record);
}

mi_write 함수의 후반부에 다른 명확한 주석이 표시되어 이것이 인덱싱 된 열에 대해 새로운 키가 만들어지는 곳이라는 것을 알 수 있습니다.

지금까지 Mysql Server Code의 Stack trace는 다음과 같다.

main in /sql/mysqld.cc
handle_connections_sockets in /sql/mysqld.cc
create_new_thread in /sql/mysqld.cc
handle_one_connection in /sql/sql_parse.cc
do_command in /sql/sql_parse.cc
dispatch_command in /sql/sql_parse.cc
mysql_stmt_execute in /sql/sql_prepare.cc
mysql_execute_command in /sql/sql_parse.cc
mysql_insert in /sql/mysql_insert.cc
write_record in /sql/mysql_insert.cc
ha_myisam::write_row in /sql/ha_myisam.cc
mi_write in /myisam/mi_write.c

mysql 5.7 version

mysqld_main        32  sql/main.cc
  onnection_event_loop  5125  sql/mysqld.cc
    process_new_connection    68  sql/conn_handler/connection_acceptor.h
      add_connection    268  sql/conn_handler/connection_handler_manager.cc

---another thread---

handle_connection		236	sql/conn_handler/connection_handler_per_thread.cc
  do_command        306  sql/conn_handler/connection_handler_per_thread.cc
    dispatch_command  1002  return_value= dispatch_command(thd, &com_data, command);
      mysql_parse      1509  mysql_parse(thd, &parser_state);
        mysql_execute_command  5658  error= mysql_execute_command(thd, true);
          execute          3640  sql/sql_parse.cc
            mysql_insert     3107  res= mysql_insert(thd, all_tables);
              write_record      769  error= write_record(thd, insert_table, &info, &update);
               handler::ha_write_row     1539  sql/sql_insert.cc
                  ha_innobase::write_row        7995  sql/handler.cc

dispatch_command에서 COM_QUERY가 계속 요청됨, mysql_execute_command에서 상세 쿼리 종류에 따라 path가 달라지게 됨.