Mysql 中query cache 的代码导读

本文主要对mysql中SQL层的query cache代码阅读的记录。

1. 背景知识(参考《Mysql性能调优与架构设计》)
MySQL 可以看成是二层架构,第一层我们通常叫做 SQL Layer,在 MySQL 数据库系统处理底层数据之前的所有工作都是在这一层完成的,包括权限判断,sql 解析,执行计划优化, query cache 的处理等等;第二层就是存储引擎层,我们通常叫做 Storage Engine Layer,也就是底层数据存取操作实现部分,由多种存储引擎共同组成。

Query Cache 模块在 MySQL 中是一个非常重要的模块,他的主要功能是将客户端提交给MySQL 的 Select 类 query 请求的返回结果集 cache 到内存中,与该 query 的一个 hash 值做一个对应。该 Query 所取数据的基表发生任何数据的变化之后,MySQL 会自动使该 query 的Cache 失效。在读写比例非常高的应用系统中,Query Cache 对性能的提高是非常显著的。当然它对内存的消耗也是非常大的。

如果是一个 Query 类型的请求,会将控制权交给 Query 解析器。Query 解析器首先分析看是不是一个 select 类型的 query,如果是,则调用查询缓存模块,让它检查该 query 在query cache 中是否已经存在。如果有,则直接将 cache 中的数据返回给连接线程模块,然后通过与客户端的连接的线程将数据传输给客户端。

2. Query Cache 的结构与主要构成
Query Cache 的结构与构成说明在mysql代码注释中给出了非常详细的解释(sql/sql_cache.cc开头部分)。
以下是一些简略说明:

  19 1. Query_cache object consists of
  20                - query cache memory pool (cache)
  21                - queries hash (queries)
  22                - tables hash (tables)
  23                - list of blocks ordered as they allocated in memory
  24 (first_block)
  25                - list of queries block (queries_blocks)
  26                - list of used tables (tables_blocks)
  27
  28 2. Query cache memory pool (cache) consists of
  29                - table of steps of memory bins allocation
  30                - table of free memory bins
  31                - blocks of memory

关于Query cache memory pool, 代码中看得不是太懂。 queries hash就是目前被系统缓存的select语句列表,在代码中对应的变量名为queries。 first_block一般是一条查询语句被缓存时初始化时创建并保存在thd->query_cache_tls 中(Query_cache::store_query, sql/sql_cache.cc)。queries_blocks是系统中所有缓存的select语句对应的缓存block的双向环形链表,新增的block插入到queries_blocks指针所指定的位置。 tables是query cache中表 block的hash。tables_blocks是query cache中所有表 block的双向链表。
table相关链表的重要用途之一是当缓存对应的表修改时,通过table链表删除这个表相关联的缓存(后面会介绍)。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读


缓存中的block 有主要有3种类型, query,table和result分别用于保存查询语句,查询关联到的表和查询对应的缓存结果。 每种类型的block的结构都是一个Query_cache_block structure的表头,里面有些块相关信息。然后是关联到的table列表,接着是每种类型block特有的表头信息,最后才是数据。
---------------------------------------
|Query_cache_block                      |
---------------------------------------
|Query_cache_block_table |
---------------------------------------
....
---------------------------------------
|Query_cache_block_table |
---------------------------------------
| query/table/result header|
---------------------------------------
| data                                                                          |
---------------------------------------

在Query_cache_block  结构下面是n个Query_cache_block_table结构。如果是queryblock的话n为这个query语句所关联的table的数量;如果是tableblock的话n为1(固定,即代码中看到的 table(0));如果是resultblock的话n为0。然后是具体类型的表头,接着是数据。
这里的数据(data)根据不同类型的block而存不同类型的东西,query的存的是lex中的query(),table块存的是数据库名+表名,result存的是真正的缓存数据。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读

每个缓存的query语句对应一个Query block,这个block中table list保存的是这个query相关联的表,然后这个query block通过result指针与它对应的缓存数据列表相关联。 缓存数据存放在result block的双向链表中。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读

一个table的block根据table(0)指针指向这个表所涉及到的query的block(其实是这个block中table list中的对应Query_cache_block_table结构)。由图可见,这是个环形链表,有新的query block被关联进来时,就直接从table(0)所指向的位置插入。 这样做的好处是,如果因为某个表被修改而要删除和它相关的所有缓存时,可以通过这个链表把所有关联的query block给找到,然后删除它们和它们关联的result block。 表block的key是“db名+表名”。

