MySQL源代碼管中窺豹(一):磁盤寫滿以後,數據庫show status受到阻塞的緣由

注意:html

1..如下全部討論都基於mysql 5.5.37版本及官方文檔,不保證適用於其餘版本。mysql

2.下文中提到的磁盤滿,指的是數據文件(數據文件,日誌文件,配置文件)所在磁盤分區。sql

3.因爲篇幅問題,最後面的代碼部分,只有關鍵的函數及邏輯判斷部分。數據庫

前兩天同事討論到一個問題,當mysql從庫磁盤滿以後,show status及show slave status會被卡住,但其餘select操做不受影響,但若是數據庫是主庫,磁盤滿了以後,只有dml會被阻塞,select及show是不會受影響的。因而一羣人討論了一會,最後決定,SMC,如下就是個人結論。app

直接查官文檔的話,官方做以下表示:函數

https://dev.mysql.com/doc/refman/5.5/en/full-disk.html測試

1.實例每分鐘檢查是否有足夠的空間寫入。若是已經有空間了,繼續執行操做。ui

2.每十分鐘給日誌文件寫入一條記錄,報告磁盤已經寫滿。spa

但就個人實驗來講,上面的說法存在很多問題。線程

下面是我對官方文檔的測試結果:

1.若是主庫上打開binlog,那麼當磁盤滿以後,每10分鐘,數據庫會報告一條Disk is full writing './mysql-bin.000001' (Errcode: 28). Waiting for someone to free space... (Expect up to 60 secs delay for server to continue after freeing disk space),也就是說bin log寫滿了,等待磁盤空間,這與文檔描述相同。

2,若是在主庫上關閉binlog,當磁盤滿了以後,任何插入行爲都會失敗,報錯爲[ERROR] ./mysqld: The table 'x' is full,官方文檔沒有提到這個狀況,此處的表x是innodb表。

上面是對主庫所在磁盤寫滿以後,數據庫實例的反應,下面講講咱們遇到的狀況:從庫磁盤寫滿以後,show status及show slave status會被卡住,但其餘select操做不受影響。

首先,如下是結論:

整個流程涉及3把鎖:

1.mi->data_lock

2.LOCK_active_mi

3.LOCK_status

說明以下(如下操做安編號順序執行):

1.當一個新操做被接收到slave io線程後,若是這時候磁盤寫滿了,這個寫入操做就會被阻塞,而後等待,直到磁盤有空間以後繼續寫入,這個操做中,會持有mi->data_lock鎖,只有操做完成或者操做失敗後,這個鎖纔會被釋放,剛好,磁盤滿不屬於錯誤,因而操做阻塞,該線程會一直持有mi->data_lock鎖。

2.當發起一個show slave status請求的時候,執行的時候,會首先鎖住LOCK_active_mi鎖,而後鎖定mi->data_lock鎖,固然,如今的狀況下,mi->data_lock不會獲得,因而LOCK_active_mi鎖就會被該線程持續持有。

3.另外其一個會話發起show global status,執行的時候首先鎖定LOCK_status鎖,因爲show status包括,Slave_heartbeat_period,Slave_open_temp_tables,Slave_received_heartbeats               ,Slave_retried_transactions這些狀態,因而還須要LOCK_active_mi鎖,因而,這個會話也會被阻塞掉。

4.以後若是再另外發起請求,因爲LOCK_status已經被鎖定,因而全部涉及show status的請求,都會被阻塞到這裏。

5.以後全部show slave status請求也都會被阻塞在LOCK_active_mi鎖處。

看了以上的結論,是否會想到另一個操做順序:磁盤寫滿-》show status,這種操做的結果是:show status不會被阻塞的。

如下是mysql源代碼(5.5.37)涉及到的具體部分:

1.io線程阻塞的相關函數及部分代碼

slave.cc

pthread_handler_t handle_slave_io(void *arg)
if (queue_event(mi, event_buf, event_len))//寫入slave日誌函數
      {
        mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE,
                   ER(ER_SLAVE_RELAY_LOG_WRITE_FAILURE),
                   "could not queue event from master");
        goto err;
      }

slave.cc

