目录

MRR原理

举例

磁盘IOPS的计算规则

开启了 MRR

顺序读带来了几个好处:

拆分查询条件,进行批量查询

使用限制

相关参数

源码浅谈

a、MRR 中有序主建的收集过程

b、MRR 中主建缓冲区的使用过程

参考


MRR原理

MRR 的全称是 Multi-Range Read Optimization,是优化器将随机 IO 转化为顺序 IO 以降低查询过程中 IO 开销的一种手段。

在不使用 MRR 时,优化器需要根据二级索引返回的记录来进行“回表”,这个过程一般会有较多的随机 IO, 使用 MRR 时,SQL 语句的执行过程是这样的:

  • 优化器将二级索引查询到的记录放到一块缓冲区中;
  • 如果二级索引扫描到索引文件的末尾或者缓冲区已满,则使用快速排序对缓冲区中的内容按照主键进行排序;
  • 线程调用 MRR 接口取 rowId,然后根据rowId 取行数据;
  • 当根据缓冲区中的 rowId 取完数据,则继续调用过程 2) 3),直至扫描结束;

通过上述过程,优化器将二级索引随机的 IO 进行排序,转化为主键的有序排列,从而实现了随机 IO 到顺序 IO 的转化,提升性能。

举例

执行一个范围查询:

mysql > set optimizer_switch='mrr=off';Query OK, 0 rows affected (0.06 sec)mysql > explain select * from stu where age between 10 and 20;+----+-------------+-------+-------+------+---------+------+------+-----------------------+| id | select_type | table | type | key | key_len | ref | rows | Extra |+----+-------------+-------+-------+----------------+------+------+-----------------------+| 1 | SIMPLE | stu | range | age | 5 | NULL | 960 | Using index condition |+----+-------------+-------+-------+----------------+------+------+-----------------------+

当这个 sql 被执行时,MySQL 会按照下图的方式,去磁盘读取数据(假设数据不在数据缓冲池里):

这张图是按照 Myisam 的索引结构画的,不过对于 Innodb 也同样适用。

对于 Myisam,左边就是字段 age 的二级索引,右边是存储完整行数据的地方。

先到左边的二级索引找,找到第一条符合条件的记录(实际上每个节点是一个页,一个页可以有很多条记录,这里我们假设每个页只有一条),接着到右边去读取这条数据的完整记录。

读取完后,回到左边,继续找下一条符合条件的记录,找到后,再到右边读取,这时发现这条数据跟上一条数据,在物理存储位置上,离的贼远!

咋办,没办法,只能让磁盘和磁头一起做机械运动,去给你读取这条数据。

第三条、第四条,都是一样,每次读取数据,磁盘和磁头都得跑好远一段路。

图中红色线就是整个的查询过程,蓝色线则是磁盘的运动路线。

磁盘的简化结构可以看成这样:

可以想象一下,为了执行你这条 sql 语句,磁盘要不停的旋转,磁头要不停的移动,这些机械运动,都是很费时的。

10,000 RPM 的机械硬盘,每秒大概可以执行 167 次磁盘读取,所以在极端情况下,MySQL 每秒只能给你返回 167 条数据,这还不算上 CPU 排队时间。

磁盘IOPS的计算规则

主要影响的三个参数,分别是平均寻址时间、盘片旋转速度以及最大传送速度:

第一个寻址时间,考虑到被读写的数据可能在磁盘的任意一个磁道,既有可能在磁盘的最内圈(寻址时间最短),也可能在磁盘的最外圈(寻址时间最长),所以在计算中我们只考虑平均寻址时间,也就是磁盘参数中标明的那个平均寻址时间,这里就采用当前最多的10krmp硬盘的5ms。 寻道时间Tseek是指将读写磁头移动至正确的磁道上所需要的时间。寻道时间越短,I/O操作越快,目前磁盘的平均寻道时间一般在3-15ms。

第二个旋转延时,和寻址一样,当磁头定位到磁道之后有可能正好在要读写扇区之上,这时候是不需要额外额延时就可以立刻读写到数据,但是最坏的情况确实要磁盘旋转整整一圈之后磁头才能读取到数据,所以这里我们也考虑的是平均旋转延时,对于10krpm的磁盘就是(60s/10k)*(1/2) = 2ms。