缓存操作的7个(类)接口函数:
Mysql <wbr>中query <wbr>cache <wbr>的代码导读

本文下面会提到的主要有:
send_result_to_client 在缓存中查找指定sql查询语句,如果找到了并且可用,把缓存的数据直接发给客户端。
store_query 在实际处理执行一条查询前调用,缓存这条语句,并且为这条语句生成对应的query_cache_tls,用于后面的数据保存。
query_cache_insert 把查询语句执行结果缓存到query cache中,这个方法在服务端的连接模块要向客户端发送查询结果数据包前被调用(sql/net_serv.cc 中的my_net_write,net_write_buff与net_real_write方法)。
Query_cache::invalidate 把被修改的表对应的缓存从query cache中删除。在执行insert, update, 与delete命令时都会调用这个方法。
Query_cache::invalidate_locked_for_write 这个在锁表时,如SQLCOM_LOCK_TABLES命令时会调用。

3. 读缓存的相关代码导读
sql/sql_parse.cc 的mysql_parse函数, 所有语句的执行都会从这个函数开始。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读


通过注释和代码可以知道, 当一条query语句来的时候,mysql会先到缓存中进行查找(函数query_cache_send_result_to_client),缓存中找不到或不可用的情况下才会进行语句的lex解析与执行。
query_cache_send_result_to_client这个函数是个宏,其实就是send_result_to_client函数 (sql/sql_cache.cc)。

在send_result_to_client中, mysql首先会检查这个语句是否是可缓存了(只有select语句,并且是从表中取数据的查询语句才是可缓存的)。
然后mysql会根据查询语句生成一个用于在缓存中查找的key, key的结构为:query语句+database+flag 。 其中database是thd->db的值, flag是一系列的标识(标识的具体意义还不是很清楚)。
从这里可以看出,query cache缓存的key是根据具体sql语句来生存了,也就是说,即便是同样意义的select语句,如果写法不一样,那么也不会被匹配。
例如 一个表 t1 :  create t1 (a int,b int);
那么select * from t1; 和 select a,b from t1; 将不是同一个缓存。执行后一条语句时是不会返回前一条语句的缓存数据的。更不用说下面这条语句 select a from t1;

但是在mysql中如果你先执行了select * from t1; , 那么select a,b from t1;和select a from t1;确实是性能会有提升。 这主要是因为一般在存储引擎层还会有意个缓存,例如innodb的buffer pool。 这一级的缓存会具体分析语句涉及的字段。关于innodb的缓存本文就不介绍了,以后会在别的文章中介绍。

Mysql <wbr>中query <wbr>cache <wbr>的代码导读

上面的代码实现主要是根据临时包表的要求,如果这个连接中有临时表的话,自动隐藏同名非临时表,而临时表相关的查询是不缓存的。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读
经过一些权限和其他的检查之后,把找到的缓存数据发送给客户端,主要调用send_data_in_chunks方法(sql/sql_cache.cc)。在send_data_in_chunks中,mysql把缓存中的数据切分成每个1M大小的包并发送给客户端。

4. 写缓存的相关代码导读
当无法从缓存中获取时,mysql就开始解析和执行从客户端来的查询语句。 在sql/sql_parse.cc的 mysql_execute_command函数中,各种类型的sql语句被一个大型swith进行分类。
case SQLCOM_SELECT: 中是关于select语句的处理, 其中调用了方法execute_sqlcom_select。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读
execute_sqlcom_select函数在实际处理查询(handle_select)之前,通过调用query_cache_store_query初始化并缓存这条查询语句,以便后面实际查询处理完成后把数据缓存进query cache.
query_cache_store_query实际调用的是Query_cache::store_query函数。
在这个函数中,mysql为新的query语句生成 key (query+db+flag,和第3节中介绍的一致)。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读
然后mysql开始为这个query分配一个query block, 上面的代码write_block_data函数中 参数local_tables是这个查询关联的表的数量, 在生成的block中要为每个关联的表分配一个query_cache_block_table (见第2节)。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读

Mysql <wbr>中query <wbr>cache <wbr>的代码导读

Mysql <wbr>中query <wbr>cache <wbr>的代码导读
然后插入hash表,把这个block 加入到它所关联的所有表的Query_cache_block_table链表中,以便将来要删除某个表所关联的缓存时使用,最后把这个query block关联到全局的block 链表中。