static int queue_event(Master_info* mi,const char* buf, ulong event_len)
mysql_mutex_lock(&mi->data_lock);
mysql_mutex_lock(log_lock);
if (likely(!(rli->relay_log.appendv(buf,event_len,0))))//寫入執行函數
    {
      mi->master_log_pos+= inc_pos;
      DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos));
      rli->relay_log.harvest_bytes_written(&rli->log_space_total);
    }
  mysql_mutex_unlock(log_lock);
err:
  mysql_mutex_unlock(&mi->data_lock);

log.cc

bool MYSQL_BIN_LOG::appendv(const char* buf, uint len,...)
{
  bool error= 0;
  DBUG_ENTER("MYSQL_BIN_LOG::appendv");
  va_list(args);
  va_start(args,len);
  DBUG_ASSERT(log_file.type == SEQ_READ_APPEND);
  mysql_mutex_assert_owner(&LOCK_log);
  do
  {
    if (my_b_append(&log_file,(uchar*) buf,len))
    {
      error= 1;
      goto err;
    }
    bytes_written += len;
  } while ((buf=va_arg(args,const char*)) && (len=va_arg(args,uint)));
  DBUG_PRINT("info",("max_size: %lu",max_size));
  if (flush_and_sync(0))//把日誌數據刷入磁盤
    goto err;
  if ((uint) my_b_append_tell(&log_file) > max_size)
    error= new_file_without_locking();
err:
  if (!error)
    signal_update();
  DBUG_RETURN(error);
}

log.cc

bool MYSQL_BIN_LOG::flush_and_sync(bool *synced)
{
  int err=0, fd=log_file.file;
  if (synced)
    *synced= 0;
  mysql_mutex_assert_owner(&LOCK_log);
  if (flush_io_cache(&log_file))
    return 1;
  uint sync_period= get_sync_period();
  if (sync_period && ++sync_counter >= sync_period)
  {
    sync_counter= 0;
    err= mysql_file_sync(fd, MYF(MY_WME));//同步寫入文件
    if (synced)
      *synced= 1;
  }
  return err;
}

mf_locache.c

int my_b_flush_io_cache(IO_CACHE *info,
                        int need_append_buffer_lock __attribute__((unused)))
if (mysql_file_write(info->file,info->write_buffer,length,
   info->myflags | MY_NABP))
info->error= -1;
      else
info->error= 0;
mysql_file.h
static inline size_t
inline_mysql_file_write(
#ifdef HAVE_PSI_INTERFACE
  const char *src_file, uint src_line,
#endif
  File file, const uchar *buffer, size_t count, myf flags)
{
  size_t result;
#ifdef HAVE_PSI_INTERFACE
  struct PSI_file_locker *locker= NULL;
  PSI_file_locker_state state;
  if (likely(PSI_server != NULL))
  {
    locker= PSI_server->get_thread_file_descriptor_locker(&state, file,
                                                   if (likely(locker != NULL))
      PSI_server->start_file_wait(locker, count, src_file, src_line);
  }
#endif
  result= my_write(file, buffer, count, flags);//寫入文件
#ifdef HAVE_PSI_INTERFACE
  if (likely(locker != NULL))
  {
    size_t bytes_written;
    if (flags & (MY_NABP | MY_FNABP))
      bytes_written= (result == 0) ? count : 0;
    else
      bytes_written= (result != MY_FILE_ERROR) ? result : 0;
    PSI_server->end_file_wait(locker, bytes_written);
  }
#endif
  return result;
}

my_write.c

size_t my_write(File Filedes, const uchar *Buffer, size_t Count, myf MyFlags)
{
for (;;)
  {
#ifdef _WIN32
    writtenbytes= my_win_write(Filedes, Buffer, Count);
#else
    writtenbytes= write(Filedes, Buffer, Count);//調用系統函數
#endif
}
} /* my_write */

errors.c

void wait_for_free_space(const char *filename, int errors)
{
  if (!(errors % MY_WAIT_GIVE_USER_A_MESSAGE))
  {
    my_printf_warning(EE(EE_DISK_FULL),
             filename,my_errno,MY_WAIT_FOR_USER_TO_FIX_PANIC);
    my_printf_warning("Retry in %d secs. Message reprinted in %d secs",
                    MY_WAIT_FOR_USER_TO_FIX_PANIC,
                    MY_WAIT_GIVE_USER_A_MESSAGE * MY_WAIT_FOR_USER_TO_FIX_PANIC );
  }
  DBUG_EXECUTE_IF("simulate_no_free_space_error",
                 {
                   (void) sleep(1);//直接退出
                   return;
                 });
  (void) sleep(MY_WAIT_FOR_USER_TO_FIX_PANIC);//等待時間
}

2.show slave status相關的函數及部分代碼

sql_parse.cc

bool dispatch_command(enum enum_server_command command, THD *thd,//轉發請求
      char* packet, uint packet_length)
case COM_QUERY:
...
mysql_parse(thd, thd->query(), thd->query_length(), &parser_state);

sql_parse.cc

void mysql_parse(THD *thd, char *rawbuf, uint length,
                 Parser_state *parser_state)
error= mysql_execute_command(thd);
sql_parse.c
int mysql_execute_command(THD *thd)
case SQLCOM_SHOW_SLAVE_STAT://執行
  {
    /* Accept one of two privileges */
    if (check_global_access(thd, SUPER_ACL | REPL_CLIENT_ACL))
      goto error;
    mysql_mutex_lock(&LOCK_active_mi);//加鎖
    if (active_mi != NULL)
    {
      res = show_master_info(thd, active_mi);//獲得信息
    }
    else
    {
      push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
                   WARN_NO_MASTER_INFO, ER(WARN_NO_MASTER_INFO));
      my_ok(thd);
    }
    mysql_mutex_unlock(&LOCK_active_mi);//解鎖
    break;
  }