第三个传送时间,磁盘参数提供我们的最大的传输速度,当然要达到这种速度是很有难度的,但是这个速度却是磁盘纯读写磁盘的速度,因此只要给定了单次 IO的大小,我们就知道磁盘需要花费多少时间在数据传送上,这个时间就是IO Chunk Size / Max Transfer Rate。(数据传输率,单位是Mb/s,兆每秒)。数据传输时间Ttransfer是指完成传输所请求的数据所需要的时间,它取决于数据传输率,其值等于数据大小除以数据传输率。目前IDE/ATA能达到133MB/s,SATA II可达到300MB/s的接口数据传输率,数据传输时间通常远小于前两部分时间。

因此,理论上可以计算出磁盘的最大IOPS,即IOPS = 1000 ms/ (Tseek + Troatation),忽略数据传输时间。假设磁盘平均物理寻道时间为3ms, 磁盘转速为7200,10K,15K rpm,则磁盘IOPS理论最大值分别为,

IOPS = 1000 / (3 + 60000/7200/2) = 140

IOPS = 1000 / (3 + 60000/10000/2) = 167

IOPS = 1000 / (3 + 60000/15000/2) = 200

开启了 MRR

很明显,要把随机访问转化成顺序访问。设置开启MRR, 重新执行 sql 语句,发现 Extra 里多了一个「Using MRR」。

mysql > set optimizer_switch='mrr=on';Query OK, 0 rows affected (0.06 sec)mysql > explain select * from stu where age between 10 and 20;+----+-------------+-------+-------+------+---------+------+------+----------------+| id | select_type | table | type | key | key_len | ref | rows | Extra |+----+-------------+-------+-------+------+---------+------+------+----------------+| 1 | SIMPLE | tbl | range | age | 5 | NULL | 960 | ...; Using MRR |+----+-------------+-------+-------+------+---------+------+------+----------------+

我们开启了 MRR,重新执行 sql 语句,发现 Extra 里多了一个「Using MRR」。

这下 MySQL 的查询过程会变成这样:

在去磁盘获取完整数据之前,会先按照 rowid 排好序,再去顺序的读取磁盘。

顺序读带来了几个好处:

1、磁盘和磁头不再需要来回做机械运动;

2、可以充分利用磁盘预读;
比如在客户端请求一页的数据时,可以把后面几页的数据也一起返回,放到数据缓冲池中。这样如果下次刚好需要下一页的数据,就不再需要到磁盘读取。
这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。

3、在一次查询中,每一页的数据只会从磁盘读取一次;
MySQL 从磁盘读取页的数据后,会把数据放到数据缓冲池,下次如果还用到这个页,就不需要去磁盘读取,直接从内存读。
但是如果不排序,可能你在读取了第 1 页的数据后,会去读取第2、3、4页数据,接着你又要去读取第 1 页的数据。这时,你发现第 1 页的数据,已经从缓存中被剔除了,于是又得再去磁盘读取第 1 页的数据。
而转化为顺序读后,你会连续的使用第 1 页的数据,这时候按照 MySQL 的缓存剔除机制,这一页的缓存是不会失效的,直到你利用完这一页的数据。
由于是顺序读,在这次查询的余下过程中,你确信不会再用到这一页的数据,可以和这一页数据说告辞了。

所以,顺序读就是通过这三个方面,最大的优化了索引的读取。

拆分查询条件,进行批量查询

此外,MRR还可以将某些范围查询,拆分为键值对,以此来进行批量的数据查询。

这样做的好处是可以在拆分过程中,直接过滤一些不符合查询条件的数据。

> SELECT * FROM t WHERE key_part1 >=1000 AND key_part1 < 2000 AND key_part2 = 1000;

表t有(key_part1,key_part2)的联合索引,因此索引根据key_part1,key_part2的位置关系进行排序。若没有MRR,此时查询类型为Range,SQL优化器会先将key_part1大于1000且小于2000的数据都取出来,即便key_part2不等于1000。取出后再根据key_part2的条件进行过滤。这会导致无用的数据被取出。

