MySQL · 源码分析 · Query Cache并发处理

本文涉及的产品
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
RDS PostgreSQL Serverless,0.5-4RCU 50GB 3个月
推荐场景:
对影评进行热评分析
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介: Query cache 的并发处理上期介绍了Query cache的一个基本工作原理,请参考MySQL · 源码分析 · Query Cache内部剖析。本期将对Query cache的并发处理过程进行一个剖析。当前Query cache是所有session共享的,也就是说同一条SELECT语句 + database + flag(包含影响执行结果的所有环境变量)构成的Key如果已经存储在

Query cache 的并发处理

上期介绍了Query cache的一个基本工作原理,请参考MySQL · 源码分析 · Query Cache内部剖析。本期将对Query cache的并发处理过程进行一个剖析。

当前Query cache是所有session共享的,也就是说同一条SELECT语句 + database + flag(包含影响执行结果的所有环境变量)构成的Key如果已经存储在Query cache中了,任何session都可以从Query cache中获取想要的结果集。所有session共享Query cache,那如何处理并发呢?当前Query cache只支持查询,插入,删除操作,不支持更新。下面我们将对这三种操作的并发原理进行分析。

在对三种操作进行分析之前,我们先来看看Query cache 并发处理的方式。Query cache的并发处理,同样是利用锁。对于Query cache对象自身的所有操作使用一把mutex锁来进行并发控制。Query_cache在其初始化,即调用Query_cache::init的时候,会初始化如下锁变量:

void Query_cache::init()
{
  mysql_mutex_init(key_structure_guard_mutex,
            &structure_guard_mutex, MY_MUTEX_INIT_FAST);
  mysql_cond_init(key_COND_cache_status_changed,
            &COND_cache_status_changed, NULL);
    m_cache_lock_status= Query_cache::UNLOCKED;
  ……
}

说明

key_structure_guard_mutex以及key_COND_cache_status_changed这两个变量是用来处理Query cache与PSI(Performance schema instrumentation interface)相关的并发控制,这里我们不对其进行介绍,如果有兴趣可以参考PSI的相关介绍。

另外一个mutex变量structure_guard_mutex用来控制Query cache的并发访问,同时它也用来配合mysql_cond_t key_COND_cache_status_changed来控制对Query cache锁的超时处理。我们会在稍后介绍加锁处理的地方进行具体描述。

m_cache_lock_status控制当前Query cache所处的状态。该变量有3个值:

UNLOCKED 表明当前Query cache处于未被使用状态。该状态下我们使用mutex来控制Query cache的并发访问。
LOCKED_NO_WAIT 表明当前的Query cache正处于Flush或者是正在关闭使用Query cache的状态。
LOCKED 表明当前的Query cache正在被使用。此时我们利用mysql_cond_t来进行加锁,同时支持锁定超时。

Query cache中一个重要的控制并发的函数是Query_cache::try_lock,也就是加锁过程,算法实现如下:

bool Query_cache::try_lock(bool use_timeout)
{
  mysql_mutex_lock(&structure_guard_mutex); //首先试图获取mutex
  while(1)
  {
    if (m_cache_lock_status == Query_cache::UNLOCKED)
    {
      m_cache_lock_status= Query_cache::LOCKED; //如果Query cache未被锁定,那么我们修改其状态为锁定状态。利用mutex进行加锁。
      break;
    }
    else if (m_cache_lock_status == Query_cache::LOCKED_NO_WAIT)
    {
      interrupt= TRUE; //这里表示Query cache正在被Flush或者处于关闭状态,没有必要再加锁继续进行操作。遇到这种状态,需要加锁的操作将直接返回。
      break;
    }
    else
    {
      if (use_timeout) //这个参数是控制是否需要超时处理。
      {
        set_timespec_nsec(waittime,(ulong)(50000000L));  // 50微秒超时
        int res= mysql_cond_timedwait(&COND_cache_status_changed,
                        &structure_guard_mutex, &waittime);
      }
      else
      {
        mysql_cond_wait(&COND_cache_status_changed, 
                        &structure_guard_mutex);
      }
    }
  }
}