slave.cc

bool show_master_info(THD* thd, Master_info* mi)
  if (mi->host[0])
  {
    DBUG_PRINT("info",("host is set: '%s'", mi->host));
    String *packet= &thd->packet;
    protocol->prepare_for_resend();
    /*
      slave_running can be accessed without run_lock but not other
      non-volotile members like mi->io_thd, which is guarded by the mutex.
    */
    
    mysql_mutex_lock(&mi->run_lock);
    protocol->store(mi->io_thd ? mi->io_thd->proc_info : "", &my_charset_bin);
    mysql_mutex_unlock(&mi->run_lock);
    mysql_mutex_lock(&mi->data_lock);//加鎖
    mysql_mutex_lock(&mi->rli.data_lock);
    mysql_mutex_lock(&mi->err_lock);
    mysql_mutex_lock(&mi->rli.err_lock);
  
  ...
    mysql_mutex_unlock(&mi->rli.err_lock);
    mysql_mutex_unlock(&mi->err_lock);
    mysql_mutex_unlock(&mi->rli.data_lock);
    mysql_mutex_unlock(&mi->data_lock);//解鎖
    if (my_net_write(&thd->net, (uchar*) thd->packet.ptr(), packet->length()))
      DBUG_RETURN(TRUE);
  }

3.show status相關的函數及部分代碼

mysqld.cc

static int show_heartbeat_period(THD *thd, SHOW_VAR *var, char *buff)
{
  mysql_mutex_lock(&LOCK_active_mi);//加鎖
  if (active_mi)
  {
    var->type= SHOW_CHAR;
    var->value= buff;
    sprintf(buff, "%.3f", active_mi->heartbeat_period);
  }
  else
    var->type= SHOW_UNDEF;
  mysql_mutex_unlock(&LOCK_active_mi);//解鎖
  return 0;
}

sql_show.cc

static bool show_status_array(THD *thd, const char *wild,SHOW_VAR *variables,enum enum_var_type value_type,truct system_status_var *status_var,onst char *prefix, TABLE *table,bool ucase_names,COND *cond)
{
    for (var=variables; var->type == SHOW_FUNC; var= &tmp)
      ((mysql_show_var_func)(var->value))(thd, &tmp, buff); //此處調用前面的函數show_heartbeat_period
}

sql_show.cc

int fill_status(THD *thd, TABLE_LIST *tables, COND *cond)
{
  if (thd->fill_status_recursion_level++ == 0) 
    mysql_mutex_lock(&LOCK_status);//加鎖
  if (option_type == OPT_GLOBAL)
    calc_sum_of_all_status(&tmp);
  res= show_status_array(thd, wild,
                         (SHOW_VAR *)all_status_vars.buffer,
                         option_type, tmp1, "", tables->table,
                         upper_case_names, cond);
  if (thd->fill_status_recursion_level-- == 1) 
    mysql_mutex_unlock(&LOCK_status);//解鎖
  DBUG_RETURN(res);
}
mysql_mutex_lock(&mi->data_lock);
相關文章
相關標籤/搜索