前言

本文主要调试一下 mysql 的如下两种查询语句 

我们也来深入的看一下, 究竟如下两个普通的查询, mysql 做了什么事情 

1. select * from `user` where id = '991';

2. select * from `user`;

3. select * from `user` where name = 'jerry991';

环境介绍

测试表 `user` schema 如下 

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `age` int(11) DEFAULT '0',
  `name` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

`user` 表中的测试数据如下 

06 mysql all查询 和 主键查询 和 非索引列查询_index

select * from `user` where id = '991'

我们这里会着重介绍这个 case, 这里面会详细的看一下 index page, data page 的情况, 以及其具体的执行的流程 

另外这个过程中我们会忽略掉一些细节, 尽量只查看这个业务查询的核心业务流程 

获取 index page 

index -> page 为 3, 表示第三个 page 为索引页面, pageNo 初始化为 index->page, 该 page 的地址为 block->frame : 0x1217f4000 

06 mysql all查询 和 主键查询 和 非索引列查询_迭代_02

我们 inspect 一下 0x1217f4000 

可以看出 offset 为 3, 对应于上面的 pageNo 

pagePrev, pageNext 为 0xffffffff, 表示只有一个索引页面 

pageNDirSlots 表示当前 page 存在两个 pageDirectory 

pageHeapTop 表示当前页面的 FreeSpace 位于 0xa2 

pageNHeap 表示有 5 个元素, 三个索引元素 + infimum + supremum 

pageNRecords 表示有三个索引元素 

pageLevel 为 1 表示下面一级才是叶子节点[存储数据的节点] 

再看下面的索引信息 [1, 302) -> page4, [302, 719) -> page5, [719, supremum] -> page6 

(lldb) x 0x1217f4000 -c 0x100
0x1217f4000: c2 b7 03 ad 00 00 00 03 ff ff ff ff ff ff ff ff  ·.�....��������
0x1217f4010: 00 00 00 00 00 1d 34 e5 45 bf 00 00 00 00 00 00  ......4�E�......
0x1217f4020: 00 00 00 00 00 06 00 02 00 a2 80 05 00 00 00 00  .........�......
0x1217f4030: 00 9a 00 02 00 02 00 03 00 00 00 00 00 00 00 00  ................
0x1217f4040: 00 01 00 00 00 00 00 00 00 16 00 00 00 06 00 00  ................
0x1217f4050: 00 02 00 f2 00 00 00 06 00 00 00 02 00 32 01 00  ...�.........2..
0x1217f4060: 02 00 1b 69 6e 66 69 6d 75 6d 00 04 00 0b 00 00  ...infimum......
0x1217f4070: 73 75 70 72 65 6d 75 6d 00 10 00 11 00 0e 80 00  supremum........
0x1217f4080: 00 01 00 00 00 04 00 00 00 19 00 0e 80 00 01 2e  ................
0x1217f4090: 00 00 00 05 00 00 00 21 ff d6 80 00 02 cf 00 00  .......!��...�..
0x1217f40a0: 00 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0x1217f40b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0x1217f40c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0x1217f40d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0x1217f40e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0x1217f40f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................


# FileHeader 
FIL_PAGE_SPACE = c2 b7 03 ad 
FIL_PAGE_OFFSET = 00 00 00 03 
FIL_PAGE_PREV = ff ff ff ff 
FIL_PAGE_NEXT = ff ff ff ff 
FIL_PAGE_LSN = 00 00 00 00 00 1d 34 e5 
FIL_PAGE_TYPE = 45 bf 
FIL_PAGE_FILE_FLUSH_LSN = 00 00 00 00 00 00 00 00 
FIL_PAGE_ARCH_LOG_NO = 00 00 00 06 

# PageHeader 
PAGE_N_DIR_SLOTS = 00 02 
PAGE_HEAP_TOP = 00 a2 
PAGE_N_HEAP = 80 05 
PAGE_FREE = 00 00 
PAGE_GARBAGE = 00 00 
PAGE_LAST_INSERT = 00 9a 
PAGE_DIRECTION = 00 02 
PAGE_N_DIRECTION = 00 02 
PAGE_N_RECS = 00 03 
PAGE_MAX_TRX_ID = 00 00 00 00 00 00 00 00 
PAGE_LEVEL = 00 01 
PAGE_INDEX_ID = 00 00 00 00 00 00 00 16 
PAGE_BTR_SEG_LEAF = 00 00 00 06 00 00 00 02 00 f2 
PAGE_BTR_SEG_TOP = 00 00 00 06 00 00 00 02 00 32 

