本文是 MySQL 简单查询语句执行过程分析
6 篇中的第 3 篇,第 1 ~ 2 篇请看这里:
[MySQL 简单查询语句执行过程分析(一)词法分析 & 语法分析]
[MySQL 简单查询语句执行过程分析(二)查询准备阶段]
示例表及 SQL 如下:
-- 表结构
CREATE TABLE `t_recbuf` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`i1` int(10) unsigned DEFAULT '0',
`str1` varchar(32) DEFAULT '',
`str2` varchar(255) DEFAULT '',
`c1` char(11) DEFAULT '',
`e1` enum('北京','上海','广州','深圳','天津','杭州','成都','重庆','苏州','南京','洽尔滨','沈阳','长春','厦门','福州','南昌','泉州','德清','长沙','武汉') DEFAULT '北京',
`s1` set('吃','喝','玩','乐','衣','食','住','行','前后','左右','上下','里外','远近','长短','黑白','水星','金星','地球','火星','木星','土星','天王星','海王星','冥王星') DEFAULT '',
`bit1` bit(8) DEFAULT b'0',
`bit2` bit(17) DEFAULT b'0',
`blob1` blob,
`d1` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2001 DEFAULT CHARSET=utf8;
-- 查询语句
select * from t_recbuf where i1 > 49276
本文讲述从 InnoDB 存储引擎中读取数据,所以文中涉及到存储引擎
的地方,都直接使用 InnoDB
代替了。
内容目录如下:
创建 InnoDB 实例
建立 MySQL 和 InnoDB 索引映射
创建 InnoDB 查询模板
填充查询模板
使用什么索引
是否需要回表
server 层需要 InnoDB 返回哪些字段
从 InnoDB 读取记录
预读缓存
从 Buffer Pool 读记录
一致性视图
MySQL 每打开一个表,就会创建一个对应的 InnoDB 实例。创建 InnoDB 实例时,主要会干 3 件事情:
创建 InnoDB 实例是在上一篇(查询准备阶段
)中讲到的打开表
的过程中进行的。
MySQL 从 InnoDB 读取数据之前,词法分析、语法分析、查询准备、查询优化这些阶段都是 server 层的范围,在 server 层中需要使用索引信息时,使用的都是 MySQL 的索引信息,InnoDB 读取数据时会使用自己的索引信息,需要通过 MySQL 索引找到 InnoDB 索引,而这个找 InnoDB 索引的过程,是每执行一条使用索引进行查询的 SQL 都会用到的,又是一个频繁而重复的操作。为了更快的找到 InnoDB 索引,在创建 InnoDB 实例
阶段就会建立 MySQL 索引和 InnoDB 索引之间的映射关系,这本质上也是个缓存(MySQL 中缓存思想无处不在)。
建立映射的过程是遍历 MySQL 表中的每一个索引,用索引的名字去对应的 InnoDB 表中找 InnoDB 索引,找到后把这个对应关系保存到数组中,数组 key 为 MySQL 表中的索引 ID,value 为 InnoDB 索引。
dict_index_t** index_mapping;
// id, idx_i1 指的是 InnoDB 中相应的索引,都是 dict_index_t 指针
index_mapping[0] = id
index_mapping[1] = idx_i1
建立映射之后,在以后的操作中,通过 MySQL 索引 ID
就可以很方便的找到 InnoDB 索引了。
InnoDB 会根据查询模板
中的信息执行查询,查询模板中包含这些信息:
还有很多其它信息,就不一一列举了。
创建 InnoDB 实例过程中,只是创建了查询模板
实例并给其中的属性设置了初始值。在填充查询模板
阶段会为这些属性设置实际的值。
查询模板中的属性很多,我们只介绍示例 SQL 执行过程中使用到的比较重要的属性:
InnoDB 中有两种索引:主键索引(也叫聚簇索引)、二级索引,在执行过程中使用哪种类型的索引,使用哪个索引,是在查询优化阶段决定的。
根据查询优化阶段确定的要使用的索引 ID
从 MySQL 和 InnoDB 索引映射
中找到 InnoDB 索引,后面执行读取数据操作时,就直接使用这个索引。
先来说说什么是回表
?
使用二级索引读取数据时,如果 server 层要求 InnoDB 返回的字段,在二级索引中并不存在,那么就需要再去读主键索引,以获取二级索引中不存在的那些字段,这个过程就叫回表。
回表是 InnoDB 的自主操作,当使用二级索引读取数据并需要回表时,InnoDB 就直接使用二级索引中读取到的主键字段值
去主键索引中读取完整的记录,并返回给 server 层,server 层对于回表无感知。
知道了回表的原理,我们可以想到回表并不复杂,只需要进行两个逻辑判断就可以了:
覆盖索引
了),不需要回表,否则就需要回表。举例说明:
假设 test 表的字段 a 上有个二级索引 idx_a,当执行以下 SQL 使用 idx_a 索引进行查询时,就需要回表
select * from test where a > 1024
InnoDB 每次从 Buffer Pool 中读取一条记录时,都会读取完整的记录(字段内容有溢出时例外),读取记录之后,会拷贝字段内容到 server 层和引擎层交换内容的记录缓冲区
中,而这个拷贝字段内容的过程也是需要时间的,如果 server 层只需要 InnoDB 返回记录中的部分字段,那么拷贝所有字段内容就存在浪费了。
如果字段内容有溢出,溢出内容会存储到单独的溢出页,只有当 server 层需要该字段时,InnoDB 才会去溢出页读取其中的内容,和该字段在记录中的部分内容一起组成字段的完整内容。
关于 server 层和 InnoDB 之间的数据交换,可以参考这篇文章:[MySQL server 层和存储引擎层是怎么交互数据的?]
举例说明:
假设 test 表有 6 个字段:d、e、f、x、y、z,当执行以下 SQL 时(表上没有索引),server 层会要求 InnoDB 返回哪些字段?
select x, y, z from test
where e > 1024 order by d
select 子句中有 x, y, z
3 个字段,server 层只会要求 InnoDB 返回这 3 个字段吗?
不不不,没这么简单,server 层还想要更多,它还需要字段 e
来做 where 条件筛选,需要字段 d
来做排序,所以它会要求 InnoDB 返回 d, e, x, y, z
这 5 个字段。
经过上面的讲述,相信大家对 server 层需要 InnoDB 返回哪些字段的逻辑已经明白了,咱们来总结一下:
只有 server 层需要用到的字段,才会要求 InnoDB 返回。那是为什么呢?两个字:效率。
接下来就要开始从 InnoDB 读取记录了,由于 InnoDB 记录存储本身也是个很大的话题,要说清楚怎么定位到要查找的记录,怎么读取下一条记录这些细节,需要很多关于 InnoDB 的文件存储以及 Buffer Pool 相关的内容作铺垫,要用一个系列的文章才能讲清楚,所以本文不会涉及 InnoDB 文件存储及 Buffer Pool 相关的细节。
为了提升执行效率,InnoDB 在读取时也是煞费苦心的,用了 2 种优化手段:
自适应哈希索引的使用有很多条件限制,本文示例 SQL 不能使用自适应哈希索引来加快读取记录的速度,所以暂时先不介绍。接下来我们来看看预读缓存
是什么以及它是怎么工作的?
InnoDB 读取数据的第一步,就是先看看预读缓存
里有没有记录,如果有就直接取出 1 条记录返回给 server 层,如果没有,才会去 Buffer Pool 中读取记录。
预读缓存
又是个新概念,我们来看看它是什么?
预读缓存是 InnoDB 丰富的内心戏的产物,当 InnoDB 心里想象着 server 层会要它读取很多记录时,它就会在读取某一条记录的时候,偷偷的多读几条缓存起来,等下次 server 层再找它要数据的时候,它就可以愉快的把已经缓存起来的记录快速的 取 1 条
出来返回给 server 层,直到预读缓存中的全部记录都取出来返回给 server 层之后,再读下一条记录时,又接着偷偷的多读几条缓存起来,循环往复,直到读完所有符合条件的记录,这个缓存就是预读缓存
。
如果 server 层和 InnoDB 是两个人的话,InnoDB 从预读缓存里读记录这么快,server 层还不得给加鸡腿?
InnoDB 想象的依据是什么呢?
虽说是靠想象,但是也不能凭空瞎想吧,所以还得有个规则,规则有 2 条:
i1 = 1024
这种形式,也就是用等号
比较。大于等于 4
条记录。上面说的 2 条规则是主要的,但是要想使用预读缓存
,还会有一些其它隐密条件的限制,导致不能使用预读缓存,不再一一列举了。
上面的 2 条规则,只需要满足其中 1 条,外加满足其它隐密条件的限制,就会使用预读缓存。
预读缓存可以存几条记录?
在其它隐密的限制条件都满足的前提下:如果当前查询的 where 条件使用了索引,并且是 i1 = 1024
这种形式,InnoDB 从 Buffer Pool 中读取 1 条记录之后,会再去读取 8
条记录,这 8 条记录都
存到预读缓存里,然后把开始单独
读的 1 条记录返回给 server 层(此时预读缓存中有 8 条记录)。
如果当前查询的 where 条件没有使用索引,或者是使用了索引,但是索引字段使用的是 IN、>、>=、<、<=、!=
等等这些非等号
比较操作符,那就会判断是不是已经连续读取了大于等于 4
条记录,如果是,InnoDB 从 Buffer Pool 中读取 1 条记录之后,会再去读取 8
条记录,这 8 条记录都
存到预读缓存里,然后把开始单独
读的 1 条记录返回给 server 层(此时预读缓存中有 8 条记录)。
读取 8 条记录存到预读缓存里,这个
8
是在代码里写死的,如果想修改,只能改代码。
如果是从预读缓存读取记录,不会再从 Buffer Pool 多读 8 条记录存到预读缓存里,只有从 Buffer Pool 中读取记录并满足条件时,才会多读 8 条记录存到预读缓存里。
InnoDB 读取记录时,都是从 Buffer Pool 中读的,如果数据页
不在 Buffer Pool 中,会先把数据页从磁盘上加载到 Buffer Pool 中,然后再从 Buffer Pool 中读取记录(这是不是跟我们日常写代码时使用缓存的思路一样?)。
InnoDB 的增、删、改、查语句,都必须在事务中执行,本文示例 SQL 执行时,设置的事务隔离级别为 REPEATABLE-READ
,可以通过 show variables like 'transaction_isolation'
查看事务隔离级别。
如果我们没有显式开启事务,InnoDB 会为每条语句默认开启一个事务,语句执行完,事务就结束了。
可重复读
隔离级别下,在一个事务中多次执行同一条查询语句
时,得到的结果应该是完全一样的(不考虑幻读的话)。那么可重复读是通过什么东西实现的呢?答案就是:一致性视图
。
创建一致性视图(ReadView
)时,会记录 InnoDB 中此刻的活跃事务
信息:
上面只列出了示例 SQL 执行过程,进行可重复读时需要使用到的信息。
在可重复读
隔离级别下,一个事务中只会创建一个 ReadView
,创建的时机为:事务中第 1 条 SQL 执行的时候。
InnoDB 的每条记录中都有一个隐藏的字段用来表示最后修改记录的事务 ID
(DB_TRX_ID),还有一个隐藏字段(DB_ROLL_PTR),是个指针,指向记录的历史版本
。
当 InnoDB 从 Buffer Pool 中读到一条记录时,这条记录可能在我们创建 ReadView
之后,读取这条记录之前被修改过,这条记录的最新版本对 ReadView
不可见,此时,需要通过 DB_ROLL_PTR 找到记录的历史版本,进行可见性判断。
如果上一个历史版本对 ReadView
也不可见,就顺着上一个历史版本中的 DB_ROLL_PTR 接着找,直到找到最早的历史记录,如果还是对 ReadView
不可见,说明这条记录对于 ReadView
来说就是不可见的。
查找历史版本的过程中,只要找到了可见版本就停下来,找到的这个历史版本的记录
就是需要返回给 server 层的。
判断一条记录对于 ReadView
是否可见的逻辑是这样的:
ReadView
所属事务 ID,该记录对于 ReadView
可见(自己改的自己当然可以看到)。活跃事务的最小 ID
,说明修改记录的事务在 ReadView
创建之前就已经提交了,该记录对于 ReadView
可见。即将分配给下一个事务的 ID
,说明修改记录的事务在 ReadView
创建之后才开启,该记录对于 ReadView
不可见。所有活跃事务的 IDs
中的一个,说明修改记录的事务在 ReadView
创建时还没有提交,记录对于 ReadView
不可见所有活跃事务的 IDs
中的一个,说明修改记录的事务在 ReadView
创建之前已经提交,记录对于 ReadView
可见。上面说的
记录
包含最新记录和历史版本中的记录。
以上,就是本文的全部内容了,感谢大家花时间阅读,如果觉得有用,还请帮忙转发朋友圈,让更多的人看到,大家一起进步,谢谢 ^_^
预告一下,下一篇要写的内容是 MySQL 简单查询语句执行过程分析(四)WHERE 条件筛选
,敬请关注!
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/fI6yh0kKx2DFJGc2s_JHzQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。