如果启用MRR优化器会使性能有巨大的提升,优化器会先将查询条件拆分为(1000,1000),(1001,1000),(1002,1000)....(1999,1000) 最后再根据这些拆分出的条件进行数据的查询。

使用限制

MRR 适用于range、ref、eq_ref的查询

相关参数

是否启用MRR优化,可以通过参数optimizer_switch中的flag来控制。

1、MRR 的开关:mrr =(on | off)

例如,打开MRR的开关:

mysql > set optimizer_switch='mrr=on';

2、用来告诉优化器,要不要基于使用 MRR 的成本:mrr_cost_based = (on | off)
例如,通通使用MRR:

SET GLOBAL optimizer_switch='mrr=on,mrr_cost_based=off';

考虑使用 MRR 是否值得(cost-based choice),来决定具体的 sql 语句里要不要使用 MRR。
很明显,对于只返回一行数据的查询,是没有必要 MRR 的,而如果你把 mrr_cost_based 设为 off,那优化器就会通通使用 MRR,这在有些情况下是很 stupid 的,所以建议这个配置还是设为 on,毕竟优化器在绝大多数情况下都是正确的。

3、设置用于给 rowid 排序的内存的大小:read_rnd_buffer_size,该值默认是256KB

查看配置

show VARIABLES like 'read_rnd_buffer_size';

显然,MRR 在本质上是一种用空间换时间的算法。MySQL 不可能给你无限的内存来进行排序,如果 read_rnd_buffer 满了,就会先把满了的 rowid 排好序去磁盘读取,接着清空,然后再往里面继续放 rowid,直到 read_rnd_buffer 又达到 read_rnd_buffe 配置的上限,如此循环。

没有MRR的情况下,二级索引里面得到多少行,那么就要去访问多少次主键索引(也不能完全这样说,因为MySQL实现了BNL,是把被驱动表的记录加载到内存的时候,一次性和多条驱动表中的记录做匹配,这样就可以大大减少重复从磁盘上加载被驱动表的代价),而有了MRR的时候,次数就大约减少为之前次数 t / buffer_size。

源码浅谈

mysql-5.7.25/sql/handler.ccclass DsMrr_impl
{.../* 利用 h2 返回的主建来查询表的全部行数据的对象 */handler *h; TABLE *table;
private:/*  MRR 访问二级索引 */handler *h2;/* MRR 执行过程中存储有序主键的缓存区,大小由 MySQL 的变量 read_rnd_buffer_size 设置. 存储 rowids, or (rowid, range_id) 对 */uchar *rowids_buf;uchar *rowids_buf_cur;   /* 当正在读/写时的当前位置*/uchar *rowids_buf_last;  /* 当正在读时:被使用缓存空间的结尾 */uchar *rowids_buf_end;   /* 缓存空间的结尾 */bool dsmrr_eof; /* TRUE 表示当读取索引文件已经达到 EOF了,即扫描结束*//*初始化和开始mrr扫描 */int dsmrr_init(handler *h, RANGE_SEQ_IF *seq_funcs, void *seq_init_param,uint n_ranges, uint mode, HANDLER_BUFFER *buf);/*初始化MRR扫描*/int handler::multi_range_read_init(RANGE_SEQ_IF *seq_funcs, void *seq_init_param, uint n_ranges, uint mode, HANDLER_BUFFER *buf);…./*用rowid填充缓冲区并按rowid排序*/int dsmrr_fill_buffer();/*在MRR扫描中获取下一个记录*/int handler::multi_range_read_next(char **range_info);/*获得DS-MRR扫描的耗费返回值FALSE  OK返回值RUE   Error, DS-MRR 不能被使用(缓存太小了,甚至连一行 rowId都存不了)*/bool get_disk_sweep_mrr_cost(uint keynr, ha_rows rows, uint flags, uint *buffer_size, Cost_estimate *cost);….
}

a、MRR 中有序主建的收集过程