# page4
0x 00 = nulls
0x 1 = delete flag
0x 0 = number of records owned by the record
0b 0000 0000 0001 0 = 2 = order number of this record
0b 001 = node pointer (inside B-tree)
0x 00 0e = next record offset -> page05
minId = 0x 80 00 00 01 = 1
pageNo = 0x 00 00 00 04

# page5
0x 00 = nulls
0x 0 = delete flag
0x 0 = number of records owned by the record
0b 0000 0000 0001 1 = 3 = order number of this record
0b 001 = node pointer (inside B-tree)
0x 00 0e = next record offset -> page06
minId = 0x 80 00 01 2e = 302
pageNo = 0x 00 00 00 05

# page6
0x 00 = nulls
0x 0 = delete flag
0x 0 = number of records owned by the record
0b 0000 0000 0010 0 = 4 = order number of this record
0b 001 = node pointer (inside B-tree)
0x ff d6 = next record offset -> supremum
minId = 0x 80 00 02 cf = 719
pageNo = 0x 00 00 00 06

根据 index page 确定数据所在 page 

通过上面 index page 里面的索引信息, 以及我们这里查询的 991 限定来确定数据属于哪一个 page 

这里的查询会有两层搜索, 通过 pageDirectory 的搜索, 通过最近的 pageDirectory 线性查询目标索引, 这里 991 属于 page6 

看这里的 low_rec, 指向的记录就是上面的 [719, supremum] -> page6 

06 mysql all查询 和 主键查询 和 非索引列查询_迭代_03

获取数据 page 

pageNo 为 6, 获取 page6, 该 page 的地址为 block->frame : 0x121804000 

06 mysql all查询 和 主键查询 和 非索引列查询_select_04

我们来 inspect 一下 0x121804000