Query cache的记录查询,插入都需要先使用Query_cache::try_lock加锁。使用Query_cache::try_lock加锁的主要原因是可以检查Query cache所处的锁定状态,如果Query cache正在FLUSH或者关闭,记录查询或者插入都将没有意义,因此检查到锁定状态为Query_cache::LOCKED_NO_WAIT就可以直接返回了。

对于删除Query cache中的记录,操作前进行的锁定是Query_cache::lock。该函数与Query_cache::try_lock的唯一区别就是不再检查Query_cache::LOCKED_NO_WAIT状态,一直等待直到获取Query cache锁。

Query cache的记录查询

基本流程如下:(下面的函数定义写的都是伪代码,如需了解详情请参考MySQL源码)

Query_cache::send_result_to_client(…)
{
  If (!SELECT语句)
    return;
  if (try_lock()) 
    return;
  构造Query cache中Key值(Key值包含了query + database + flag(包含影响执行结果的所有环境变量));
  query_block= 通过Key值查找Query cache中的Query_cache_block;

  if (!query_block) //未找到任何记录
    return;

  if (query_block->result_type == Query_cache_block::RESULT) // 这里的条件是用来判断与该条Query相关的结果集是否已经被完全的写入了Query cache中。如果结果集没有全部写入,显然我们也不能返回结果集。
  {
    RD_lock (query_block); //这个Query_cache_block的块锁应该没什么用处,因为所有操作都需要Query cache的全局mutex。
    if (表的权限检查成功)
      返回结果集;
    RD_unlock(query_block); //释放Query_cache_block的Read锁。
  }

  unlock(); // 释放Query cache的全局mutex。
}

Query cache数据的插入

目前插入流程如下:

Query_cache::store_query();
// 该函数首先生成Query_cache_block的header部分。
// header包含哪几部分请参考往期月报, MySQL · 源码分析 · Query Cache内部剖析。
// 生成的header会挂到thd->query_cacne_tld.first_query_block。
// thd->query_cacne_tld.first_query_block用来在接下来的Query_cache::insert()过程中判断是否当前session需要缓存结果集。

注意:Query cache目前实现中只有生成Query_cache_block header的session才可以为该block添加数据,
     其他session如果输入同样的执行语句,在调用Query_cache::store_query()会发现已经有session生成了header,就不会再重复生成header了。
     这样实现的目的是让一个session负责写入所有的结果集,可以避免其他session进行干扰。

Query_cache::insert(…) //负责将结果集缓存到Query_cache_block的数据部分。
{
  if (query_block= thd->query_cache_tls->first_query_block) //检查当前session是否需要缓存结果集
  {
    if (try_lock()) 
      return;
    RW_lock(query_block->query()->lock); //这里的写锁同样没有作用了,因为Query cache的mutex会对并发进行控制。
      append_result_data(); //将结果集缓存到Query_cache_block中。
    RW_unlock(query_block->query()->lock); //释放排他锁。
    unlock(); // 释放Query cache的全局排他锁。
  }
}

Query cache的删除:

Query_cache::invalidate_table(…)
{
  lock(); 
  // 这里使用lock而非try_lock,是因为我们需要强制失效所有与table相关的Query_cache_block。
  // 而try_lock会在Query cache的状态为Query_cache::LOCKED_NO_WAIT的时候直接返回。
  invalidate_table_internal(); //失效所有与指定表相关的Query cache。
  unlock(); //释放全局mutex。
}

对于Query cache的失效部分,目前的处理方式非常暴力,任何对表数据的修改,包括UPDATE/INSERT/DELETE操作,都会将该表相关的所有Query cache记录实效掉,这种实效方式影响非常大。建议增加对于WHERE,HAVING等过滤条件的判断,如果Query cache中的记录涉及的结果集与当前UPDATE/INSERT/DELETE所涉及的数据没有交集,我们完全没有必要实效掉这样的记录。比如:

SELECT * FROM t WHERE t.a > 10;

我们对于这样一条SELECT语句进行结果集的缓存。对于如下的INSERT/UPDATE/DELETE 语句来说,我们完全没有必要去失效与这条SELECT语句相关的结果集缓存,因为下面这几条语句操作的数据集和SELECT的结果集没有发生任何交集。

INSERT INTO t (a) VALUES(1);

UPDATE t SET a=4 WHERE a < 5;

DELETE FROM t WHERE a < 5;

