目录
1. 概述:index merge的数据结构
2. 案例1:简单的index merge
3. 案例2:单个查询多个index merge
4. 成本计算
4.1 什么是Rowid-Ordered Retrieval
4.2 成本概述
4.3 SEL_TREE子树的成本
4.4 non-ROR场景的成本计算
4.4.1 去重复成本计算概述
4.4.2 二级ROWID过滤成本
4.4.3 排序比较成本
4.4.4 外排序时候的磁盘IO成本
4.4.5 合并成本
4.4.6 最后的读取
4.5.3 根据ROWID取出记录的成本
4.5 ROR场景的成本计算
4.5.1 索引读取成本
4.5.2 合并排序
5 最后
首页 数据库 mysql教程 index merge的数据结构和成本评估

index merge的数据结构和成本评估

Jun 07, 2016 pm 04:35 PM
index merge 成本 数据结构 案例 评估

前面以案例的形式介绍了什么是index merge,以及它的使用场景。本文将介绍index merge实现的主要数据结构以及MySQL如何评估index merge的成本。在开始本文之前,需要先理解Range访问相关的数据结构介绍:SEL_ARG结构,SEL_TREE结构。 1. 概述:index merge的

前面以案例的形式介绍了什么是index merge,以及它的使用场景。本文将介绍index merge实现的主要数据结构以及MySQL如何评估index merge的成本。在开始本文之前,需要先理解Range访问相关的数据结构介绍:SEL_ARG结构,SEL_TREE结构。

1. 概述:index merge的数据结构

index merge的主要数据结构仍然是存放在SEL_TREE中:

class SEL_TREE :public Sql_alloc
{
...
  List<sel_imerge> merges;
...
};</sel_imerge>
登录后复制

在merges这个list中存放了所有可能的index merge。本文将从几个案例,来看看SEL_TREE/SEL_IMERGE如何代表一个index merge访问方式。本文将不再重复介绍SEL_ARG/SEL_TREE的Range相关结构。

SEL_IMERGE的主要成员是一个SEL_TREE的链表,每一个SEL_TREE代表了一个独立的索引条件,这个链表中多个条件共同构成这个index merge。我们通过两个案例看看一个SEL_TREE如何表示一个index merge。(这里需要注意,SEL_TREE既可以代表一个RANGE条件,也可以代表一个index merge;代表Range时,其merges成员为空)。

2. 案例1:简单的index merge

SELECT * FROM tmp_sel_tree where 
  ( key1_part1 = 1 or (key1_part2 = 2 and key2_part1 = 3) ) or
  ( key3_part1 = 5 )
登录后复制

这是一个多个索引的index merge,且没有任何的range可以使用。or条件的三个分支,分表可以使用一个独立的索引,其构成的SEL_TREE结构如下:

SEL_TREE
  |
  |-->List<sel_imerge> merges;
     |
     |              / SEL_TREE-> SEL_ARG(key1_part1 = 1)
     \ SEL_IMERGE1  | SEL_TREE-> SEL_ARG(key2_part1 = 3)
                    \ SEL_TREE-> SEL_ARG(key3_part1 = 5)</sel_imerge>
登录后复制

3. 案例2:单个查询多个index merge

SELECT * FROM tmp_sel_tree where 
  ( key1_part1 = 1 or (key1_part2 = 2 and key2_part1 = 3) ) and
  ( key3_part1 = 5 or  key2_part1 = 5)
登录后复制

这个案例中,And条件两边都可以各自使用index merge,MySQL可以选择其中任何一个执行。对应的SEL_TREE中,将会有多个SEL_IMERGE对象,每个SEL_IMERGE对象里面存储了多个独立的可以使用索引的条件(有独立的SEL_TREE表示):

SEL_TREE
  |
  \-->List<sel_imerge> merges;
     |
     |              / SEL_TREE-> SEL_ARG(key1_part1 = 1)
     | SEL_IMERGE1  | SEL_TREE-> SEL_ARG(key2_part1 = 3)
     |              \ SEL_TREE-> SEL_ARG(key3_part1 = 5)
     |
     |              / SEL_TREE-> SEL_ARG(key2_part1 = 5)
     \ SEL_IMERGE2  | 
                    \ SEL_TREE-> SEL_ARG(key3_part1 = 5)</sel_imerge>
