ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

mysql FTWRL

2022-07-12 10:03:40  阅读:150  来源: 互联网

标签:缓存 lock mysql time table FTWRL wait


转自:https://www.modb.pro/db/401005

【1】Flush Tables With Read Lock

一、FTWRL的原理

实际上这部分我们可以在函数mysql_execute_command寻找case SQLCOM_FLUSH 的部分,实际上主要调用函数为reload_acl_and_cache,其中核心部分为:

if (thd->global_read_lock.lock_global_read_lock(thd))//加 MDL GLOBAL 级别S锁
 return 1;                               // Killed
      if (close_cached_tables(thd, tables, //关闭表操作释放 share 和 cache
                              ((options & REFRESH_FAST) ?  FALSE : TRUE),
                              thd->variables.lock_wait_timeout)) //等待时间受lock_wait_timeout影响
      {
        /*
          NOTE: my_error() has been already called by reopen_tables() within
          close_cached_tables().
        */
        result= 1;
      }

      if (thd->global_read_lock.make_global_read_lock_block_commit(thd)) // MDL COMMIT 锁
      {
        /* Don't leave things in a half-locked state */
        thd->global_read_lock.unlock_global_read_lock(thd);
        return 1;
      }

更具体的关闭表的操作和释放table缓存的部分包含在函数close_cached_tables中,我就不详细写了。但是我们需要明白table缓存实际上包含两个部分:

  • table cache define:每一个表第一次打开的时候都会建立一个静态的表定义结构内存,当多个会话同时访问同一个表的时候,从这里拷贝成相应的instance供会话自己使用。由参数table_definition_cache定义大小,由状态值Open_table_definitions查看当前使用的个数。对应函数get_table_share。

  • table cache instance:同上所述,这是会话实际使用的表定义结构是一个instance。由参数table_open_cache定义大小,由状态值Open_tables查看当前使用的个数。对应函数open_table_from_share。

这里我统称为table缓存,好了下面是我总结的FTWRl的大概步骤:

第一步: 加MDL LOCK类型为GLOBAL 级别为S。如果出现等待状态为‘Waiting for global read lock’。注意select语句不会上GLOBAL级别上锁,但是DML/DDL/FOR UPDATE语句会上GLOBAL级别的IX锁,IX锁和S锁不兼容会出现这种等待。下面是这个兼容矩阵:

          | Type of active   |
  Request |   scoped lock    |
   type   | IS(*)  IX   S  X |
 ---------+------------------+
 IS       |  +      +   +  + |
 IX       |  +      +   -  - |
 S        |  +      -   +  - |
 X        |  +      -   -  - |

第二步:推进全局表缓存版本。源码中就是一个全局变量 refresh_version++。

第三步:释放没有使用的table 缓存。可自行参考函数close_cached_tables函数。

第四步:判断是否有正在占用的table缓存,如果有则等待,等待占用者释放。等待状态为'Waiting for table flush'。这一步会去判断table缓存的版本和全局表缓存版本是否匹配,如果不匹配则等待而等待的结束就是占用的table缓存的占用者释放,这个释放操作存在于函数close_thread_table中,如下:

if (table->s->has_old_version() || table->needs_reopen() ||
      table_def_shutdown_in_progress)
  {
    tc->remove_table(table);//关闭 table cache instance
    mysql_mutex_lock(&LOCK_open);
    intern_close_table(table);//去掉 table cache define
    mysql_mutex_unlock(&LOCK_open);
  }

最终会调用函数MDL_wait::set_status将FTWRL唤醒,也就是说对于正在占用的table缓存释放者不是FTWRL会话而是占用者自己。不管怎么样最终整个table缓存将会被清空,如果经过FTWRL后去查看Open_table_definitions和Open_tables将会发现重新计数了。下面是唤醒函数的代码,也很明显:

bool MDL_wait::set_status(enum_wait_status status_arg) open_table
{
  bool was_occupied= TRUE;
  mysql_mutex_lock(&m_LOCK_wait_status);
  if (m_wait_status == EMPTY)
  {
    was_occupied= FALSE;
    m_wait_status= status_arg;
    mysql_cond_signal(&m_COND_wait_status);//唤醒
  }
  mysql_mutex_unlock(&m_LOCK_wait_status);//解锁
  return was_occupied;
}

第五步:加MDL LOCK类型COMMIT 级别为S。如果出现等待状态为‘Waiting for commit lock’。如果有大事务的提交很可能出现这种等待。

二、FTWRL堵塞和被堵塞场景