1)执行初始化方法dsmrr_init(...) ,优化器对查询语句的条件进行分析并选择合适的二级索引,并对二级索引的条件进行筛选拼装成要遍历的范围序列,将相关序列方法和序列参数传入初始化函数h->handler::multi_range_read_init(seq_funcs, seq_init_param, n_ranges, mode, buf) ,继而会调用 dsmrr_fill_buffer 函数;

2)在dsmrr_fill_buffer中会使用二级索引的句柄查找符合 ranges 的数据并添加至 rowids_buf 中,在扫描结束或缓冲区满的时候会对 rowids_buf 进行快速排序。

b、MRR 中主建缓冲区的使用过程

1) 物理执行阶段,调用 h->handler::multi_range_read_next(range_info);,在使用 MRR 的情况下会从过程a中收集的有序主建的缓冲区取主建,然后再调用引擎层的 h->ha_rnd_pos(table->record[0], rowid) 直接找到数据。

2) 二缓索引(h2)& 主建索引(h) 的协同是通过rowids_buf_cur来进行的。最初的初始化过程中,h2 会首先将数据填冲到 rowids_buf 中,如果发现缓冲区中的数据已经取完,则会继续调用 dsmrr_fill_buffer 往 rowids_buf 填主键并进行排序,如此反复,直至 h2 扫描至文件末尾,详情可以参考函数 DsMrr_impl::dsmrr_next。

uint mode可以是由下面标志来组合

/* 下面这两个没有被使用,因为没有对应的实现
*/
#define HA_MRR_SINGLE_POINT 1
#define HA_MRR_FIXED_KEY  2 /*  表示 RANGE_SEQ_IF::next(&range)不需要填充'range' 参数
*/
#define HA_MRR_NO_ASSOCIATION 4 /*
按输入key的顺序范围,MRR其处理的结果必须按键顺序返回行
*/
#define HA_MRR_SORTED 8 /* MRR 不需要检索完整的记录 */
#define HA_MRR_INDEX_ONLY 16 /*  内存缓冲区设置为最大限制,调用者不能承担更大的缓冲。
*/
#define HA_MRR_LIMITS 32 /* 默认MRR实现
*/
#define HA_MRR_USE_DEFAULT_IMPL 64 /* 保证扫描范围的边界将没有NULL值。
*/
#define HA_MRR_NO_NULL_ENDPOINTS 128

下面为一些重要方法的具体实现

