《MySQL是怎样运行的》学习笔记(4)——访问方法和连接原理

文章目录

      • 1. 单表访问方法
        • 1.1 const
        • 1.2 ref
        • 1.3 ref_or_null
        • 1.4 range
        • 1.5 index
        • 1.6 all
      • 2.连接原理
        • 2.1 嵌套循环连接(Nested-Loop Join)
        • 2.2 基于块的嵌套循环连接(Blocked Nested-Loop Join)

学习资料:
MySQL是怎样运行的:从根儿上理解MySQL

访问方法是什么概念呢?就是方法嘛(废话)。

mysql其实也是一个软件,我们给它语句,它返回结果,至于它是如何获得这个结果的,我们不管。大多数情况下,都是select查询语句,这个如何获得结果,也就是执行查询语句的方式,就称为访问方法。也就是说,同样的语句,用不同的方法执行,虽然得到的结果肯定是一样,但过程不一样啊。就像你导航,起点和终点一样,但是你可以选择是要时间最短、红绿灯最少、换乘最少还是步行最少的啊。

1. 单表访问方法

单表,就是from后面只有一个表的查询方法。

1.1 const

看名字可以猜到,这是常数的意思,前面说了索引,最快的其实就是用聚簇索引和唯一二级索引的等值比较了。

-- id为pk
select * from table where id = 1024;

也好理解,无论是聚簇索引还是唯一二级索引,首先其值就是唯一的,所以等值比较肯定是直接定位到其唯一所在的记录,中间不用轮询比较之类的操作,就算是回表,其操作也算快的。这有点像代码中,没有循环递归等复杂语句,只有普通的赋值语句,无论是10行还是100行,其实都快。

这就是常数级别的操作

1.2 ref

刚才const的前提是:聚簇索引或者唯一二级索引,那么如果不唯一呢?此时就不一定了,因为唯一的话,就是直接匹配到再去回表,目标明确,不会产生多余的情况,但是其实如果二级索引不唯一的话,得到的结果集,再回表,此时就是一个随机I/O的操作了,这就很费时了。

-- key1 为普通二级索引
select * from table where key1 = 'abc';

如上,普通二级索引可能对应多条记录,这时候,即可以采用二级索引+回表的方式,也可以直接拿筛选条件去全表扫描匹配。

所以就要评估这两种方法,如果二级索引得到的记录较少,那么速度还是可以的。

这种普通二级索引进行等值匹配的方法,就叫ref。

1.3 ref_or_null

前面说的两种等值比较(const/ref)其实都漏了一种情况,那就是null值的存在。在数据库中,无论啥时候,null值都是特殊人物。

-- key1为普通二级索引
select * from table where key1 = "abc" or key1 is null; 

这就是ref_or_null

(再详细一点的我也不知道了=_=)

1.4 range

这个也比较明显吧,前面说的都是等值比较的情况,但很多时候其实是如下这样的条件:

-- key2 为普通二级索引
select * from table where key2 in (1024,2048) or (key2 > 256 and key2 < 512);

此时当然也可以采用全表扫描的方法,但也可以采用key2这个二级索引+回表的方法,这样就叫range。

1.5 index

索引除了聚簇索引和二级索引,还有一种联合索引(其实也属于二级索引)。有时候会出现这样的情况:

-- key1_key2_key3是联合索引idx_key

select key1,key2,key3 from table where key2 = "abc"

这时候有哪儿不一样呢:

  1. select中只有三列,而联合索引idx_key中又包含这三列;
  2. 搜索条件中只有key2这一列,而这一列又包含在联合索引idx_key中;

所以,虽然搜索条件不是直接的索引等值匹配,但是可以通过直接遍历该联合索引来进行筛选匹配,二级索引比聚簇索引要小的多,所以遍历成本也要小,匹配成功后直接输出结果集就行了,也不用进行回表操作。这就是index操作。

1.6 all

这就是最后的招数了,全表扫描。

说一下我的感觉:

其实这些,都是为了更好更快的得到结果,在使用的过程中,也可以思考一下,如何在最小的成本中得到结果,其实可以得到很多启发,上面这些都是常规思路而已,在具体的工作中,可以参考这种思路,结合自己的业务及数据特点,来进行针对性的优化。

2.连接原理

大致形式如下:

select ti.xxx,t2.xxx from t1 [inner|left|right] join t2 on t1.id = t2.1d

这里面,t1就叫驱动表,t2就叫被驱动表。关于连接、内连接和外连接这些基本概念就不多说了。

2.1 嵌套循环连接(Nested-Loop Join)

上面提到了驱动表和被驱动表,其大致过程就是:

  1. 选取驱动表,使用与驱动表相关的筛选条件,使用代价最低的单表访问方法来执行单表查询,得到驱动表的结果集;
  2. 然后对于驱动表结果集中的每一条记录,去遍历被驱动表进行筛选,看是否符合条件;

为什么说选择驱动表:
因为在外连接中,是固定的左边或者右边为驱动表,但是在内连接中,选哪个都可以。

这个借用学习资料中的一段伪代码来展示其过程吧:

for each row in t1 {    # 驱动表中得到的记录
    for each row in t2 {    # 左连接的话,从左到右顺序查询的表
        for each row in t3 {    # 前面两表join后得到的结果集,再与第三个表进行join
            if row satisfies join conditions, send to client
        }
    }
}

这种驱动表只访问一次,但被驱动表访问多次,访问次数取决于对前面的驱动表进行单表查询得到的结果集记录条数,这就是最简单的连接方式,就叫嵌套循环连接。

可以利用被驱动表的索引加快连接速度,在此就不详细展开了

2.2 基于块的嵌套循环连接(Blocked Nested-Loop Join)

select * from t1 join t2 on t1.id = t2.id;

上面说的操作,都有一个前提,那驱动表的结果集和被驱动表的记录进行条件对比,这个动作是在内存中进行的,也就是说,其实真实过程是:

  1. 将被驱动表(t2)加载到内存,将驱动表(t1)的结果集的中一条记录加载到内存,然后拿被驱动表(t2)的每一条记录与t1加载进来的一条记录进行比较;
  2. 比较完后,内存就会被清除,然后取t1的下一条记录,和t2表加载进内存继续上面一步。。。

所以说,这样的话,磁盘I/O太多了,为啥不一次性加载多条记录进内存呢?尽量减少访问被驱动表t2的次数

在连接以前,先申请一块内存,叫join buffer,先将若干条驱动表结果集t1的记录加载进去(只加载需要的列,例如查询的或者筛选条件中的),然后开始扫描被驱动表t2,被驱动表t2中的每一条记录,就每次和驱动表t1的多条记录进行比较。盗图如下:

《MySQL是怎样运行的》学习笔记(4)——访问方法和连接原理_第1张图片

我个人觉得,这个基于块的嵌套循环连接和不基于块的好像没啥差别。

你可能感兴趣的:(MySQL)