MySQL源码:索引相关的数据结构(后篇)

   前篇介绍了MySQL存储索引信息的基本数据结构。本篇将延续下去,介绍MySQL如何找到可以使用的索引,以及期间需要使用的主要数据结构。

   谁适合阅读: 本文不打算从High Level来介绍MySQL索引及其使用,相反是从MySQL源码对应的数据结构开始介绍。如果你了解MySQL索引的基本原理,还打算继续从源码的角度解决一些索引使用的问题,那么你适合参考本文,否则,打住,真的很枯燥:(。在可见的未来,作者还将介绍Range优化相关的数据结构等。

0. 概述

   本文介绍MySQL如何发现WHERE条件中的等值表达式,并通过分析这些等值表达式,找到可以使用的索引。在这个过程中,MySQL将递归的访问所有WHERE条件"谓词",并将等值表达式都存储到KEY_FIELD对象的数组中。

   然后遍历该KEY_FIELD数组,并同时对比所有索引列,找到哪些字段是在索引列中出现,这些字段则可能可以使用索引,MySQL将所有这些字段都存储在对象KEYUSE数组中。

   最后,对KEYUSE进行处理,包括排序、删除无法使用的索引列。这时KEYUSE数组就是所有可以使用REF的索引列了。

1. KEY_FIELD

1.1 概述

   在函数JOIN::optimize/make_join_statistics/update_ref_and_keys中,对所有WHERE条件中的等值表达式,都认为可能会走上索引,所以都暂时存放到KEY_FIELD数组中。例如有表达式:"seller_id = 631389273",那么KEY_FIELD数组中就有对应的对象。结构如下:

(gdb) b add_key_part
Breakpoint 2 at 0x6009e1: file sql_select.cc, line 3668.
(gdb) c
Continuing.

(gdb) p key_field[0]
$44 = {
  field = 0x7f6514011728,      # 对应seller_id字段
  val = 0x7f6514005ae0,        # 指向值为631389273的Item
  level = 0,
  optimize = 0,
  eq_func = true,
  null_rejecting = false,
  cond_guard = 0x0
}

   MySQL在后面的处理中,会遍历所有的KEY_FIELD,如果发现恰好有对应的索引在这个字段上,就会将该索引标记为可以使用。选择执行计划的时候,就会考虑使用这个索引。

1.2 定义

 3065 typedef struct key_field_t {
 3066   Field         *field;
 3067   Item          *val;                   ///< May be empty if diff constant
 ......
 3077 } KEY_FIELD;

   KEY_FIELD的Field和Item字段分表存储了字段和对应的值。

1.3 KEY_FIELD数组

   假设有更复杂一点的WHERE条件:

WHERE
  seller_id =631389273 AND
  gmt_modified = \'2012-02-12 09\' and
  PARENT_ID=119985497951753 and
  AUCTION_ID= 8932244966

   上面每个条件都会生成一个对应的KEY_FIELD对象来存储,对应的KEY_FIELD数组结构图如下:

   indexofkeyfield

2. MySQL如何生成KEY_FIELD数组(概述)

   在函数update_ref_and_keys中,先根据WHERE条件生成KEY_FIELD数组,再进一步处理,最后找到所有REF可以使用的索引。

2.1 update_ref_and_keys函数的主流程

(1) 函数通过add_key_fields将所有的可能用到的索引字段,全部都放到key_fields数组中
    (1.1) 遍历WHERE树,递归调用add_key_fields。对每一个Item_func,调用一次add_key_fields
    (1.2) 对每一个Item_func(有两个Item),调用add_key_equal_fields
          (1.2.1) 一般来说Item_func(如col > 10),有两个Item
    (1.3) add_key_equal_fields函数
          (1.3.1)将调用add_key_field
                 该函数将等值比较放到KEY_FIELD数组中
                 不等值,如果可能用上索引,则存放到key_map对象join_tab->const_keys中。
                 详细的:
                 {
                 输入:WHERE中的一个子表达式,例如col > 10
                 处理:
                     (1) field->key_start 全都加入possible keys;
                         即所有以col开头的索引,都是可能的索引
                     (2) 如果Field op constant则将,直接放到possible keys?
                 结果:
                     (1) key1 > 10 会直接存放到possible keys然存放存放到join_tab->const_keys
                     (2) key1 = 10 and key2 > 10,会放到possible keys,
                         再存放到join_tab->const_keys。key1会存放到key_field数组中
                 }
(2) 调用add_key_part将所有的KEY_FIELD存放到数组KEYUSE
(3) 移除KEYUSE数组中无法使用part,例如之使用了索引的第二个字段; 对KEYUSE排序,
    相同的KEY的字段放一起
    (3.1) 先使用my_sort进行排序:根据table/index/keypart对所有的KEYUSE对象进行排序
          3986     my_qsort(keyuse->buffer,keyuse->elements,sizeof(KEYUSE),
          3987           (qsort_cmp) sort_keyuse);

   (这里的(2),(3)步骤,在本篇文章后部分将详细解释)

   这个函数会对所有=比较表达式相关的谓词都放入key_fields当中,然后,MySQL会根据各个索引字段信息生成对应的KEYUSE数组。

   WHERE seller_id =631389273 AND   gmt_modified > \'2012-02-12 09\'\\G

   这样的WHERE条件之后,我们看到,key_field里面只存储了一个对象,里面存储的是field是SELLER_ID。

2.2 函数的调用栈

#0 add_key_field
#1 add_key_fields
#2 update_ref_and_keys
#3  make_join_statistics
#4  JOIN::optimize

3. KEY_FIELD数组转化成KEYUSE对象

3.1 KEYUSE对象

   KEY_FIELD数组中包含了所有等值表达式对应字段,但并不是所有这些字段都有对应的索引。KEYUSE对象就是用来存储所有,有索引的KEY_FIELD,并将更多索引信息存储到KEYUSE中,以便后续使用。这个过程分两步:筛选;排序;再筛选。

3.1.0 定义
(gdb) p s->keyuse[4]
$90 = {
 table = 0x7f5bb800e980,
 val = 0x7f5bb8001570, 		# 存储对应的值,这里是\'2012-02-12 09\'
 used_tables = 0,
 key = 6,					# 使用第6个索引
 keypart = 1, 				# 从零开始,keypart=1表示使用的第二个column
 optimize = 0,
 keypart_map = 2, 			# 二进制11,使用前面两个column
 ref_table_rows = 18446744073709551615,
 null_rejecting = false,
 cond_guard = 0x0
}
3.1.1 筛选
for ( ; field != end ; field++) #遍历key_field数组@update_ref_and_keys
{
  for (uint key=0 ; key < table->keys ; key++){  #遍历所有索引@add_key_part
    for (uint part=0 ; part <  key_parts ; part++)  #遍历索引所有字段@add_key_part
    {
       if field->eq(table->key_info[key].key_part[part].field){
         #如果索引字段跟key_field中的字段相同
         
         insert_dynamic(keyuse_array,(uchar*) &keyuse);
       }
    }
  }
}
3.1.2 排序

   这一步较简单,MySQL会根据table/index/key part对所有的KEYUSE对象进行排序:

 3986     my_qsort(keyuse->buffer,keyuse->elements,sizeof(KEYUSE),
 3987           (qsort_cmp) sort_keyuse);

   这里,my_qsort是一个通用快排函数,排序顺序安装函数sort_keyuse给出:tablenr越大,值越大;索引编号越大,值越大;索引列越靠前,值越大。

3.1.3 再筛选

   前面筛选,会将所有在索引中的字段都放到KEYUSE数组中,这里将继续移除以下的KEYUSE对象:

   (1) 某个列虽然是索引列,但是KEYUSE中没有前导列。例如有key(a,b,c)但条件只有b < 5,则移除。

   (2) 如果有等值和等值引用,则移除后面的等值引用,如有key(a,b)和条件a=3 and b=7 and b=t2.d,那么就会移除条件b=t2.d。

   条件(1)很好理解,B-Tree索引不能简单的使用这样的字段做索引。这里解释一下条件(2)。看如下场景:

CREATE TABLE `employee` (
  `LastName` varchar(20) DEFAULT NULL,
  `DepartmentID` int(11) DEFAULT NULL,
  KEY `从` (`LastName`,`DepartmentID`)
);
CREATE TABLE `department` (
  `DepartmentID` int(11) DEFAULT NULL,
  `DepartmentName` varchar(20) DEFAULT NULL,
  KEY `IND_D` (`DepartmentID`)
)
做如下查询:
SELECT
  *
FROM
  employee right outer JOIN department
ON
  employee.DepartmentID = department.DepartmentID and
  employee.DepartmentID=33 and
  employee.lastname = \'Zhou\'

   因为right join,所以department顺序总是在前。MySQL在考察employee表可以走哪些索引的时候,先收集到三个KEY_FIELD等值表达式,因为索引IND_L_D包含了这两个字段,所以这三个等值表达式都会存储到KEYUSE数组中。而三个KEYUSE在数组的中的顺序如下:

   KEYUSE(lastname,\'Zhou\'),KEYUSE(DepartmentID,33),KEYUSE(DepartmentID,department.DepartmentID)

   这里的第三个等式,是一个引用,但是employee是连接的外部表,所以在扫描employee时,将忽略第三个条件,对应的KEYUSE将删除这个条件。

   更多解释和疑问:(1) KEYUSE排序会将常数放在前面 (2) 一个疑问,ON条件中的employee.lastname = \'Zhou\',放在ON里面和放在WHERE里面有什么区别?

3.2 完整的KEYUSE数组

|-> p s->keyuse[1]
          |-->keyuse[0]  [KEYUSE]   |->   { table = 0x7f5bb800e980,
INDEX[1]->|-->keyuse[1] ----------> |->     ...
          |-->keyuse[2]             |->     key = 1,           #使用第一个索引
                                    |->     keypart = 1,       #从零开始,1表示使用的第二个column
          |-->keyuse[3]             |->     keypart_map = 2,   #二进制11,使用前面两个column
INDEX[3]->|                         |->     ...
          |-->keyuse[4]             |->   }

觉得文章有用?立即: 和朋友一起 共学习 共进步!

猜您喜欢

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>