强迫症犯了

1叨逼叨

上周看一个旧项目,发现了个类 800多 行 if else if ... ... 这是人干的事?

解决办法,业务逻辑优化,使用 switch、状态模式、职责链模式等等,网上一搜一大堆。

这个项目对应的数据库表结构... ...

解决办法,参考大佬写的《为什么数据库字段要使用 not null?》

2text 类型的字段、varchar(1000)

varchar当存储的字符串长度小于255字节时,其需要1字节的空间,当大于255字节时,需要2字节的空间。

InnoDB 的数据是按数据页为单位来读写的。也就是说,当需要读一条记录的时候,并不是将这个记录本身从磁盘读出来,而是以页为单位,将其整体读入内存。在 InnoDB 中,每个数据页的大小默认是 16KB。

原则:只要一行记录的总和超过8k,就会溢出。

所以:varchar(9000) 或者 varchar(3000) + varchar(3000) + varchar(3000),当实际长度大于8k的时候,就会溢出;

所以:Blob,text,一行数据如果实际长度大于8k会溢出,如果实际长度小于8k则不会溢出,并非所有的blob,text都会溢出。

溢出有什么危害

溢出的数据不再存储在B+tree中;

溢出的数据使用的是uncompress BLOB page,并且存储独享,这就是存储越来越大的真正原因。

如何对大字段进行优化

如果有多个大字段,尽量序列化后,存储在同一列中,避免多次off-page(行溢出),将text等大字段从主表中拆分出来:

  • 存储到key-value中 ;
  • 存储在单独的一张子表中,并且压缩,必须保证一行记录小于8k;

3如果用 left join 的话,左边的表一定是驱动表吗?

小表驱动大表,什么是小表。

在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与 join 的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表。

那么答案肯定是不一定了,为什么不一定,举个例子:

表结构
表结构

表 a 和 b 都有两个字段 f1 和 f2,不同的是表 a 的字段 f1 上有索引。然后,我往两个表中都插入了 6 条记录,其中在表 a 和 b 中同时存在的数据有 4 行。

两种写法:

可以看到:

  • 语句 Q1 返回的数据集是 6 行,表 a 中即使没有满足匹配条件的记录,查询结果中也会返回一行,并将表 b 的各个字段值填成 NULL。
  • 语句 Q2 返回的是 4 行。从逻辑上可以这么理解,最后的两行,由于表 b 中没有匹配的字段,结果集里面 b.f2 的值是空,不满足 where 部分的条件判断,因此不能作为结果集的一部分。

实际执行这两条语句时,MySQL 是怎么做的。

语句 Q1 的 explain 结果:

可以看到,这个结果符合我们的预期:

  • 驱动表是表 a,被驱动表是表 b;
  • 由于表 b 的 f1 字段上没有索引,所以使用的是 Block Nested Loop Join(简称 BNL) 算法。

... 咋变成 hash join 了

我去看下官网 https://dev.mysql.com/doc/refman/8.0/en/hash-joins.html

大概意思是 从 MySQL 8.0.20 开始,删除了 Block Nested-Loop Join ,并且服务器在以前使用 BNL 的地方现在都使用hash join。

是说 MySQL 8.0.18 及更高版本尽可能使用哈希连接,然后就是配置和注意事项。

翻译大概是 explain 的 type ALL、index、range 都可以使用 Join buffering,总之就是比之前的 BNL 算法变快了。

回到 Q1 这条语句

这条语句的执行流程其实是这样的:

  1. 把表 a 的内容读入 join_buffer 中。因为是 select * ,所以字段 f1 和 f2 都被放入 join_buffer 了。
  2. 顺序扫描表 b,对于每一行数据,判断 join 条件(也就是 (a.f1=b.f1) and (a.f1=1))是否满足,满足条件的记录, 作为结果集的一行返回。如果语句中有 where 子句,需要先判断 where 部分满足条件后,再返回。
  3. 表 b 扫描完成后,对于没有被匹配的表 a 的行(在这个例子中就是 (1,1)、(2,2) 这两行),把剩余字段补上 NULL,再放入结果集中。

语句 Q1 的 explain 结果:

如果一条 join 语句的 Extra 字段什么都没写的话,就表示使用的是 Index Nested-Loop Join(简称 NLJ)算法。

语句 Q2 的执行流程是这样的:顺序扫描表 b,每一行用 b.f1 到表 a 中去查,匹配到记录后判断 a.f2=b.f2 是否满足,满足条件的话就作为结果集的一部分返回。

为什么语句 Q1 和 Q2 这两个查询的执行流程会差距这么大呢?

其实,这是因为优化器基于 Q2 这个查询的语义做了优化。

在 MySQL 里,NULL 跟任何值执行等值判断和不等值判断的结果,都是 NULL。这里包括, select NULL = NULL 的结果,也是返回 NULL。

因此,语句 Q2 里面 where a.f2=b.f2 就表示,查询结果里面不会包含 b.f2 是 NULL 的行,这样这个 left join 的语义就是“找到这两个表里面,f1、f2 对应相同的行。对于表 a 中存在,而表 b 中匹配不到的行,就放弃”。

这样,这条语句虽然用的是 left join,但是语义跟 join 是一致的。

因此,优化器就把这条语句的 left join 改写成了 join,然后因为表 a 的 f1 上有索引,就把表 b 作为驱动表,这样就可以用上 NLJ 算法。在执行 explain 之后,你再执行 show warnings,就能看到这个改写的结果,如图所示:

DataGrip 客户端 show warnings 还用不了... ..

非得终端... .

即使在 SQL 语句中写成 left join,执行过程还是有可能不是从左到右连接的。也就是说,使用 left join 时,左边的表不一定是驱动表。

回顾之前 Q1、Q2 这两个 left join 语句的语义逻辑是不相同的。

如果需要 left join 的语义,就不能把被驱动表的字段放在 where 条件里面做等值判断或不等值判断,必须都写在 on 里面。

select * from a join b on(a.f1=b.f1) and (a.f2=b.f2); /*Q3*/
select * from a join b on(a.f1=b.f1) where (a.f2=b.f2);/*Q4*/

可以看到,这两条语句都被改写成:


select * from a join b where (a.f1=b.f1) and (a.f2=b.f2);

执行计划自然也是一模一样的。

也就是说,在这种情况下,join 将判断条件是否全部放在 on 部分就没有区别了。

4Simple Nested Loop Join 的性能问题

BNL 算法的执行大概逻辑是:

  1. 首先,将驱动表的数据全部读入内存 join_buffer 中,这里 join_buffer 是无序数组;
  2. 然后,顺序遍历被驱动表的所有行,每一行数据都跟 join_buffer 中的数据进行匹配,匹配成功则作为结果集的一部分返回。

Simple Nested Loop Join 算法的执行逻辑是:顺序取出驱动表中的每一行数据,到被驱动表去做全表扫描匹配,匹配成功则作为结果集的一部分返回。

  1. 在对被驱动表做全表扫描的时候,如果数据没有在 Buffer Pool 中,就需要等待这部分数据从磁盘读入;
  2. 从磁盘读入数据到内存中,会影响正常业务的 Buffer Pool 命中率,而且这个算法天然会对被驱动表的数据做多次访问,更容易将这些数据页放到 Buffer Pool 的头部;
  3. 即使被驱动表数据都在内存中,每次查找“下一个记录的操作”,都是类似指针操作。而 join_buffer 中是数组,遍历的成本更低。所以说,BNL 算法的性能会更好。

叨逼叨完了

今天这是强迫症犯了顺便复习下

其实 mysql 的东西也忘的差不多了

还是得没事的时候再复习复习

MySQL中varchar最大长度是多少?: https://www.cnblogs.com/gomysql/p/3615897.html

【mysql】关于InnoDB存储引擎 text blob 大字段的存储和优化: https://www.cnblogs.com/chenpingzhao/p/6719258.html

MySQL实战系列:大字段如何优化: https://developer.aliyun.com/article/59256

MySQL优化之大字段longtext、text引发的生产问题: https://zhuanlan.zhihu.com/p/358724108

posted @ 2021-10-05 09:43  爱生活的阿琦  阅读(34)  评论(0编辑  收藏  举报