(1)被什么堵塞
  • 长时间的DDL\DML\FOR UPDATE堵塞FTWRL,因为FTWRL需要获取 GLOBAL的S锁,而这些语句都会对GLOBAL持有IX(MDL_INTENTION_EXCLUSIVE)锁,根据兼容矩阵不兼容。等待为:Waiting for global read lock 。本文的案例1就是这种情况。
  • 长时间的select堵塞FTWRL, 因为FTWRL会释放所有空闲的table缓存,如果有占用者占用某些table缓存,则会等待占用者自己释放这些table缓存。等待为:Waiting for table flush 。即便KILL FTWRL会话也不行,除非KILL掉长时间的select操作才行。实际上flush table也会存在这种堵塞情况。
  • 长时间的commit(如大事务提交)也会堵塞FTWRL,因为FTWRL需要获取COMMIT的S锁,而commit语句会对commit持有IX(MDL_INTENTION_EXCLUSIVE)锁,根据兼容矩阵不兼容。
(2)堵塞什么
  • FTWRL会堵塞DDL\DML\FOR UPDATE操作,堵塞点为 GLOBAL级别 的S锁,等待为:Waiting for global read lock 。
  • FTWRL会堵塞commit操作,堵塞点为COMMIT的S锁,等待为Waiting for commit lock 。
  • FTWRL不会堵塞select操作,因为select不会在GLOBAL级别上锁。

最后提醒一下很多备份工具都要执行FTWRL操作,包含mysqldump和5.7的XTRBACKUP ,一定要注意它的堵塞/被堵塞场景和特殊场景,当然xtrbackup 8.0有所改善,我们后面进行分析

三、慢查询对FTWRL的记录方式

通常来讲DML(SELECT FOR UPDATE)和SELECT都会堵塞FTWRL,具体参见上面,而且我们知道这都是MDL LOCK堵塞,在以往的慢查询认知中,MDL LOCK堵塞是计入到慢查询的LOCK time,但是FTWRL却不一样。因为FTWRL不会过接口mysql_lock_tables,因此MDL LOCK堵塞时间不会被慢查询记录,正常的语句关于慢查询的部分大概如下:

1、语句开始, 记录开始时间点为 X
2、获取MDL LOCK   -> 正常语句如果本处超时(超过lock_wait_timeout)直接退出,也不会记录到下面一步记录的lock time时间中,因此lock time为0。
3、上MySQL层锁(比如myisam,记录lock time时间差量(1-3的时间差量Y)
4、优化器优化语句。
5、执行器开始执行语句,与innodb层交互,  如果每行需要等待innodb行锁,记录每次获取的lock time时间差量(第5步的每次等待innodb行锁的时间差量综合为Y)
6、语句结束,触发记录慢查询接口,并且获取当前时间点为(Z)判定记录慢查询如下。
如果(Z-X)-Y > 慢查询设置的时间则需要记录
也就是慢查询 query time:Z-X lock time为:Y
需要注意的是语句异常结束也会记录慢查询。

但是FTWRL不记录Y这个时间,因此慢查询中对于FTWRL 统计的方式变为了(Z-X),也就是它执行了多久就是多久,且lock time为0。*

超时(lock_wait_timeout设置为120)
# Time: 2022-05-04T16:26:35.226862+08:00
# User@Host: root[root] @ localhost []  Id: 10534
# Query_time: 120.004726  Lock_time: 0.000000 Rows_sent: 0  Rows_examined: 0
SET timestamp=1651652675;
flush table with read lock;

 kill FTWRL
# Time: 2022-05-04T16:27:22.215598+08:00
# User@Host: root[root] @ localhost []  Id: 10534
# Query_time: 30.727657  Lock_time: 0.000000 Rows_sent: 0  Rows_examined: 0
SET timestamp=1651652811;
flush table with read lock;

登录后复制

上面两个例子表明了这一点,我们注意到Lock_time为0。还需要注意的是如果正常语句遇到lock_wait_timeout超时记录的慢查询的Lock_time也是0,原因一致(不过接口mysql_lock_tables),如下:

# Time: 2022-05-06T11:06:29.665024Z
# User@Host: root[root] @ localhost []  Id:   257
# Schema: test  Last_errno: 1205  Killed: 0
# Query_time: 120.004564  Lock_time: 0.000000  Rows_sent: 0  Rows_examined: 0  Rows_affected: 0
# Bytes_sent: 67
SET timestamp=1651835189;
select * from testup11;

可以看到这是一个select由于Waiting for table flush等待超时,但是语句Lock_time为0,执行时间大约为120秒,也就是超时时间(lock_wait_timeout设置为120)。

标签:缓存,lock,mysql,time,table,FTWRL,wait
来源: https://www.cnblogs.com/gered/p/16468945.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有