登录后复制

MySQL会在选择执行计划时,逐一评估每个SEL_IMERGE的成本,然后选择最优的执行计划。

4. 成本计算

MySQL在计算index merge的成本时,分开考虑了ROR和non-ROR的场景。所以这里先单独介绍一下什么是ROR,后面再介绍MySQL如何区别对待ROR的成本计算。

4.1 什么是Rowid-Ordered Retrieval

Rowid-Ordered Retrieval简称ROR。看下面的说明。有基于索引的查询:

"key_1=c_1 AND ... AND key_n=c_n"
登录后复制

该索引定义为:(key_1, ..., key_N [,a_1, ..., a_m]),且主键列为(a_1, ..., a_m, b1, ..., b_k),并且n >= N。

那么这个查询就是一个ROR查询。简单说明:对于该索引左前缀(key_1,...key_n)都是定值,对应该值的子树顺序是按照剩余索引列来排序的,而剩余的索引列又都是主键最左前缀,所以子树的顺序恰好同主键顺序相同。

(这一段可以参考函数is_key_scan_ror的注释和实现部分)

示例:

CREATE TABLE `tmp_index_merge` (
  `id` int(11) NOT NULL,
  `key1_part1` int(11) NOT NULL,
  `key1_part2` int(11) NOT NULL,
  `key2_part1` int(11) NOT NULL,
  `key2_part2` int(11) NOT NULL,
  `key2_part3` int(11) NOT NULL,
  `key3_part1` int(11) NOT NULL DEFAULT '4',
  PRIMARY KEY (`id`),
  KEY `ind2` (`key2_part1`,`key2_part2`,`key2_part3`),
  KEY `ind1` (`key1_part1`,`key1_part2`,`id`),
  KEY `ind3` (`key3_part1`,`id`)
) ENGINE=InnoDB;
explain select * from tmp_index_merge where (key1_part1 = 4333 and key1_part2 = 1657) or (key3_part1 = 2877);
j+----+-------------+-----------------+-------------+---------------+-----------+---------+------+------+-------------------------------------+
| id | select_type | table           | type        | possible_keys | key       | key_len | ref  | rows | Extra                               |
+----+-------------+-----------------+-------------+---------------+-----------+---------+------+------+-------------------------------------+
|  1 | SIMPLE      | tmp_index_merge | index_merge | ind1,ind3     | ind1,ind3 | 8,4     | NULL |    2 | Using union(ind1,ind3); Using where |
+----+-------------+-----------------+-------------+---------------+-----------+---------+------+------+-------------------------------------+
这就是一个ROR的index查询。ROR在Explain的执行计划中并没有任何体现,通过在代码中设置断点可以观察到。在函数get_best_disjunct_quick中,代码会跳到标签skip_to_ror_scan处执行。
登录后复制

在对index merge的成本评估时,只有所有的SEL_TREE子树都是ROR的,对应的SEL_IMERGE才是ROR的。后面我们将看看ROR和non-ROR在成本评估上的不同。

4.2 成本概述

一个index merge是由多个SEL_TREE子树组成,每个SEL_TREE对应一个range操作(参考),所以每个SEL_TREE成本仍然会按照range操作类似各自计算成本,并累加。

各个SEL_TREE子树各自获取ROWIDs后,MySQL需要对这些ROWID进行去重,最后根据ROWID获取所有数据。去重操作其实是一个对多组ROWID归并排序的问题。对于ROR和non-ROR场景归并排序复杂度略有不同。对于non-ROR的场景,需要先进行分组排序,然后合并。而对于ROR,因为ROWID是顺序的,所以前面的分组排序就省略了,直接做合并操作,这让non-ROR和ROR在成本计算上有较大的不同。

在完成去重之后,最后是根据ROWID取出主键的成本(对应的二级索引里面取出的ROWID)。

