MySQL源码分析-慢查询

源码版本:MySQL 5.7.22

一条sql语句在MySQL中执行超过一定时间,会被记录为慢查询,慢查询相关的参数有以下几个:

  • slow_query_log=1
  • long_query_time=0.5
  • slow_query_log_file=/mysql/data/mysql_slow.log
  • log_queries_not_using_indexes=1
  • log_throttle_queries_not_using_indexes=20
  • min_examined_row_limit=100
  • log_slow_admin_statements=1
  • log_slow_slave_statements=1

slow_query_log: 控制是否记录慢查询;
long_query_time: 慢查询阈值,单位秒,执行时间超过这个值的将被记录为慢查询日志中;
slow_query_log_file: 慢查询日志路径;
log_queries_not_using_indexes: 没有使用索引的sql也将被记录到慢查询日志中;
log_throttle_queries_not_using_indexes: 如果log_queries_not_using_indexes打开,没有使用索引的sql将会写入到慢查询日志中,该参数将限制每分钟写入的sql数量;
min_examined_row_limit: 对于查询扫描行数小于此参数的sql,将不会记录到慢查询日志中;
log_slow_admin_statements: 管理语句执行时间大于阈值也将写入到慢查询日志中,管理语句包括alter table, check table等等;
log_slow_slave_statements: 从库应用binlog,如果binlog格式是statement,执行时间超过阈值时,将写入从库的慢查询日志, 对于ROW格式binlog,不管执行时间有没有超过阈值,都不会写入到从库的慢查询日志。

线索一:long_query_time变量

根据慢查询相关参数,在源码中搜索long_query_time,源码中肯定有执行时间大于这个值,就记为慢查询的相关代码逻辑,搜索到一段代码如下:

//sql/sql_class.h 3279行
void update_server_status()
  {
    ulonglong end_utime_of_query= current_utime();
    if (end_utime_of_query > utime_after_lock + variables.long_query_time)
      server_status|= SERVER_QUERY_WAS_SLOW;
  }

这段代码里的if条件是 end_utime_of_query > utime_after_lock + variables.long_query_time, 我们稍微变换一下:
end_utime_of_query - utime_after_lock > variables.long_query_time

end_utime_of_query 为sql执行完取到的当前时间,单位微秒;
utime_after_lock 为sql执行期间锁等待的时间,也就是说MySQL慢查询并不把锁等待的时间算在里面,每次执行sql前,会通过thd->set_time()将该变量重置为当前时间,单位微秒;
variables.long_query_time 为慢查询阈值。

线索二:update_server_status函数

update_server_status函数是THD类的成员函数,utime_after_lock 为THD类的成员变量,下面是THD类的定义简化示意:

//sql/sql_class.h
class THD :public MDL_context_owner,
           public Query_arena,
           public Open_tables_state
{
...
    public:
      ulonglong  start_utime, utime_after_lock;
...
    public:
    void update_server_status()
      {
        ulonglong end_utime_of_query= current_utime();
        if (end_utime_of_query > utime_after_lock +  variables.long_query_time)
          server_status|= SERVER_QUERY_WAS_SLOW;

      }
...
};

下面来看一下update_server_status这个THD的成员函数,什么时候被调用。MySQL 是Server/Engine 架构, 慢查询相关逻辑在Server层进行处理,先看一下MySQL执行一条sql的函数调用栈,如下:

main                //main.cc
mysqld_main         //mysqld.cc
mysqld_socket_acceptor->connection_event_loop    //mysqld.cc
Connection_handler_manager::process_new_connection(Channel_info* channel_info)  //connection_handler_manager.cc
Per_thread_connection_handler::add_connection(Channel_info* channel_info)  //connection_handler_per_thread.cc
handle_connection        //connection_handler_per_thread.cc
do_command               //sql_parse.cc
dispatch_command         //sql_parse.cc
mysql_parse              //sql_parse.cc
mysql_execute_command    //sql_parse.cc
...

在源码中搜索update_server_status, 在dispatch_command函数中,看到如下一段代码逻辑,简化后如下:

// sql/sql_parse.cc
bool dispatch_command(THD *thd, const COM_DATA *com_data,
                      enum enum_server_command command)
{
    thd->set_time();
    mysql_parse(thd, &parser_state);
    thd->update_server_status();
    thd->send_statement_status();
    log_slow_statement(thd);
}

其中
thd->set_time() 对utime_after_lock进行了重置,重置后的值为当前时间,单位是微秒,后面附thd->set_time()函数的具体实现。
mysql_parse(thd, &parser_state) 为sql执行的具体实现,包括调用引擎层的接口,向客户端发送查询结果数据等。
thd->update_server_status() 判断执行时间是否大于设置的慢查询阈值,如果大于慢查询阈值,设置server_status|= SERVER_QUERY_WAS_SLOW
thd->send_statement_status() 发送sql语句执行状态
log_slow_statement(thd) 根据thd->server_status 是否包含 SERVER_QUERY_WAS_SLOW 标志,决定是否写入慢查询日志

线索三:utime_after_lock

另外一个问题,utime_after_lock 这个值应该是不断改变的,因为它记录了锁等待的时间,代码中哪里修改了这个变量值呢?继续搜索代码,发现这么一个函数:

// sql/sql_class.cc
extern "C"
void thd_storage_lock_wait(THD *thd, long long value)
{
  thd->utime_after_lock+= value;
}

这段代码由引擎层调用,引擎层遇到锁等待时,调用这个函数,修改thd->utime_after_lock 变量的值

总结:

至此,MySQL在Server层判断一个sql执行是否属于慢查询的逻辑变得清晰了,总结分以下几步:
(1)sql执行前,通过thd->set_time()函数,记录当前时间(微秒)到 utime_after_lock
(2)sql执行过程中,引擎层遇到锁等待,将等待的时间(微秒)通过thd_storage_lock_wait函数调用,加到utime_after_lock变量上
(3)sql执行完,再次记录当前时间(微秒)到end_utime_of_query, 通过 判断 (end_utime_of_query - utime_after_lock > variables.long_query_time)这个if条件,来决定是否记录到慢查询日志。

附相关函数:

// sql/sql_class.h  THD成员函数
inline void set_time()
  {
    start_utime= utime_after_lock= my_micro_time();
    if (user_time.tv_sec || user_time.tv_usec)
    {
      start_time= user_time;
    }
    else
      my_micro_time_to_timeval(start_utime, &start_time);
#ifdef HAVE_PSI_THREAD_INTERFACE
    PSI_THREAD_CALL(set_thread_start_time)(start_time.tv_sec);
#endif
  }
// sql/sql_class.h   THD成员函数current_utime,实际调用的还是my_micro_time
ulonglong current_utime()  { return my_micro_time(); }

// my_getsystime.c
ulonglong my_micro_time()
{
#ifdef _WIN32
  ulonglong newtime;
  my_get_system_time_as_file_time((FILETIME*)&newtime);
  newtime-= OFFSET_TO_EPOCH;
  return (newtime/10);
#else
  ulonglong newtime;
  struct timeval t;
  /*
    The following loop is here because gettimeofday may fail on some  systems
  */
  while (gettimeofday(&t, NULL) != 0)
  {}
  newtime= (ulonglong)t.tv_sec * 1000000 + t.tv_usec;
  return newtime;
#endif
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容