神奇的 SQL 之 联表细节 → MySQL JOIN 的执行过程(二)
开心一刻
一头母牛在吃草,突然一头公牛从远处狂奔而来说:“快跑啊!!楼主来了!”
母牛说:“楼主来了关我屁事啊?”
公牛急忙说:“楼主吹牛逼呀!”
母牛大惊,拔腿就跑,边跑边问:“你是公牛你怕什么啊?”
公牛无奈道:“现在的楼主不仅吹牛逼,还扯蛋!”
然后小牛也在跟着跑,公牛和母牛问:“儿子你跑什么呢?”
小牛说:“楼主还扯犊子啊”
前情回顾
神奇的 SQL 之 联表细节 → MySQL JOIN 的执行过程(一)中,我们讲到了 JOIN 的部分内容,像:驱动表、JOIN 大致流程等。什么,还没看?赶紧去看呀,啊? 你都知道呀,那你走吧
走就走,你把欠的内容还上我就走;我欠什么了? 我欠,我欠... 我好像是欠点东西
1、BKA(Batched Key Access)
2、ON 和 WHERE
请各位坐好,我要开始表演了
环境准备
数据库: MySQL 5.7.1
存储引擎: InnoDB
建表和初始化数据
-- 查看版本和存储引擎
SELECT VERSION();
SHOW ENGINES;
SHOW VARIABLES LIKE '%storage_engine%'; -- 表创建与数据初始化
DROP TABLE IF EXISTS tbl_user;
CREATE TABLE tbl_user (
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键',
user_name VARCHAR(50) NOT NULL COMMENT '用户名',
sex TINYINT(1) NOT NULL COMMENT '性别, 1:男,0:女',
create_time datetime NOT NULL COMMENT '创建时间',
update_time datetime NOT NULL COMMENT '更新时间',
remark VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注',
PRIMARY KEY (id)
) COMMENT='用户表'; DROP TABLE IF EXISTS tbl_user_login_log;
CREATE TABLE tbl_user_login_log (
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键',
user_name VARCHAR(50) NOT NULL COMMENT '用户名',
ip VARCHAR(15) NOT NULL COMMENT '登录IP',
client TINYINT(1) NOT NULL COMMENT '登录端, 1:android, 2:ios, 3:PC, 4:H5',
create_time datetime NOT NULL COMMENT '创建时间',
PRIMARY KEY (id)
) COMMENT='登录日志';
INSERT INTO tbl_user(user_name,sex,create_time,update_time,remark) VALUES
('何天香',1,NOW(), NOW(),'朗眉星目,一表人材'),
('薛沉香',0,NOW(), NOW(),'天星楼的总楼主薛摇红的女儿,也是天星楼的少总楼主,体态丰盈,乌发飘逸,指若春葱,袖臂如玉,风姿卓然,高贵典雅,人称“天星绝香”的武林第一大美女'),
('慕容兰娟',0,NOW(), NOW(),'武林东南西北四大世家之北世家慕容长明的独生女儿,生得玲珑剔透,粉雕玉琢,脾气却是刚烈无比,又喜着火红,所以人送绰号“火凤凰”,是除天星楼薛沉香之外的武林第二大美女'),
('苌婷',0,NOW(), NOW(),'当今皇上最宠爱的侄女,北王府的郡主,腰肢纤细,遍体罗绮,眉若墨画,唇点樱红;虽无沉香之雅重,兰娟之热烈,却别现出一种空灵'),
('柳含姻',0,NOW(), NOW(),'武林四绝之一的添愁仙子董婉婉的徒弟,体态窈窕,姿容秀丽,真个是秋水为神玉为骨,芙蓉如面柳如腰,眉若墨画,唇若点樱,不弱西子半分,更胜玉环一筹; 摇红楼、听雨轩,琵琶一曲值千金!'),
('李凝雪',0,NOW(), NOW(),'李相国的女儿,神采奕奕,英姿飒爽,爱憎分明'),
('周遗梦',0,NOW(), NOW(),'音神传人,湘妃竹琴的拥有者,云髻高盘,穿了一身黑色蝉翼纱衫,愈觉得冰肌玉骨,粉面樱唇,格外娇艳动人'),
('叶留痕',0,NOW(), NOW(),'圣域圣女,肤白如雪,白衣飘飘,宛如仙女一般,微笑中带着说不出的柔和之美'),
('郭疏影',0,NOW(), NOW(),'扬灰右使的徒弟,秀发细眉,玉肌丰滑,娇润脱俗'),
('钟钧天',0,NOW(), NOW(),'天界,玄天九部 - 钧天部的部主,超凡脱俗,仙气逼人'),
('王雁云',0,NOW(), NOW(),'尘缘山庄二小姐,刁蛮任性'),
('许侍霜',0,NOW(), NOW(),'药王谷谷主女儿,医术高明'),
('冯黯凝',0,NOW(), NOW(),'桃花门门主,娇艳如火,千娇百媚');
INSERT INTO tbl_user_login_log(user_name, ip, client, create_time) VALUES
('薛沉香', '10.53.56.78',2, '2019-10-12 12:23:45'),
('苌婷', '10.53.56.78',2, '2019-10-12 22:23:45'),
('慕容兰娟', '10.53.56.12',1, '2018-08-12 22:23:45'),
('何天香', '10.53.56.12',1, '2019-10-19 10:23:45'),
('柳含姻', '198.11.132.198',2, '2018-05-12 22:23:45'),
('冯黯凝', '198.11.132.198',2, '2018-11-11 22:23:45'),
('周遗梦', '198.11.132.198',2, '2019-06-18 22:23:45'),
('郭疏影', '220.181.38.148',3, '2019-10-21 09:45:56'),
('薛沉香', '220.181.38.148',3, '2019-10-26 22:23:45'),
('苌婷', '104.69.160.60',4, '2019-10-12 10:23:45'),
('王雁云', '104.69.160.61',4, '2019-10-16 20:23:45'),
('李凝雪', '104.69.160.62',4, '2019-10-17 20:23:45'),
('许侍霜', '104.69.160.63',4, '2019-10-18 20:23:45'),
('叶留痕', '104.69.160.64',4, '2019-10-19 20:23:45'),
('王雁云', '104.69.160.65',4, '2019-10-20 20:23:45'),
('叶留痕', '104.69.160.66',4, '2019-10-21 20:23:45'); SELECT * FROM tbl_user;
SELECT * FROM tbl_user_login_log; DROP TABLE IF EXISTS tbl_range_access;
CREATE TABLE tbl_range_access (
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键',
a INT(11) NOT NULL COMMENT '测试索引',
name VARCHAR(50) NOT NULL COMMENT '姓名',
age TINYINT(3) NOT NULL COMMENT '年龄',
PRIMARY KEY (id),
INDEX i_a(a)
) COMMENT='mrr测试';
INSERT INTO tbl_range_access(a,name,age) VALUES
(5,'', 23),
(8, 'asdf',20),
(1,'lljl',19),
(4, '',64),
(7,'zhangsan', 45),
(9,'lisi',46),
(2,'zhaoqian',25),
(6,'hello', 23),
(3,'world',100),
(10,'',66),
(88, '',88); SELECT * FROM tbl_range_access;
表 tbl_range_access 的数据要多一点,像上面示例只有 11 条记录,那么即使 a 字段上有索引, SELECT * FROM tbl_range_access WHERE a BETWEEN 4 AND 9; 也不会走索引,执行计划如下
数据太少,优化器觉得走索引,然后回表查询数据,还不如直接走聚簇索引全表查询来的快,所以没有选择走索引 i_a
既然数据太少,我们就多造点数据,运行 data-init 下的 RangeAccessTest.java 中的 batchAddData 方法就好,轻轻松松 10W 到手! 此时执行计划如下
MRR
讲 BKA 之前了,我们不得不先看下 MRR,它是 BKA 的重要支柱
全称 Multi-Range Read ,是对多行 IO 查询进行优化的一种策略,详情可看 MySQL 的 mrr-optimization 或者 MariaDB 的 Multi Range Read Optimization(MySQL 和 MariaDB 是什么关系? 呃,这么说吧,他们是一个爹的儿子)。简单点来说,MRR 是优化器将随机 IO 转化为顺序 IO 以降低查询过程中 IO 开销的一种手段
什么是读盘与落盘(IO) 当前绝大多少情况下,MySQL 的数据是存在机械硬盘(SATA 盘)上的,极少数情况下是存在固态硬盘(SSD)上的;读盘指的是从磁盘读取数据的过程,落盘指的是从内存持久化到磁盘的过程 为什么顺序读盘比随机读盘快 这不是绝对的,多数情况下是这样的;至于为什么,这涉及到机械硬盘的硬件知识了,包括其组织结构,以及磁盘的读盘过程,另外还需要了解 MySQL 数据的落盘与读盘(页为单位),内容太多,就不在本篇讲了(其实是触及到了楼主的知识盲区,楼主不会!)
MariaDB 中说明了如下 3 个原因
1、Rotating drives do not have to move the head back and forth
2、One can take advantage of IO-prefetching done at various levels
3、Each disk page will be read exactly once, which means we won't rely on disk cache (or buffer pool) to save us from reading the same page multiple times
使用场景
不是任何情况下 MySQL 都会使用 MRR 的,只是在某些情况下会用 MRR 来进行优化
摘自 Multi Range Read Optimization
MySQL 中的 NDB 也会用到 MRR,一般而言,我们无需关注,我们只关注上图中的情况就行了
理论之后来点案例,完美!
range access
表 tbl_range_access 的 a 字段上我们已经建了索引 i_a ,我们来个范围查询,看下执行计划 EXPLAIN SELECT * FROM tbl_range_access WHERE a BETWEEN 4 AND 9; 如下
此时没有用到 MRR,执行此查询时,磁盘 IO 访问模式将遵循下图中的红线
因为是 SELECT * ,所以通过索引 i_a 先找到主键 ID,然后通过主键 ID 回表(从聚簇索引)查询完整记录;a 在索引 i_a 中是有序的,但不保证主键在 i_a 中也是有序的(关于 MySQL 的索引,推荐大家去看:MySQL的索引),这就导致回表的过程是随机 IO
为什么 MySQL 没有采用 MRR 来保证回表的过程是顺序 IO 呢?mrr-optimization 中有这么一段话
Two optimizer_switch system variable flags provide an interface to the use of MRR optimization. The mrr flag controls whether MRR is enabled. If mrr is enabled (on), the mrr_cost_based flag controls whether the optimizer attempts to make a cost-based choice between using and not using MRR (on) or uses MRR whenever possible (off). By default, mrr is on and mrr_cost_based is on
mrr 和 mrr_cost_based 的默认值是 on ;我简单画个图,大家就明白这两个开关的作用了
上面的示例之所以没使用 MRR,是优化器觉得使用 MRR 反而提升了成本,还不如不使用
我们强制优化器使用 MRR:
-- 查看所有开关及其默认值
SELECT @@optimizer_switch; -- mrr_cost_based设置成off,强制优化器使用 mrr
SET optimizer_switch='mrr_cost_based=off';
我们再来看看执行计划是什么样的
此时用到 MRR,执行此查询时,磁盘 IO 访问模式将遵循下图中的红线
此时回表查询的主键是有序的,会采用顺序 IO 来读取数据,从而提高查询效率
MySQL 中有个 rowids_buffer,用来缓存从索引 i_a 中查询到的数据记录(包含字段 a 和主键 ID),缓存满了或者索引查完了,再对缓存中记录按照主键 id 进行排序,再用排序后的主键 id 进行回表,使得回表查询的过程是顺序 IO
是不是感觉 MRR 有点像二级索引与主键的 JOIN 操作,有这感觉就对了,后面的 BKA 也就好理解了
BKA
BKA 全称是: Batched Key Access ,是对 INL 优化后的一种联表算法,类似与 BNL 对 SNL 的优化,但又有些不同,具体我们往下看
先在表 tbl_user 新增一个索引 ALTER TABLE tbl_user ADD index i_aaa(user_name); ,此时查看执行计划 EXPLAIN SELECT * FROM tbl_user_login_log tl LEFT JOIN tbl_user tu ON tl.user_name = tu.user_name; 如下图
此时的联表算法就是 INL,因为表 tbl_user_login_log 的 user_name 是无索引的,那么从表 tbl_user_login_log 取出的 user_name 的值就是无序的,再去关联 tbl_user ,就会随机匹配索引 i_aaa ,类似下图
是不是有点类似于前面讲过的回表随机 IO ?
BKA 功能默认是关闭的( batched_key_access=off ),开启它
SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
我们再来看执行计划
从tbl_user_login_log 查询到的 user_name 的值先放到 join buffer,当 join buffer 满了或者数据查完了,再对 join buffer 里面的值进行排序,然后再去关联 tbl_user ,此时就会顺序匹配索引 i_aaa ,类似下图
如果需要回表,那么 MySQL 会按之前讲到过的回表流程再优化一次
默认值的思考
MRR 相关的 3 个开关的默认值是这样的 mrr=on,mrr_cost_based=on,batched_key_access=off
mrr=on 表示 mrr 功能是开启的,开启并不代表一定会使用,但不开启则一定享受不到 mrr 带来的优化;
mrr_cost_based=on 表示优化器会基于成本考虑来决定是否使用 mrr,使用 mrr 反而使成本变高,那为什么使用 mrr ?只有 mrr 确实是带来了效率上的提升,那么使用它才有意义,但是成本的计算又是优化器来完成的,而且是一个比较复杂的过程,一定能保证优化器的成本计算是准确的吗? 100%准确肯定不敢保证,但经过这么多年的沉淀,绝大多数情况下,优化器的成本计算是准确的,所以 mrr_cost_based 建议就采用默认值 on ,由优化器来决定是否采用 mrr
batched_key_access=off 表示默认不启用 BKA,说实话,我没太理解这么做的意图;既然是否使用 mrr 交由优化器来决定了,没什么不把是否使用 BKA 也交由优化器来决定?我能猜到的可能原因之一是 基本用不到 ,为什么这么说? 我们回想下 BKA 会在什么情况下使用: 驱动表在关联的字段上无索引,而被驱动表在关联的字段上有索引 ,而如果驱动表在关联的字段上有索引了,还有必要进行缓存、排序、再关联被驱动表吗 ? 很显然不必了,因为索引的字段本来就是有序的了;而实际应用中,关联的字段,不管是驱动表还是被驱动表,往往是同时存在索引的,而不是一个存在索引而另一个不存在索引。这只是我个人的猜想,望知道的大神能解惑下,小弟不胜感激!
总结
1、mrr 带来的性能上的提升就是将随机 IO 优化成 顺序 IO,从而提高查询效率
2、mrr 的使用场景比较有限, range access 和基于 req、eq_ref access 的 BKA,至于其他不适用的场景,我们可以结合 mrr 的特性分析出原因
3、mrr 相关的 3 个开关的默认值不建议改动,这可是 MySQL 这么多年的经验总结
有人可能会这样说了,既然这 3 个开关不推荐改,那看与不看这篇博文没什么区别,额...,你好像说的对
4、关于 ON 和 WHERE,我只能说真的抱歉了,又要往后拖了,实在是不行,你,你......,你来打我呀
神奇的 SQL 之 联表细节 → MySQL JOIN 的执行过程(二)的更多相关文章
- 神奇的 SQL 之 联表细节 → MySQL JOIN 的执行过程
问题背景 对于 MySQL 的 JOIN,不知道大家有没有去想过他的执行流程,亦或有没有怀疑过自己的理解(自信满满的自我认为!):如果大家不知道怎么检验,可以试着回答如下的问题 驱动表的选择 MySQ ...
- 神奇的 SQL 之 联表细节 → MySQL JOIN 的执行过程(一)
开心一刻 我:嗨,老板娘,有冰红茶没 老板娘:有 我:多少钱一瓶 老板娘:3块 我:给我来一瓶,给,3块 老板娘:来,你的冰红茶 我:玩呐,我要冰红茶,你给我个瓶盖干哈? 老板娘:这是再来一瓶,我家卖 ...
- MySQL查询语句执行过程及性能优化(JOIN/ORDER BY)-图
http://blog.csdn.net/iefreer/article/details/12622097 MySQL查询语句执行过程及性能优化-查询过程及优化方法(JOIN/ORDER BY) 标签 ...
- MySQL查询语句执行过程及性能优化-查询过程及优化方法(JOIN/ORDER BY)
在上一篇文章MySQL查询语句执行过程及性能优化-基本概念和EXPLAIN语句简介中介绍了EXPLAIN语句,并举了一个慢查询例子:
- [转]MySQL查询语句执行过程详解
Mysql查询语句执行原理 数据库查询语句如何执行?语法分析:首先进行语法分析,对使用sql表示的查询进行语法分析,生成查询语法分析树.语义检查:检查sql中所涉及的对象以及是否在数据库中存在,用户是 ...
- Mysql查询语句执行过程
Mysql查询语句执行过程 Mysql分为server层和存储引擎两部分,或许可以再加一层连接层 连接层(器) Mysql使用的是典型的C/S架构.连接器通过典型的TCP握手完成连接. 需要注 ...
- MySQL查询语句执行过程及性能优化-基本概念和EXPLAIN语句简介
网站或服务的性能关键点很大程度在于数据库的设计(假设你选择了合适的语言开发框架)以及如何查询数据上. 我们知道MySQL的性能优化方法,一般有建立索引.规避复杂联合查询.设置冗余字段.建立中间表.查询 ...
- sql语句联表更新(从一个数据库中的一张表更新到另一个数据库的另一张表)
一.sql server数据库写法: update a set a.ksgmm=b.ksgmm,a.ksgm=b.ksgm,a.scztm=b.scztm,a.sczt=b.sczt from lan ...
- sql 中 联表on 和where
left join on 中对表添加的过滤条件 只对右表起作用 左表会完整的呈现出来 要想过滤左表 on 之后用where 进行过滤 不过这样实际上是对量表之后的结果集进行过滤. rint ...
随机推荐
- 17.使用android_studio开发libgdx
以前用eclipse开发libgdx,由于每次开机都会自检一遍安卓环境,觉得慢,就把安卓项目包给关掉了,结果再打开资源目录发生了变化,导致安卓打包不了,所以决定尝试使用as开发 首先安装as,导入gd ...
- JAVA内存dump
# 注意点: # 项目运行的用户 # 使用的jdk版本下的jstack去查看 /opt/jdk1..0_191/bin/jmap -dump:format=b,file=/webser/www/`da ...
- Django框架登录验证及产生随机验证码的实例
1:views视图代码 # 登录验证 def login(request): # 使用ajax请求可以使用判断 # if request.is_ajax(): if request.method == ...
- 2019-2-27-如何替换一个-exe-的图标
title author date CreateTime categories 如何替换一个 exe 的图标 lindexi 2019-02-27 20:33:16 +0800 2019-2-27 2 ...
- 《C语言深度解剖》学习笔记之内存管理
第5章 内存管理 1.野指针 定义指针变量的同时最好初始化为NULL,用完指针后也将变量的值设置为NULL.也就是说除了使用时,别的时间都把它设置为NULL 2.堆,栈和静态区 堆:由malloc系列 ...
- @loj - 6353@「CodePlus 2018 4 月赛」组合数问题 2
目录 @description@ @solution@ @accepted code@ @details@ @description@ 请你找到 k 个不同的组合数,使得对于其中任何一个组合数 \(C ...
- protobuf_1
我使用的是最新版本的protobuf(protobuf-2.6.1),编程工具使用VS2010.简单介绍下google protobuf: google protobuf 主要用于通讯,是google ...
- 唯一索引与非唯一索引区别(UNIQUE INDEX, NON-UNIQUE INDEX)
索引是我们经常使用的一种数据库搜索优化手段.适当的业务操作场景使用适当的索引方案可以显著的提升系统整体性能和用户体验.在Oracle中,索引有包括很多类型.不同类型的索引适应不同的系统环境和访问场景. ...
- 割点(tarjan)
对于根来说,如果它有超过1棵子树,那么它是一个割点 对于非叶结点来说,如果它的某一个儿子没有回边能到达高于它的点,那么它是一个割点 叶节点不是割点 //洛谷3388 #include<algor ...
- setTimeout 传参
一般setTimeout中的参数为 setTimeout(f,time)但是如果我想要给f函数传入一个参数怎么办 setTimeout(f(arguments),time) 如果我这样写的话,那么ti ...