一个细节:如果某个SEL_TREE对应的索引恰好是主键索引时,那么MySQL会在其他SEL_TREE子树扫描时,直接判断扫描出来的ROWID是否在主键对应的SEL_TREE的range内,如果这个ROWID已经存在,则不在记录。这样可以尽可能的减少归并排序的元素个数。我们称这部分成本味"二级ROWID过滤成本"。

4.3 SEL_TREE子树的成本

这部分成本计算与range成本计算相同(参考),这里会将多个子树成本单独计算并累加。

for (every SEL_TREE IN SEL_IMERGE){
  cur_child= get_key_scans_params(param, *ptree, TRUE, FALSE, read_time);
  imerge_cost += (*cur_child)->read_cost;
  ......
}
登录后复制

4.4 non-ROR场景的成本计算

这里通过排序进行去重,是典型的归并排序,如果超过MySQL排序内存的限制,则是典型的外排序。先分组做红黑树排序,然后进行合并。成本分为几部分:创建红黑树、外排时磁盘读写、最后顺序读取排序结果。

4.4.1 去重复成本计算概述

这部分的成本可以完整的参考函数Unique::get_use_cost,这里做一个较为详细的补充说明。

对这个问题做一个简单的抽象:有两部分数据,第一部分有cpk_scan_records条,已排序。第二部分有non_cpk_scan_records未排序,现在需要返回去重后所有数据。单条数据大小为key_size,可用内存为max_in_memory_size。因为前面对第二部数据做了"二级ROWID过滤",所以这部分ROWID跟第一部分没有重复。因此,仅这里的第二部分数据需要进行去重。去重通过一个排序实现。

简单的说,需要对non_cpk_scan_records条记录进行外排序,最大可用内存是max_in_memory_size,单条记录大小是key_size。排序分成两部分,对部分数据做排序,然后合并。

4.4.2 二级ROWID过滤成本

如果有子树SEL_TREE是对应主键聚簇索引,另一部分子树SEL_TREE对应二级索引,那么在遍历二级索引时将取出对应的ROWID,看看是否再聚簇索引的SEL_TREE子树中,如果是,那么可以忽略这个ROWID,以免重复计算(减少后面Unique操作)。这部分的成本计算为:

imerge_cost += non_cpk_scan_records / TIME_FOR_COMPARE_ROWID;
登录后复制

另外,这里记cpk_scan_records为主键聚簇索引对应的SEL_TREE返回的ROWID数量,non_cpk_scan_records为二级索引对应的所有SEL_TREE返回的ROWID数量。

4.4.3 排序比较成本

需要进行N=non_cpk_scan_records*key_size/max_in_memory_size次排序。在每次排序过程中,如果已经排序好的记录树m个,那么新增一条记录平均需要做log2(m+1)次比较操作,m取值是从1,2...N。比较操作的成本为log2((m+1)!),MySQL使用了如下公式计算log2((m+1)!):

\[n! \approx \sqrt{2{\pi}n}(\frac{n}{e})^n\]

\[\log{n!} \approx \log{\sqrt{2{\pi}n}} + n*\log{\frac{n}{e}} \]

这里log是2为底数,再使用\[log_{n}{m} = \frac{\lg{n}}{\lg{m}}\] 通过此公式底数都可以转换为10进行运算(这一部并不是必须的,不过MySQL是这样计算的)。

阶乘转换参考:斯特靈公式(口味略重,慎入)。

对应的代码段:

result+= n_full_trees * log2_n_fact(max_elements_in_tree + 1.0);
result /= TIME_FOR_COMPARE_ROWID;
登录后复制
4.4.4 外排序时候的磁盘IO成本

在外排的时候,需要对所有的数据进行一次IO操作,成本计算如下:

293   result += DISK_SEEK_BASE_COST * n_full_trees * max_elements_in_tree / IO_SIZE;
295   result += DISK_SEEK_BASE_COST * key_size * last_tree_elems / IO_SIZE;
登录后复制

第一行是完整树的IO成本,第二部分是最后一个可能不完整树的IO成本。

4.4.5 合并成本

最后是合并成本,这是一个典型的归并排序,是对K个有序列表进行归并,时间复杂度为:

\[O(N*\lg{K})\]

归并过程中有一次读写操作,IO和比较成本加起来就是合并的成本:

