6 mysql源码中的查询逻辑

mysql的架构采用引擎分离的模式,innodb引擎负责最终的数据查询。

mysql解析sql后,调用innodb进行搜索数据,这个过程并不是mysql一次性调用,然后等待innodb返回所有的结果。

innodb提供了一个查询方法,每次只查询一行记录,然后返回记录,直到查询不出结果。

mysql innodb的核心查询逻辑

方法位置:

storage/innobase/row/row0sel.cc

row_search_mvcc

单条查询,每次只查询一条,即使是全表扫描,也是用这个方法一条一条的读出来

参数:

buf 最终结果将存储到这个buf

mode 查询模式,B+树搜索/叶子节点正向遍历/叶子节点逆向遍历 PAGE_CUR_GE PAGE_CUR_L PAGE_CUR_G

prebuilt 表信息和查询条件,其中的search_tuple就是要条件索引条件字段和值

prebuilt->search_tuple 就是要条件索引条件字段和值

prebuilt->pcur 指向上一条查询出的记录,

match_mode 精确匹配, prefix匹配(mysql的最左匹配原则)

direction 升序或逆序查询

如果是精确匹配,并且如果是联合索引,索引字段不为空。 那么结果只可能是0或1个

cpp 复制代码
if (match_mode == ROW_SEL_EXACT
    && dict_index_is_unique(index)
    && dtuple_get_n_fields(search_tuple)
    == dict_index_get_n_unique(index)
    && (dict_index_is_clust(index)
	|| !dtuple_contains_null(search_tuple))) {
搜索结果是唯一的
unique_search = TRUE;
开启事务
cpp 复制代码
trx_start_if_not_started(trx, false);
// 如果隔离级别是读未提交或读已提交,不用加gap锁
if (trx->isolation_level <= TRX_ISO_READ_COMMITTED
    && prebuilt->select_lock_type != LOCK_NONE
    && trx->mysql_thd != NULL
    && thd_is_select(trx->mysql_thd)) {

	set_also_gap_locks = FALSE;
}
升序降序
cpp 复制代码
direction==0 默认或1(ROW_SEL_NEXT就是ASC)
moves_up = TRUE 设为升级遍历
if (direction == 0) {

	if (mode == PAGE_CUR_GE
	    || mode == PAGE_CUR_G
	    || mode >= PAGE_CUR_CONTAIN) {

		moves_up = TRUE;
	}

} else if (direction == ROW_SEL_NEXT) {

	moves_up = TRUE;
}

取第一个索引,第一个索引必定是聚集索引

clust_index = dict_table_get_first_index(index->table);

事务read_view

若当前查询不需要加锁LOCK_NONE,

那就只需要保证一致性读,也就是最普通的select查询,

需要生成一个read_view,实现一致性读

cpp 复制代码
if (prebuilt->select_lock_type == LOCK_NONE) {
	
	if (!srv_read_only_mode) {
		trx_assign_read_view(trx);
	}

	prebuilt->sql_stat_start = FALSE;
}
S锁

需要加锁的情况

这里先加了表锁, 没有直接加行锁

若是S锁,则加IS锁,否则加IX锁

复制代码
else {
            // 加锁读,先加意向表锁
            // 加表锁,要么是LOCK_IS,要么是LOCK_IX

	err = lock_table(0, index->table,
			 prebuilt->select_lock_type == LOCK_S
			 ? LOCK_IS : LOCK_IX, thr);

	if (err != DB_SUCCESS) {

		table_lock_waited = TRUE;
		goto lock_table_wait;
	}
	prebuilt->sql_stat_start = FALSE;
}

判断查询条件,是否在索引范围内

这决定是否走索引查询

索引查询

if (dtuple_get_n_fields(search_tuple) > 0) { ... }

利用索引遍历B+树

search_tuple就是查询目标值,根据参数查询到第0层(叶子节点)

cpp 复制代码
btr_pcur_open_with_no_init(index, search_tuple, mode,
				   BTR_SEARCH_LEAF,
				   pcur, 0, &mtr);
	主要逻辑就是遍历树 
	btr_cur_search_to_nth_level 找到第n层
		先找到索引树的根页,加载到内存 
		page_cur_search_with_match(index,tuple,mode) 在当前页进行查找(从根页开始,深度优先遍历)
		node_ptr = page_cur_get_rec(page_cursor) 找到下一个node
		loop 循环遍历, 直到找到目标层或第0层,
		page_cur_search_with_match_bytes()  找到目标行

根据搜索到的指针pcur,读取找到的行记录

rec = btr_pcur_get_rec(pcur)

在索引遍历时,如果是降序遍历,并且还需要加锁

就会额外处理,找到当前行的下一行,增加GAP锁

若当前id=100,下一个110,则会在100和110的间隙加间隙锁。

cpp 复制代码
if (!moves_up
    && !page_rec_is_supremum(rec)
    && set_also_gap_locks
    && !(srv_locks_unsafe_for_binlog
	 || trx->isolation_level <= TRX_ISO_READ_COMMITTED)
    && prebuilt->select_lock_type != LOCK_NONE
    && !dict_index_is_spatial(index)) {

	const rec_t*	next_rec = page_rec_get_next_const(rec);

	offsets = rec_get_offsets(next_rec, index, offsets,
				  ULINT_UNDEFINED, &heap);
	err = sel_set_rec_lock(pcur,
			       next_rec, index, offsets,
			       prebuilt->select_lock_type,
			       LOCK_GAP, thr, &mtr);

	switch (err) {
	case DB_SUCCESS_LOCKED_REC:
		err = DB_SUCCESS;
	case DB_SUCCESS:
		break;
	default:
		goto lock_wait_or_error;
	}
}

非索引查询

如果不走索引,那就进行全表扫描, 利用叶子节点的双向链表遍历。

PAGE_CUR_G 从B+树叶子节点最左侧开始扫描(最小节点升序遍历)

PAGE_CUR_L 从B+树叶子节点最右侧开始扫描(最大节点降序遍历)

cpp 复制代码
else if (mode == PAGE_CUR_G || mode == PAGE_CUR_L) {

    btr_pcur_open_at_index_side(
		mode == PAGE_CUR_G, index, BTR_SEARCH_LEAF,
		pcur, false, 0, &mtr);
}

row_search_mvcc方法内容非常多,主要的查询逻辑就是这些。

还有锁的处理,事务的处理,mvcc,将在后面分析。

各种查询条件对应的查询逻辑

范围查询

小于查询

select * from t where id<10;

参数mode为 PAGE_CUR_G

search_tuple为空,取出根页的最小记录,查出最小页,最小记录。

然后根据叶子节点的链表,依次取出所有符合条件的记录。

cpp 复制代码
if(mode==PAGE_CUR_G || mode==PAGE_CUR_L)
	btr_pcur_open_at_index_side() 
btr_pcur_open_at_index_side方法将从叶子节点的最左或最右取值
mode决定了它从哪边取值
PAGE_CUR_G从最左侧(最小)取值,PAGE_CUR_L从最右侧(最大)取值。

终结条件

compare_key(end_range)

找到大于end_range时,符合判断跳出循环。

找到符合条件的记录时,会判断当前版本是不是readView可见

如果不可见,将从版本链的上一级取出。

lock_clust_rec_cons_read_sees(rec,index,offsets,trx_get_read_view(trx))

判断找到的记录是不是被删了

rec_get_deleted_flag(rec, comp)

转格式,存储引擎是innodb,取出来的数据是innodb格式,需要转成mysql

row_sel_store_mysql_rec()

大于查询

select * from t where id>10;

和小于不同,search_tuple有值,也就是需要先查询出"id=10"的记录。

然后从"id=10"的记录往后遍历,剩余部分同小于查询

全表扫描

select * from t;

同小于查询 mode类型为 PAGE_CUR_G ,只是没有终结条件

升序查询

聚集索引默认就是按升序存储的,全表扫描就是升序查询

MOVES_UP = true 升序

降序查询

mode类型为 PAGE_CUR_L, 从叶子节点链表的最右侧取值

然后从左往右遍历

MOVES_UP = false 降序

叶子节点页存在双向链表指针,从而可以快速取出数据。

但记录与记录之间只有单向链表关系。

如果是升序取数据,可以利用记录的链表关系。

如果是降序,只能通过页之间的链表关系,页内的记录就无法通过链表依次取出,会比升序要复杂一些。

btr_pcur_move_to_pre()

btr_pcur_move_to_prev_on_page()

btr_pcur_move_to_pre(btr_pcur_get_page_cur(cursor))

页内降序查询的办法比较笨,从页内最小记录开始遍历,匹配到目标值,回退到上一个,就是反向遍历。

如存在1-10的一组数据, 当前值10,从1开始遍历,找到10,退回到9,返回

再从1开始遍历,找到9,退回到8,返回

范围+降序查询

原本的小于查询从最小页开始遍历,有降序条件后,将从范围值开始降序遍历

原本的大于查询从范围值开始遍历,有降序条件后,将从最大页开始降序遍历,直到终结条件。

in查询

原理页就是逐个按主键取。

但它返回的数据是升序的,这说明它在查询前就把in里面的条件排序过了再逐个查询。

辅助索引

通用也是走row_search_mvcc 不同的是传入的index索引是辅助索引,search_tuple为辅助索引值 匹配到辅助索引的记录后,再进行回表查询。 回表查询,因为辅助索引的叶子节点没有记录完整的数据,只存储了主键id和辅助索引值,如果select的值超出了辅助索引存的值,就需要从聚集索引里面取值,也就是再进行一次或多次主键索引查询,查出完整的数据。 如果辅助索引是唯一索引,只会发生一次回表。 如果辅助索引不是唯一索引,就有可能多次回表。 ·

cpp 复制代码
row_sel_get_clust_rec_for_mysql(prebuilt, index, rec,
					      thr, &clust_rec,
					      &offsets, &heap,
					      need_vrow ? &vrow : NULL,
					      &mtr);
这个方法最终调用前面用到的遍历B+树的方法从聚集索引查询出数据
拿到聚集索引
clust_index = dict_table_get_first_index(sec_index->table);
遍历聚集索引B+树
btr_pcur_open_with_no_init

mysql通过bitmap来记录当前表需要获取的字段

相关推荐
哈哈不让取名字20 分钟前
用Pygame开发你的第一个小游戏
jvm·数据库·python
程序员敲代码吗20 分钟前
Python异步编程入门:Asyncio库的使用
jvm·数据库·python
liux352825 分钟前
MySQL主从复制技术全面解析:从基础原理到高级架构实践(八)
mysql
a程序小傲29 分钟前
听说前端又死了?
开发语言·前端·mysql·算法·postgresql·深度优先
志凌海纳SmartX32 分钟前
榫卯企业云平台:让企业自建云更简单
数据库
老邓计算机毕设36 分钟前
SSM学生信息管理系统ow05a(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·学生信息管理系统·ssm 框架·高校教育管理
小Mie不吃饭1 小时前
MySQL慢查询日志全解析:从配置到优化实践
mysql
Access开发易登软件2 小时前
数据处理中的两大基石:何时选择Excel,何时考虑Access
数据库·信息可视化·excel·vba·access
Alex老夫子2 小时前
android room数据库增加字段注意事项
android·数据库