对于DDL其实我们也可以做的更好,比如对于下面这条SELECT语句的结果集缓存记录来说:

SELECT a FROM t WHERE t.a > 10;

如果对于下面的DDL,完全可以不去失效SELECT语句的结果集缓存记录。

ALTER TABLE t ADD COLUMN c INT;

总而言之,Query cache的并发处理的粒度比较大,几乎所有的操作都需要拿到Query cache的全局mutex。如果可以对Query cache的全局状态变量使用Free lock,只对于存储分配使用mutex,对Query_cache_block进行加锁处理会对性能有所改进。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
13天前
|
SQL 关系型数据库 MySQL
MySQL的match WITH QUERY EXPANSION 模式是什么?如何使用?
【8月更文挑战第29天】MySQL的match WITH QUERY EXPANSION 模式是什么?如何使用?
27 4
|
22天前
|
关系型数据库 MySQL Java
解决com.mysql.cj.jdbc.exceptions.PacketTooBigException: Packet for query is too large
这篇文章提供了解决MySQL JDBC驱动中`com.mysql.cj.jdbc.exceptions.PacketTooBigException: Packet for query is too large`错误的步骤,主要是通过增加配置文件中的`max_allowed_packet`参数值并重启服务来允许更大的数据包传输。
解决com.mysql.cj.jdbc.exceptions.PacketTooBigException: Packet for query is too large
|
10天前
|
SQL 关系型数据库 MySQL
MySQL 的 WITH QUERY EXPANSION 如何使用举例
【9月更文挑战第2天】MySQL 的 WITH QUERY EXPANSION 如何使用举例
19 0
|
10天前
|
存储 关系型数据库 MySQL
MySQL MATCH 函数如何使用 WITH QUERY EXPANSION?
【9月更文挑战第2天】MySQL MATCH 函数如何使用 WITH QUERY EXPANSION?
23 0
|
2月前
|
DataWorks 监控 关系型数据库
利用 DataWorks 数据推送定期推播 MySQL 或 StarRocks Query 诊断信息
DataWorks 近期上线了数据推送功能,能够将数据库查询的数据组织后推送到各渠道 (如钉钉、飞书、企业微信及 Teams),除了能将业务数据组织后推送,也能将数据库自身提供的监控数据组织后推送,这边我们就以 MySQL (也适用于StarRocks) 为例,定期推播 MySQL 的数据量变化等信息,帮助用户掌握 MySQL 状态。
86 1
|
2月前
|
存储 大数据 数据库
MySQL设计规约问题之为什么要利用pt-query-digest定期分析slow query log并进行优化
MySQL设计规约问题之为什么要利用pt-query-digest定期分析slow query log并进行优化
|
2月前
|
负载均衡 关系型数据库 MySQL
MySQL PXC集群多个节点同时大量并发update同一行
如本文标题,MySQL PXC集群多个节点同时大量并发update同一行数据,会怎样? 为此,本人做了一个测试,来验证到底会怎样!
30 0
|
2月前
|
SQL 关系型数据库 MySQL
[已解决]com.mysql.cj.jdbc.exceptions. PacketTooBigException: Packet for query is too large (3,456,888
[已解决]com.mysql.cj.jdbc.exceptions. PacketTooBigException: Packet for query is too large (3,456,888
15 0
|
3月前
|
SQL 安全 关系型数据库
MySQL数据库——事务-简介、事务操作、四大特性、并发事务问题、事务隔离级别
MySQL数据库——事务-简介、事务操作、四大特性、并发事务问题、事务隔离级别
67 1
|
3月前
|
SQL 关系型数据库 MySQL
MySQL并发事务是怎么处理的?
这篇内容探讨了数据库并发事务处理,特别是MySQL中的策略。文章指出并发编程常面临安全性和一致性的挑战,Java使用synchronized和Lock等机制,而MySQL通过事务隔离和MVCC(多版本并发控制)来解决。MVCC允许读事务无需等待写事务,通过保存数据的多个版本来避免冲突,提高并发性能。文章还分析了并发事务的三种情况,并解释了MVCC如何通过Read View选择可见数据版本。最后总结了事务隔离级别对并发处理的影响以及MVCC的关键作用。

相关产品

  • 云数据库 RDS MySQL 版