\[\frac{total\_buf\_elems*\log(n\_buffers)}{TIME\_FOR\_COMPARE\_ROWID*\log2} + 2*\frac{total\_buf\_elems*elem\_size}{IO\_SIZE} \]

total_buf_elems是总元素个数;n_buffers子树数量;elem_size为单个元素大小。

未尽的细节:MySQL一次最多对15(MERGEBUFF2)颗子树做归并。

4.4.6 最后的读取

这时,完成了所有的排序操作,最后是读取结果到内存的成本:

result += ceil((double)key_size*nkeys/IO_SIZE);

4.4.7 根据ROWID取出记录的成本

所有非聚簇索引扫描获得ROWID后,最后仍然需要根据这些ROWID获取记录。

对于索引组织表(聚簇索引,InnoDB),这部分的成本计算较为简单,假设聚簇索引的总page为total_pages,这里二级索引取出的rowid数量为rows,该表的总记录树为total_rows,那么成本为:

(rows / total_rows) *total_pages

代码参考:

imerge_cost += get_sweep_read_cost(param, non_cpk_scan_records);
登录后复制

4.5 ROR场景的成本计算

ROR的时候,去重时则少了对子队列的排序,直接是对多个已经排列好的队列做合并排序。所以这里的成本计算相对简单:索引读取,合并排序,最后是根据ROWID取出所有记录的成本。

4.5.1 索引读取成本

这部分计算与索引覆盖扫描计算相同。假设单个索引块大小为BS,索引字段长度味KL,ROWID长度为RL,总是假设索引块有50%为空,如果需要扫描的记录数为RS,那么这部分成本计算为:

\[\frac{RS}{\frac{1}{2}\frac{BS}{(KL+RL)}}\]

参考函数get_index_only_read_time的实现。

4.5.2 合并排序

这次合并排序,是对多个有序列表的合并。若有K个有序列表,总记录数味N,那么其成本为:

\[O(N*\lg{K})\]

这里N为各个SEL_TREE子树对应found_records之和(MySQL这里的计算略微不同)。

4.5.3 根据ROWID取出记录的成本

这部分成本于NON-ROR场景相同,对于索引组织表(聚簇索引,InnoDB),这部分的成本计算较为简单,假设聚簇索引的总page为total_pages,这里二级索引取出的rowid数量为rows,该表的总记录树为total_rows,那么成本为:

(rows / total_rows) *total_pages

在MySQL中,对于上面表达式的rows计算做了一些不一样的处理。这里说一下主要思想,MySQL假设每个SEL_TREE完全独立,总记录数味R,如果有三个SEL_TREE子树,记对应的记录数为R(1),R(2),R(3)。如果数据都均匀分布,那么去重后总记录数为:

(R(1)+R(2)+R(3)) - R(a)*(R(1)*R(2)+R(2)+R(3)+R(1)*R(3))/R(a)^2 + R(a)*((R(1)*R(2)*R3)/R(a)^3)
登录后复制

MySQL这里做了一个近似:

(R(1)+R(2)+R(3)) - R(a)*((R(1)*R(2)*R3)/R(a)^3)
登录后复制

MySQL利用这个近似值作为上面公式的rows。到这里ROR部分成本就完成了。

5 最后

最后,如果index merge的成本比其他执行计划的成本要更小的话,那么MySQL就会选择改执行计划。案例可以参考index merge介绍。

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1656
14
CakePHP 教程
1415
52
Laravel 教程
1309
25
PHP教程
1257
29
C# 教程
1229
24
使用Java函数比较进行复杂数据结构比较 使用Java函数比较进行复杂数据结构比较 Apr 19, 2024 pm 10:24 PM

Java中比较复杂数据结构时,使用Comparator提供灵活的比较机制。具体步骤包括:定义比较器类,重写compare方法定义比较逻辑。创建比较器实例。使用Collections.sort方法,传入集合和比较器实例。

Java数据结构与算法:深入详解 Java数据结构与算法:深入详解 May 08, 2024 pm 10:12 PM