(lldb) x 0x121804000 -c 0x100 
0x121804000: e5 86 89 21 00 00 00 06 00 00 00 05 ff ff ff ff  �..!........����
0x121804010: 00 00 00 00 00 1d a6 f8 45 bf 00 00 00 00 00 00  ......��E�......
0x121804020: 00 00 00 00 00 06 00 47 27 fc 81 1b 00 00 00 00  .......G'�......
0x121804030: 27 df 00 02 01 18 01 19 00 00 00 00 00 00 00 00  '�..............
0x121804040: 00 00 00 00 00 00 00 00 00 16 00 00 00 00 00 00  ................
0x121804050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00  ................
0x121804060: 02 00 1c 69 6e 66 69 6d 75 6d 00 06 00 0b 00 00  ...infimum......
0x121804070: 73 75 70 72 65 6d 75 6d 08 00 00 00 10 00 24 80  supremum......$.
0x121804080: 00 02 cf 00 00 00 00 41 75 f1 00 00 01 e7 01 10  ..�....Au�...�..
0x121804090: 80 00 00 16 6a 65 72 72 79 37 31 39 08 00 00 00  ....jerry719....
0x1218040a0: 18 00 24 80 00 02 d0 00 00 00 00 41 76 f2 00 00  ..$...�....Av�..
0x1218040b0: 01 e8 01 10 80 00 00 16 6a 65 72 72 79 37 32 30  .�......jerry720
0x1218040c0: 08 00 00 00 20 00 24 80 00 02 d1 00 00 00 00 41  .... .$...�....A
0x1218040d0: 77 f3 00 00 01 e9 01 10 80 00 00 16 6a 65 72 72  w�...�......jerr
0x1218040e0: 79 37 32 31 08 00 04 00 28 00 24 80 00 02 d2 00  y721....(.$...�.
0x1218040f0: 00 00 00 41 78 f4 00 00 01 ea 01 10 80 00 00 16  ...Ax�...�......

# FileHeader 
FIL_PAGE_SPACE = e5 86 89 21 
FIL_PAGE_OFFSET = 00 00 00 06 
FIL_PAGE_PREV = 00 00 00 05 
FIL_PAGE_NEXT = ff ff ff ff 
FIL_PAGE_LSN = 00 00 00 00 00 1d a6 f8 
FIL_PAGE_TYPE = 45 bf 
FIL_PAGE_FILE_FLUSH_LSN = 00 00 00 00 00 00 00 00 
FIL_PAGE_ARCH_LOG_NO = 00 00 00 06 

# PageHeader 
PAGE_N_DIR_SLOTS = 00 47 
PAGE_HEAP_TOP = 27 fc 
PAGE_N_HEAP = 81 1b 
PAGE_FREE = 00 00 
PAGE_GARBAGE = 00 00 
PAGE_LAST_INSERT = 27 df 
PAGE_DIRECTION = 00 02 
PAGE_N_DIRECTION = 01 18 
PAGE_N_RECS = 01 19 
PAGE_MAX_TRX_ID = 00 00 00 00 00 00 00 00 
PAGE_LEVEL = 00 00 
PAGE_INDEX_ID = 00 00 00 00 00 00 00 16 
PAGE_BTR_SEG_LEAF = 00 00 00 00 00 00 00 00 00 00 
PAGE_BTR_SEG_TOP = 00 00 00 00 00 00 00 00 00 00 

# UserRecords 
jerry719 -> jerry720 -> jerry721 ... -> jerry999

我们来 inspect 一下 0x121807f00

从上面元数据可以知道, pageDirectory 总共有 71 个, 合计 142 字节, 加上末尾的 8 字节 FileTailer 合计 150 字节 

0x121807fff - 0x121807f6a + 1 = 0x96 = 150 字节 

可以看到这里的 pageDirectory 也是逆序排列的 infimum -> 0xeb -> 0x017b -> ... -> supremum [根据 id 排序]

(lldb) x 0x121807f00 -c 0x100
0x121807f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0x121807f10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0x121807f20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0x121807f30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0x121807f40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0x121807f50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0x121807f60: 00 00 00 00 00 00 00 00 00 00 00 70 27 2b 26 9b  ...........p'+&.
0x121807f70: 26 0b 25 7b 24 eb 24 5b 23 cb 23 3b 22 ab 22 1b  &.%{$�$[#�#;"�".
0x121807f80: 21 8b 20 fb 20 6b 1f db 1f 4b 1e bb 1e 2b 1d 9b  !. � k.�.K.�.+..
0x121807f90: 1d 0b 1c 7b 1b eb 1b 5b 1a cb 1a 3b 19 ab 19 1b  ...{.�.[.�.;.�..
0x121807fa0: 18 8b 17 fb 17 6b 16 db 16 4b 15 bb 15 2b 14 9b  ...�.k.�.K.�.+..
0x121807fb0: 14 0b 13 7b 12 eb 12 5b 11 cb 11 3b 10 ab 10 1b  ...{.�.[.�.;.�..
0x121807fc0: 0f 8b 0e fb 0e 6b 0d db 0d 4b 0c bb 0c 2b 0b 9b  ...�.k.�.K.�.+..
0x121807fd0: 0b 0b 0a 7b 09 eb 09 5b 08 cb 08 3b 07 ab 07 1b  ...{.�.[.�.;.�..
0x121807fe0: 06 8b 05 fb 05 6b 04 db 04 4b 03 bb 03 2b 02 9b  ...�.k.�.K.�.+..
0x121807ff0: 02 0b 01 7b 00 eb 00 63 14 23 33 ed 00 1d a6 f8  ...{.�.c.#3�..��

根据数据page 定位 id = 991

这里的查询会有两层搜索, 通过 pageDirectory 的搜索, 通过最近的 pageDirectory 线性查询目标索引, 这里 991 对应于 up_rec  

看这里的 up_rec, 指向的记录就是上面的 (991, 22, jerry991) 

06 mysql all查询 和 主键查询 和 非索引列查询_迭代_05

我们来 inspect 一下 up_rec, 正是我们这里期望查询的 id = '991' 对应的记录 

(lldb) x 0x1218066bf
0x1218066bf: 80 00 03 df 00 00 00 00 42 85 81 00 00 01 32 01  ...�....B.....2.
0x1218066cf: 10 80 00 00 16 6a 65 72 72 79 39 39 31 08 00 00  .....jerry991...

外部的业务函数 row_search_for_mysql

上面这里查询到了 id = '991' 对应的记录, 封装在了 pcur->btr_cur->page_cur->rec 里面 

上面的流程是在 row_search_for_mysql 的 btr_pcur_open_with_no_init 这里, 这里的作用就是初始化 pcur, 为后面的业务迭代做准备 

接下来走的便是根据 pcur->btr_cur->page_cur->rec 进行迭代, 比较是否符合查询条件, 输出记录信息到客户端等等流程 

所以这里会做的事情是, 当前 rec 是在 id = '991', 然后 比较是否符合查询条件, 这里是符合, 然后将记录输出到客户端 

然后会进入 prebuilt->n_fetch_cached < MYSQL_FETCH_CACHE_SIZE 的条件, 继续下一个元素的迭代 

迭代到 rec 是在 id = '992', 然后 比较是否符合查询条件, 这里是不匹配, 走的时候 normal_return, 退出查询 

06 mysql all查询 和 主键查询 和 非索引列查询_mysql_06

我们来 inspect 一下 0x1218066bf

(lldb) x 0x1218066bf
0x1218066bf: 80 00 03 df 00 00 00 00 42 85 81 00 00 01 32 01  ...�....B.....2.
0x1218066cf: 10 80 00 00 16 6a 65 72 72 79 39 39 31 08 00 00  .....jerry991...

迭代元素 id = '992'

06 mysql all查询 和 主键查询 和 非索引列查询_mysql_07

我们来 inspect 一下 0x1218066e3 

(lldb)  x 0x1218066e3
0x1218066e3: 80 00 03 e0 00 00 00 00 42 86 82 00 00 01 35 01  ...�....B.....5.
0x1218066f3: 10 80 00 00 16 6a 65 72 72 79 39 39 32 08 00 00  .....jerry992...

explain 的结果

我们来看一下 explain 的结果, 会使用 primary key 来索引, 大约会扫描 1 行记录

06 mysql all查询 和 主键查询 和 非索引列查询_query_08

select * from `user`

初始化 pcur->btr_cur->page_cur->rec

这个查询相对来说 走的流程比较简单, 查询的最小的 数据page的 infimum, 然后不断往后迭代, 直到所有元素迭代完成 

这里是初始化 pcur->btr_cur->page_cur->rec 为最小的 数据page的 infimum 

这里的迭代是从 index page 开始的, 从 level 最高的 index page 开始向下迭代, 如果是 fromLeft 表示最终取最小的数据页的 infimum, 如果是 !fromLeft 表示最终取最大的数据页的 supremum 

06 mysql all查询 和 主键查询 和 非索引列查询_index_09

根据 pcur->btr_cur->page_cur->rec 进行迭代 

然后接下来就是不断地向后迭代, 输出查询的元素信息到客户端 

06 mysql all查询 和 主键查询 和 非索引列查询_select_10

当前 page 到达了末尾, 切换到下一个 page 

btr_pcur_is_after_last_in_tree 判断当前 page 是否是最后一个 page, 判断的方式是 pageNext 是否为 0xffffffff 

06 mysql all查询 和 主键查询 和 非索引列查询_index_11

explain 的结果

我们来看一下 explain 的结果, 全表扫描, 大约会扫描 906 行记录

06 mysql all查询 和 主键查询 和 非索引列查询_index_12

select * from `user` where name = 'jerry991'

初始化 pcur->btr_cur->page_cur->rec 

这里的初始化过程 和上面的 "select * from `user`" 的初始化是一样的, 初始化 pcur->btr_cur->page_cur->rec 为 page4->infimum 

然后不断向后迭代数据, 直到全表的数据迭代完成 

06 mysql all查询 和 主键查询 和 非索引列查询_select_13

我们来 inspect 一下 这一页的数据 

(lldb) x 0x11cf1c000 -c 0x100
0x11cf1c000: 94 8c b5 2e 00 00 00 04 ff ff ff ff 00 00 00 05  ..�.....����....
0x11cf1c010: 00 00 00 00 00 1d 34 e5 45 bf 00 00 00 00 00 00  ......4�E�......
0x11cf1c020: 00 00 00 00 00 06 00 36 3b 0d 81 a3 1d b0 1d 64  .......6;..�.�.d
0x11cf1c030: 00 00 00 05 00 00 00 d0 00 00 00 00 00 00 00 00  .......�........
0x11cf1c040: 00 00 00 00 00 00 00 00 00 16 00 00 00 00 00 00  ................
0x11cf1c050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00  ................
0x11cf1c060: 02 00 1c 69 6e 66 69 6d 75 6d 00 01 00 0b 00 00  ...infimum......
0x11cf1c070: 73 75 70 72 65 6d 75 6d 05 00 00 00 10 00 21 80  supremum......!.
0x11cf1c080: 00 00 01 00 00 00 00 2b 07 04 00 00 01 56 04 7b  .......+.....V.{
0x11cf1c090: 80 00 00 1c 6a 65 72 72 79 04 00 00 00 18 00 20  ....jerry...... 
0x11cf1c0a0: 80 00 00 02 00 00 00 00 35 04 83 00 00 01 36 01  ........5.....6.
0x11cf1c0b0: 10 80 00 00 16 6c 75 63 79 06 00 00 00 20 00 22  .....lucy.... ."
0x11cf1c0c0: 80 00 00 05 00 00 00 00 3d 14 8a 00 00 01 91 01  ........=.......
0x11cf1c0d0: 10 80 00 00 16 6a 65 72 72 79 35 06 00 04 00 28  .....jerry5....(
0x11cf1c0e0: 00 22 80 00 00 07 00 00 00 00 3d 0b 84 00 00 01  ."........=.....
0x11cf1c0f0: 37 01 10 80 00 00 16 6a 65 72 72 79 37 06 00 00  7......jerry7...

根据 pcur->btr_cur->page_cur->rec 进行迭代 

然后接下来就是不断地向后迭代,  输出查询的元素信息到客户端 

todo, 这里根据条件过滤, 以及数据传递, 还需要再找找 

06 mysql all查询 和 主键查询 和 非索引列查询_迭代_14

完