在register_all_tables中 mysql取出 新生成query block的第一个Query_cache_block_table结构(其实是当作Query_cache_block_table列表的启始地址),然后调用
register_tables_from_list来把这个query用到的table block(没有的话,新建)与对应的Query_cache_block_table结构建立关联。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读
在register_tables_from_list中,mysql遍历 query用到的table列表 和queryblock中的Query_cache_block_table结构。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读
然后调用insert_table函数把Query_cache_block_table结构插入到对应表的Query_cache_block_table列表中。 这里的key是用于查找table block的key (db+table)。

insert_table函数中先根据key查找对应的table block, 如果没有的话就新分配一个,新分配的block中Query_cache_block_table结构的个数为1。新分配的block要插入对应的hash表(tables)与全局table block列表(tables_blocks)。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读
然后就是进行链接了。 这里的node是传参,即query block中的Query_cache_block_table结构。


在handle_select函数中通过调用mysql_select(sql/sql_select.cc ),而mysql_select又调用了JOIN::exec() (sql/sql_select.cc ), exec()函数中又调用了select_send::send_data (sql/sql_class.cc)。
从这里开始mysql就开始调用连接模块把查询结果发送给客户端,而缓存结果的操作也就是在连接模块中被调用的。
send_data函数调用了Protocol::write()(sql/protocol.cc)。write函数中调用了 my_net_write(sql/net_serv.cc), my_net_write又调用了net_real_write。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读
在net_real_write中调用了query cache的接口函数query_cache_insert 来缓存查询结果(sql/sql_cache.cc)。
query_cache_insert方法中调用了insert方法, 主要的操作就是把数据包封装成一个result block,然后把这个block连接到query block的result环形链表中(这里具体的代码比较多了,设计到block的申请,数据块切分成多个result block进行保存之类的,等等)。

5.删除缓存的代码导读
当缓存的查询语句关联的表发生更改时, 如 insert, update, delete 数据, query cache中该表相关联的缓存要相应的被删除。
删除操作主要调用的是query cache的接口函数invalidate(sql/sql_cache.cc)。

在sql/sql_cache.cc根据不同的情况和需求重载了好几个invalidate函数。

Mysql <wbr>中query <wbr>cache <wbr>的代码导读
当处理插入操作时(sql/sql_parse.cc  mysql_execute_command函数的大swith中的
case SQLCOM_INSERT: 与 case SQLCOM_INSERT_SELECT:) 都直接或间接地调用了query_cache_invalidate3.

SQLCOM_INSERT是在调用的mysql_insert(sql/sql_insert.cc)中调用的。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读
可以看出query_cache_invalidate3实际上就是调用了invalidate函数。
当处理修改时 (case SQLCOM_UPDATE:)与删除(case SQLCOM_DELETE)通过mysql_update(sql/sql_update.cc)与mysql_delete(sql/sql_delelte.cc)来调用
query_cache_invalidate3。

Mysql <wbr>中query <wbr>cache <wbr>的代码导读
这个是query_cache_invalidate3调用的invalidate函数,根据注释我们可以看出,它会把与指定表相关联的query的缓存删除。

遍历所有被修改的表,进行一些检验与例外情况处理之后,调用invalidate_table 函数来删除缓存。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读
invalidate_table函数会删除table列表中第一个表相关的缓存。 其中分为表被打开和表没被打开(drop 一个表)两种情况。

可以看出最终都是调用3个参数的invalidate_table函数,根据key(db名+表名)来进行缓存的删除。
在3参数的invalidate_table中又调用了invalidate_table_internal。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读
invalidate_table_internal中根据key在全局缓存的tables列表中找到这个table对应的block, 然后根据table(0)指针得到这个表所关联的所有query的query block。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读

Mysql <wbr>中query <wbr>cache <wbr>的代码导读
query block通过block指针得到Query_cache_block对象(通过当前指针位置减去n个Query_cache_block_table地址长度,再减去一个Query_cache_block),Query_cache_block的链表是个双向链表(本文的第2节中有介绍),next指向下一个节点,prev指向上一个节点。
最后通过调用free_query来释放这个query block。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读
先删除key然后再把对应的block删除。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读
可以看出这里被删除的缓存其实是被放回缓存池中以供之后的缓存操作使用。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读
从全局query双向链表中把这个query block删除,然后删除这个查询所关联的表。
Mysql <wbr>中query <wbr>cache <wbr>的代码导读

Mysql <wbr>中query <wbr>cache <wbr>的代码导读

Mysql <wbr>中query <wbr>cache <wbr>的代码导读
删除query cache的同时肯定要把它相关连的result cache给删除了, result block也是一个环行链表。
最后在删除并回收这个query block。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值