数据结构和算法是Java开发的基础,本文深入探讨Java中的关键数据结构(如数组、链表、树等)和算法(如排序、搜索、图算法等)。这些结构通过实战案例进行说明,包括使用数组存储分数、使用链表管理购物清单、使用栈实现递归、使用队列同步线程以及使用树和哈希表进行快速搜索和身份验证等。理解这些概念可以编写高效且可维护的Java代码。

PHP数据结构:AVL树的平衡之道,维持高效有序的数据结构 PHP数据结构:AVL树的平衡之道,维持高效有序的数据结构 Jun 03, 2024 am 09:58 AM

AVL树是一种平衡二叉搜索树,确保快速高效的数据操作。为了实现平衡,它执行左旋和右旋操作,调整违反平衡的子树。AVL树利用高度平衡,确保树的高度相对于节点数始终较小,从而实现对数时间复杂度(O(logn))的查找操作,即使在大型数据集上也能保持数据结构的效率。

深入了解Go语言中的引用类型 深入了解Go语言中的引用类型 Feb 21, 2024 pm 11:36 PM

引用类型在Go语言中是一种特殊的数据类型,它们的值并非直接存储数据本身,而是存储数据的地址。在Go语言中,引用类型包括slices、maps、channels和指针。深入了解引用类型对于理解Go语言的内存管理和数据传递方式至关重要。本文将结合具体的代码示例,介绍Go语言中引用类型的特点和使用方法。1.切片(Slices)切片是Go语言中最常用的引用类型之一

Java集合框架全解析:解剖数据结构,揭秘高效存储之道 Java集合框架全解析:解剖数据结构,揭秘高效存储之道 Feb 23, 2024 am 10:49 AM

Java集合框架概述Java集合框架是Java编程语言的重要组成部分,它提供了一系列可以存储和管理数据的容器类库。这些容器类库具有不同的数据结构,可以满足不同场景下的数据存储和处理需求。集合框架的优势在于它提供了统一的接口,使得开发人员可以使用相同的方式来操作不同的容器类库,从而降低了开发难度。Java集合框架的数据结构Java集合框架中包含多种数据结构,每种数据结构都有其独特的特性和适用场景。下面是几种常见的Java集合框架数据结构:1.List:List是一个有序的集合,它允许元素重复。Li

基于哈希表的数据结构优化PHP数组交集和并集的计算 基于哈希表的数据结构优化PHP数组交集和并集的计算 May 02, 2024 pm 12:06 PM

利用哈希表可优化PHP数组交集和并集计算,将时间复杂度从O(n*m)降低到O(n+m),具体步骤如下:使用哈希表将第一个数组的元素映射到布尔值,以快速查找第二个数组中元素是否存在,提高交集计算效率。使用哈希表将第一个数组的元素标记为存在,然后逐个添加第二个数组的元素,忽略已存在的元素,提高并集计算效率。

PHP SPL 数据结构:为你的项目注入速度和灵活性 PHP SPL 数据结构:为你的项目注入速度和灵活性 Feb 19, 2024 pm 11:00 PM

PHPSPL数据结构库概述PHPSPL(标准php库)数据结构库包含一组类和接口,用于存储和操作各种数据结构。这些数据结构包括数组、链表、栈、队列和集合,每个数据结构都提供了一组特定的方法和属性,用于操纵数据。数组在PHP中,数组是存储一系列元素的有序集合。SPL数组类提供了对原生的PHP数组进行加强的功能,包括排序、过滤和映射。以下是使用SPL数组类的一个示例:useSplArrayObject;$array=newArrayObject(["foo","bar","baz"]);$array

PHP源码运行问题:index报错解决方法 PHP源码运行问题:index报错解决方法 Mar 09, 2024 pm 09:24 PM

PHP源码运行问题:index报错解决方法,需要具体代码示例PHP是一种广泛使用的服务器端脚本语言,经常被用于开发动态网站和Web应用程序。然而,有时候在运行PHP源码时会遇到各种问题,其中“index报错”是比较常见的一种情况。本文将介绍一些常见的index报错原因以及解决方法,并提供具体的代码示例,帮助读者更好地处理这类问题。问题描述:在运行PHP程序时

See all articles