/**ds-mrr初始化和开始mrr扫描 */
int DsMrr_impl::dsmrr_init(handler *h_arg, RANGE_SEQ_IF *seq_funcs, void *seq_init_param, uint n_ranges, uint mode,HANDLER_BUFFER *buf)
{uint elem_size;int retval= 0;DBUG_ENTER("DsMrr_impl::dsmrr_init");THD *thd= h_arg->table->in_use;    h= h_arg;// DS-MRR 不进行排序,我们可以通过OPTIMIZER_SWITCH_MRR参数来控制if (!hint_key_state(thd, h->table, h->active_index,MRR_HINT_ENUM, OPTIMIZER_SWITCH_MRR) ||mode & (HA_MRR_USE_DEFAULT_IMPL | HA_MRR_SORTED)) {use_default_impl= TRUE;retval= h->handler::multi_range_read_init(seq_funcs, seq_init_param,n_ranges, mode, buf);DBUG_RETURN(retval);}/* 如果已经将索引条件压入主键索引对象,然后改变主意,使用不同的索引进行MRR检索数据。以下标准之一必须是真实的:1. 我们没有在这个处理程序上推入索引条件。2. 我们已经推入了一个索引条件,这是当前使用的索引。3. 我们已经推入了一个索引条件,但这不是针对主键的。4. 我们已经推入了一个索引条件,这个已经被转移到了处理程序对象的克隆(h2)。*/DBUG_ASSERT(!h->pushed_idx_cond ||h->pushed_idx_cond_keyno == h->active_index ||h->pushed_idx_cond_keyno != table->s->primary_key ||(h2 && h->pushed_idx_cond_keyno == h2->active_index));rowids_buf= buf->buffer;is_mrr_assoc= !MY_TEST(mode & HA_MRR_NO_ASSOCIATION);if (is_mrr_assoc)table->in_use->status_var.ha_multi_range_read_init_count++;rowids_buf_end= buf->buffer_end;elem_size= h->ref_length + (int)is_mrr_assoc * sizeof(void*);rowids_buf_last= rowids_buf + ((rowids_buf_end - rowids_buf)/ elem_size)*elem_size;rowids_buf_end= rowids_buf_last;/*DS-MRR扫描使用第二个处理程序对象(h2)来完成索引扫描。通过克隆primary handler object来创建。当DsMrr_impl::reset()执行时,h2会被删除。*/if (!h2){handler *new_h2;if (check_stack_overrun(thd, 5*STACK_MIN_SIZE, (uchar*) &new_h2))DBUG_RETURN(1);if (!(new_h2= h->clone(h->table->s->normalized_path.str, thd->mem_root)))DBUG_RETURN(1);h2= new_h2; table->prepare_for_position();}/*MAX_KEY 表示 Max used keysh2 使用 primary handler的key进行索引扫描  */if (h2->active_index == MAX_KEY){DBUG_ASSERT(h->active_index != MAX_KEY);const uint mrr_keyno= h->active_index;if ((retval= h2->ha_external_lock(thd, h->m_lock_type)))goto error;if ((retval= h2->extra(HA_EXTRA_KEYREAD)))goto error;if ((retval= h2->ha_index_init(mrr_keyno, false)))goto error;// 从 h 到 h2 转移 ICP(索引下推) if (mrr_keyno == h->pushed_idx_cond_keyno){if (h2->idx_cond_push(mrr_keyno, h->pushed_idx_cond)){retval= 1;goto error;}}else{// 取消之前任何潜在的索引条件推送h2->cancel_pushed_idx_cond();}}else{/*h2 已经是一个 open index. 这种情况出现在当第一次重新开启时没有关闭它。 在这种情况下,因为从表中读取数据,primary handler必须被使用 在做新的范围扫描时, h2不能被打开。在这种情况下,primary handler的active_index既不能被设置,也不能为primary key.*/DBUG_ASSERT(h->inited == handler::RND);DBUG_ASSERT(h->active_index == MAX_KEY || h->active_index == table->s->primary_key);}/*现在索引扫描已经转移到 h2了,我们可以关闭 primary handler 的open index scan*/if (h->inited == handler::INDEX){/*调用 h->ha_index_end()时,会再调用 dsmrr_close(),其将关闭h2的索引扫描。我们需要保持h2打开,所以进行临时性移除*/handler *save_h2= h2;h2= NULL;retval= h->ha_index_end();h2= save_h2;if (retval)goto error;}/*验证一致性 between h and h2.*/DBUG_ASSERT(h->inited != handler::INDEX);DBUG_ASSERT(h->active_index == MAX_KEY || h->active_index == table->s->primary_key);DBUG_ASSERT(h2->inited == handler::INDEX);DBUG_ASSERT(h2->active_index != MAX_KEY);DBUG_ASSERT(h->m_lock_type == h2->m_lock_type);if ((retval= h2->handler::multi_range_read_init(seq_funcs, seq_init_param, n_ranges, mode, buf)))goto error;if ((retval= dsmrr_fill_buffer()))goto error;...
}
/*初始化MRR扫描*/
int handler::multi_range_read_init(RANGE_SEQ_IF *seq_funcs, void *seq_init_param,uint n_ranges, uint mode, HANDLER_BUFFER *buf)
{DBUG_ENTER("handler::multi_range_read_init");mrr_iter= seq_funcs->init(seq_init_param, n_ranges, mode);mrr_funcs= *seq_funcs;mrr_is_output_sorted= MY_TEST(mode & HA_MRR_SORTED);mrr_have_range= FALSE;DBUG_RETURN(0);
}
/*用rowid填充缓冲区并按rowid排序*/
int DsMrr_impl::dsmrr_fill_buffer()
{char *range_info;int res= 0;DBUG_ENTER("DsMrr_impl::dsmrr_fill_buffer");DBUG_ASSERT(rowids_buf < rowids_buf_end);/*将key_read设置为TRUE,因为我们只从索引中读取字段,这也确保了任何虚拟列都是从索引中读取,而不是试图从基列进行读取*/DBUG_ASSERT(table->key_read == FALSE);table->key_read= TRUE;rowids_buf_cur= rowids_buf;while ((rowids_buf_cur < rowids_buf_end) && !(res= h2->handler::multi_range_read_next(&range_info))){KEY_MULTI_RANGE *curr_range= &h2->handler::mrr_cur_range;/*检查记录组合是否匹配索引条件*/if (h2->mrr_funcs.skip_index_tuple &&h2->mrr_funcs.skip_index_tuple(h2->mrr_iter, curr_range->ptr))continue;/* 把 rowid 或者 {rowid, range_id} 对加入 buffer */h2->position(table->record[0]);memcpy(rowids_buf_cur, h2->ref, h2->ref_length);rowids_buf_cur += h2->ref_length;if (is_mrr_assoc){memcpy(rowids_buf_cur, &range_info, sizeof(void*));rowids_buf_cur += sizeof(void*);}}// 恢复key_read,因为下一个读操作将读取完整的行table->key_read= FALSE;/* HA_ERR_END_OF_FILE 表示 No rows in range*/if (res && res != HA_ERR_END_OF_FILE)DBUG_RETURN(res); dsmrr_eof= MY_TEST(res == HA_ERR_END_OF_FILE);uint elem_size= h->ref_length + (int)is_mrr_assoc * sizeof(void*);size_t n_rowids= (rowids_buf_cur - rowids_buf) / elem_size;/* 通过rowId对缓存内容进行排序 */my_qsort2(rowids_buf, n_rowids, elem_size, (qsort2_cmp)rowid_cmp,(void*)h);rowids_buf_last= rowids_buf_cur;rowids_buf_cur=  rowids_buf;DBUG_RETURN(0);
}
/*在MRR扫描中获取下一个记录
*/
int handler::multi_range_read_next(char **range_info)
{int result= HA_ERR_END_OF_FILE;int range_res;/**一些非虚拟的ha_*函数,负责读取行,与ha_rnd_pos()类似,必须确保虚拟生成的变量在他们返回之前就算好了。为此,他们应该设置这个成员在开始时设置为true,并在返回前进行检查:如果成员仍然是true,这意味着他们应该计算;如果它是false,这意味着已经被一些更低层次的方法计算处理了,不需要重新做(这就是为什么我们需要这个状态标志:为性能,以避免冗余计算)*/m_update_generated_read_fields= table->has_gcol();/*TRUE 表示我们目前正在遍历mrr_cur_range中的一个范围*/if (!mrr_have_range){mrr_have_range= TRUE;goto start;}do{if (mrr_cur_range.range_flag != (UNIQUE_RANGE | EQ_RANGE)){result= read_range_next();/* 在 success or non-EOF 错误时跳出循环 */if (result != HA_ERR_END_OF_FILE)break;}else{/*在UPDATE或DELETE中,如果游标下的行被另一个事务行锁定,存储引擎在游标下使用最后一个乐观读取已提交的行值,然后引擎从中返回1函数。这可以用来避免不必要的锁等待。如果该方法返回非零值,它将通知存储引擎的下一个读取将是对该行的锁定重新读取。*/if (was_semi_consistent_read())goto scan_it_again;}start:/* 尝试获取下一个 range(s), 直到匹配上一行记录 */while (!(range_res= mrr_funcs.next(mrr_iter, &mrr_cur_range))){
scan_it_again:result= read_range_first(mrr_cur_range.start_key.keypart_map ?&mrr_cur_range.start_key : 0,mrr_cur_range.end_key.keypart_map ?&mrr_cur_range.end_key : 0,MY_TEST(mrr_cur_range.range_flag &EQ_RANGE),                                                         mrr_is_output_sorted);if (result != HA_ERR_END_OF_FILE)break;}}while ((result == HA_ERR_END_OF_FILE) && !range_res);*range_info= mrr_cur_range.ptr;/* 更新虚拟生成变量*/if (!result && m_update_generated_read_fields){result= update_generated_read_fields(table->record[0], table, active_index);m_update_generated_read_fields= false;}DBUG_PRINT("exit",("handler::multi_range_read_next result %d", result));DBUG_RETURN(result);
}
/*获得DS-MRR扫描的耗费FALSE  OKTRUE   Error, DS-MRR 不能被使用(缓存太小了,甚至连一行 rowId都存不了)*/
bool DsMrr_impl::get_disk_sweep_mrr_cost{ha_rows rows_in_last_step; uint n_full_steps; const uint elem_size= h->ref_length + sizeof(void*) * (!MY_TEST(flags & HA_MRR_NO_ASSOCIATION));const ha_rows max_buff_entries= *buffer_size / elem_size;if (!max_buff_entries)return TRUE; /* DS-MRR 不能被使用(缓存太小了,甚至连一行 rowId都存不了) *//* rows可以full buffer的次数*/n_full_steps= (uint)floor(rows2double(rows) / max_buff_entries);/*处理最后一次迭代时(non-full buffer)的行数 */rows_in_last_step= rows % max_buff_entries;DBUG_ASSERT(cost->is_zero());if (n_full_steps){/*计算一次排序和扫描的消耗*/get_sort_and_sweep_cost(table, max_buff_entries, cost);cost->multiply(n_full_steps);}else{/* 调整缓冲区大小,因为只有部分缓冲区将被使用:*/const ha_rows keys_in_buffer=max<ha_rows>(static_cast<ha_rows>(1.2 * rows_in_last_step), 100);/*2. 如果估计所需的缓冲区大小小于建议的,调用者将其设置为估计的缓冲区大小。*/*buffer_size= min<ulong>(*buffer_size,static_cast<ulong>(keys_in_buffer) * elem_size);}Cost_estimate last_step_cost;get_sort_and_sweep_cost(table, rows_in_last_step, &last_step_cost);(*cost)+= last_step_cost;/*内存消耗暂时不计算在 total_cost() function ,未来会加入。*/cost->add_mem(*buffer_size);/* 所有索引访问的total cost */(*cost)+= h->index_scan_cost(keynr, 1, static_cast<double>(rows));/*增加处理记录的cpu消耗(详情看 @handler::multi_range_read_info_const()).*/cost->add_cpu(table->cost_model()->row_evaluate_cost(static_cast<double>(rows)));return FALSE;
}
/*计算排序记录行数的消耗*/
static void get_sort_and_sweep_cost(TABLE *table, ha_rows nrows, Cost_estimate *cost)
{if (nrows){/*读取记录行的消耗*/get_sweep_read_cost(table, nrows, FALSE, cost);const double ROWID_COMPARE_SORT_COST=table->cost_model()->key_compare_cost(1.0) / 10;// cpu_sort= nrows * log2(nrows) * table->cost_model()->rowid_compare_cost();    const double cpu_sort= nrows * log2(nrows) * ROWID_COMPARE_SORT_COST;cost->add_cpu(cpu_sort);}
}

参考

  • MySQL MRR
  • Mariadb MRR
  • MySQL索引背后的数据结构及算法原理

Mysql的新特性--MRR相关推荐

  1. mysql mts_MySQL新特性MTS

    一.MTS:多线程复制 MTS简介 在MySQL 5.6版本之前,Slave服务器上有两个线程I/O线程和SQL Thread线程.I/O线程负责接收二进制日志(Binary Log,更准确的说是二进 ...

  2. mysql derived2_MySQL · 新特性分析 · 5.7中Derived table变形记-阿里云开发者社区

    Derived table实际上是一种特殊的subquery,它位于SQL语句中FROM子句里面,可以看做是一个单独的表.MySQL5.7之前的处理都是对Derived table进行Material ...

  3. MySQL 8新特性--InnoDB相关新特性

    文章目录 6.1. auto-increment 6.2.innodb_deadlock_detect 6.3. INFORMATION_SCHEMA.INNODB_CACHED_INDEXES 6. ...

  4. 【MySQL】MySQL 8 新特性

    1. 默认字符集由latin1变为utf8mb4 在8.0版本之前,默认字符集为latin1,utf8指向的是utf8mb3,8.0版本默认字符集为utf8mb4,utf8默认指向的也是utf8mb4 ...

  5. 华为云数据库 MySQL 内核新特性上线,首家彻底解决用户上云需改造应用的问题

    最新消息,搭载 HWSQL 内核的华为云数据库 MySQL,近期上线了几大关键特性.其中通过深入改造.去除社区版 GTID 约束限制的特性,更是首家彻底解决了用户上云需要对应用进行改造的问题. GTI ...

  6. mysql applier_新特性解读 | MySQL 8.0.18 有权限控制的复制

    背景 MySQL 8.0.18 以前,从服务器都是在不检查权限的情况下执行复制事务的,这样做是为了能够让主服务器获取所有内容.实际上,这意味着从机完全信任主机.但是,可能存在一些设置,其中更改跨越了主 ...

  7. mysql failover_新特性解读 | MySQL 8.0.22 新特性 Async Replication Auto failover

    作者:洪斌 爱可生南区负责人兼技术服务总监,MySQL  ACE,擅长数据库架构规划.故障诊断.性能优化分析,实践经验丰富,帮助各行业客户解决 MySQL 技术问题,为金融.运营商.互联网等行业客户提 ...

  8. mysql query browswer_MySQL数据库新特性之存储过程入门教程

    MySQL数据库新特性之存储过程入门教程 在MySQL 5中,终于引入了存储过程这一新特性,这将大大增强MYSQL的数据库处理能力.在本文中将指导读者快速掌握MySQL 5的存储过程的基本知识,带领用 ...

  9. mysql5.7 json特性_【Mysql】Mysql5.7新特性之-json存储

    一 写在前面 本系列文章基于 5.7.12 版本讲述MySQL的新特性.从安装,文件结构,SQL ,优化 ,运维层面 复制,GITD等几个方面展开介绍 5.7 的新特性和功能.同时也建议大家跟踪官方b ...

最新文章

  1. 【初探HTML本相】道之真谛不过自然,html标签脱俗还真
  2. [Python3网络爬虫开发实战] 1.7.1-Charles的安装
  3. 新冠肺炎疫情预测与防控策略评价
  4. Linux 文件查找命令
  5. PeopleTools 8.54 first install note
  6. ELK之ES-Logstash-Kibana互联
  7. 遗传算法学习笔记01
  8. 【数据结构与算法】删除线性表中的零元素
  9. 鸡兔同笼,有35个头,94只脚,求鸡兔各几只
  10. 简单有效的通过js使用qrcode扫描二维码
  11. php打开excel文件,PHP读取Excel文件的简单示例
  12. Android 插件化之—— 加载插件中的资源
  13. 十进制转化成二、八、十六进制的一个小程序
  14. latte - 拿铁咖啡
  15. 2020-12-16 垂死挣扎
  16. python_习题四
  17. kaldi训练thchs30做在线识别
  18. Win7系统服务优化完全攻略
  19. 皇家贝贝《秦俑情》开机 杜淳安以轩“接棒”张艺谋巩俐
  20. 微信小程序-自定义导航组件

热门文章

  1. 国科大-图像处理复习(王伟强)
  2. 利用web端接口实现QQ好友列表获取、QQ群成员获取列表的实例分析
  3. js获取判断苹果手机机型
  4. PHP输出结构类型变量输出,1.PHP基本语法__输出语句、变量、数据类型
  5. Neuratron AudioScore Ultimate 2018.7 v8.9.1 WiN 音频打谱软件
  6. 天气通android2.1,老黄历天气通app
  7. 利用DBMS_FILE_TRANSFER迁移数据库从AIX至Linux
  8. 强推!你值得拥有的十大顶级大数据可视化工具
  9. Unity for kinect的开发教程
  10. 2021年4月1日 深入理解网络层和传输层相关协议!!!