扫描一个表的过程其实是先把这个表从磁盘上加载到内存中,然后从内存中比较匹配条件是否满足。但内存里可能并不能完全存放的下表中所有的记录,所以在扫描表前边记录的时候后边的记录可能还在磁盘上,等扫描到后边记录的时候可能内存不足,所以需要把前边的记录从内存中释放掉。我们前边又说过,采用SNLJ 算法的两表联接过程中, 被驱动表可是要被访问好多次的 。被驱动表具体的访问次数就是由驱动表返回结果集记录数决定!如果这个被驱动表中的数据特别多而且不能使用索引进行访问,那就相当于要从磁盘上读好几次这个表,这个I/O代价就非常大了,所以我们得想办法:
成都创新互联提供高防主机、云服务器、香港服务器、服务器托管等
当被驱动表中的数据非常多时,每次访问被驱动表,被驱动表的记录会被加载到内存中,在内存中的每一条记录只会和驱动表结果集的一条记录做匹配,之后就会被从内存中清除掉。然后再从驱动表结果集中拿出另一条记录,再一次把被驱动表的记录加载到内存中一遍,周而复始,驱动表结果集中有多少条记录,就得把被驱动表从磁盘上加载到内存中多少次。
也就是说在有索引的情况下,MySQL会尝试去使用Index Nested-Loop Join算法,在有些情况下,可能Join的列就是没有索引,那么这时MySQL的选择绝对不会是最先介绍的Simple Nested-Loop Join算法,SNLJ算法是最慢的join,毕竟是笛卡尔积!
而Block Nested-Loop Join算法较Simple Nested-Loop Join的改进就在于可以减少内表的扫描次数,甚至可以和Hash Join算法一样,仅需扫描内表一次。其使用Join Buffer(联接缓冲)来减少内部循环读取表的次数。
关于 join buffer
可以看到相比Simple Nested-Loop Join算法,Block Nested-LoopJoin算法仅多了一个所谓的Join Buffer,为什么这样就能减少内表的扫描次数呢?下图相比更好地解释了Block Nested-Loop Join算法的运行过程:
可以看到Join Buffer用以缓存联接需要的列(所以再次提醒我们,最好不要把*作为查询列表,只需要把我们关心的列放到查询列表就好了,这样还可以在join buffer中放置更多的记录呢),然后以Join Buffer批量的形式和内表中的数据进行联接比较。就上图来看,记录r1,r2 … rT的联接仅需扫内表一次,如果join buffer可以缓存所有的外表列,那么联接仅需扫描内外表各一次,从而大幅提升Join的性能。
Block Nested-Loop Join开销
Block Nested-Loop Join极大的避免了内表的扫描次数,如果Join Buffer可以缓存外表的数据,那么内表的扫描仅需一次,这和Hash Join非常类似。但是Block Nested-Loop Join依然没有解决的是Join比较的次数,其仍然通过Join判断式进行比较。综上所述,到目前为止各Join算法的成本比较如下所示:
Block Nested-Loop Join影响
在使用 Block Nested-Loop Join(BNL) 算法时,还是可能会对被驱动表做多次扫描(尽管可能已经将驱动表中大部分关联字段数据存入join buffer)。如果这个被驱动表是一个大的冷数据表,除了会导致 IO 压力大以外,还会对 buffer pool产生严重的影响!
如果了解 InnoDB 的 LRU 算法就会知道,由于 InnoDB 对 Bufffer Pool 的 LRU 算法做了优化,即:第一次从磁盘读入内存的数据页,会先放在 old 区域。如果 1 秒之后这个数据页不再被访问了,就不会被移动到 LRU 链表头部,这样对 Buffer Pool 的命中率影响就不大。
但是,如果一个使用 BNL 算法的 join 语句,多次扫描一个冷表,而且这个语句执行时间超过 1 秒,就会在再次扫描冷表的时候,把冷表的数据页移到 LRU 链表头部。这种情况对应的,是冷表的数据量小于整个 Buffer Pool 的 3/8,能够完全放入 old 区域的情况。如果这个冷表很大,就会出现另外一种情况:业务正常访问的数据页,没有机会进入 young 区域。(导致正常业务sql查询因为没有剩余buffer pool空间进一步让磁盘IO变多而变得缓慢)
由于优化机制的存在,一个正常访问的数据页,要进入 young 区域,需要隔 1 秒后再次被访问到。但是,由于我们的 join 语句在循环读磁盘和淘汰内存页,进入 old 区域的数据页,很可能在 1 秒之内就被淘汰了。这样,就会导致这个 MySQL 实例的 Buffer Pool 在这段时间内,young 区域的数据页没有被合理地淘汰。
也就是说,这两种情况都会影响 Buffer Pool 的正常运作。 大表 join 操作虽然对 IO 有影响,但是在语句执行结束后,对 IO 的影响也就结束了。但是,对 Buffer Pool 的影响就是持续性的,需要依靠后续的查询请求慢慢恢复内存命中率。
为了减少这种影响,你可以考虑增大 join_buffer_size 的值,减少对被驱动表的扫描次数!
也就是说,BNL 算法对系统的影响主要包括三个方面: 可能会多次扫描被驱动表,占用磁盘 IO 资源; 判断 join 条件需要执行 M*N 次对比(M、N 分别是两张表的行数),如果是大表就会占用非常多的 CPU 资源; 可能会导致 Buffer Pool 的热数据被淘汰,影响内存命中率。
那么假设被驱动表全在内存中,这个时候 SNLJ 和 BNL 算法还有性能差别吗?当然是有的,由于 SNLJ 这个算法天然会对被驱动表的数据做多次访问,所以更容易将这些数据页放到 Buffer Pool 的头部,从而污染 Buffer Pool。另外,即使被驱动表数据都在内存中,但每次查找“下一个记录的操作”,都是类似指针操作。而 BNL 算法中的 join_buffer 是数组,遍历的成本更低,从被驱动表读取一条数据去 join_buffer 中遍历。
BNL的相关设置
mysql默认开启BNL
开关BNL
1、 缓存块嵌套循环连接通过一次性缓存多条数据,把参与查询的列缓存到Join Buffer 里,然后拿join buffer里的数据批量与内层表的数据进行匹配,从而减少了内层循环的次数、减少了内部表访问次数(遍历一次内层表就可以批量匹配一次Join Buffer里面的外层表数据)。
2、什么时候会使用BNL? 当内表关联字段上没有索引时,不使用Index Nested-Loop Join的时候,默认使用Block Nested-Loop Join。
3、join buffer的相关概念:
待续。。。。。
4、使用Block Nested-Loop Join算法需要开启优化器管理配置的optimizer_switch的设置block_nested_loop为on,默认为开启。
没有用过mysql加密,但一般都是用服务端语言把数据加密后存入数据库中,比如,在PHP里用md5函数把用户的密码加密之后存入数据中,一般都 是这么解决的。
以每24小时作为一份时间(而非自然日),根据用户的配置有两种工作模式:带状模式中,用户仅定义开始日期时,从开始日期(含)开始,每份时间1个分片地无限增加下去;环状模式中,用户定义了开始日期和结束日期时,以结束日期(含)和开始日期(含)之间的时间份数作为分片总数(分片数量固定),以类似取模的方式路由到这些分片里。
1. DBLE 启动时,读取用户在 rule.xml 配置的 sBeginDate 来确定起始时间
2. 读取用户在 rule.xml 配置的 sPartionDay 来确定每个 MySQL 分片承载多少天内的数据
3. 读取用户在 rule.xml 配置的 dateFormat 来确定分片索引的日期格式
4. 在 DBLE 的运行过程中,用户访问使用这个算法的表时,WHERE 子句中的分片索引值(字符串),会被提取出来尝试转换成 Java 内部的时间类型
5. 然后求分片索引值与起始时间的差,除以 MySQL 分片承载的天数,确定所属分片
1. DBLE 启动时,读取用户在 rule.xml 配置的起始时间 sBeginDate、终止时间 sEndDate 和每个 MySQL 分片承载多少天数据 sPartionDay
2. 根据用户设置,建立起以 sBeginDate 开始,每 sPartionDay 天一个分片,直到 sEndDate 为止的一个环,把分片串联串联起来
3. 读取用户在 rule.xml 配置的 defaultNode
4. 在 DBLE 的运行过程中,用户访问使用这个算法的表时,WHERE 子句中的分片索引值(字符串),会被提取出来尝试转换成 Java 内部的日期类型
5. 然后求分片索引值与起始日期的差:如果分片索引值不早于 sBeginDate(哪怕晚于 sEndDate),就以 MySQL 分片承载的天数为模数,对分片索引值求模得到所属分片;如果分片索引值早于 sBeginDate,就会被放到 defaultNode 分片上
与MyCat的类似分片算法对比
中间件
DBLE
MyCat
分片算法种类 date 分区算法 按日期(天)分片
两种中间件的取模范围分片算法使用上无差别
开发注意点
【分片索引】1. 必须是字符串,而且 java.text.SimpleDateFormat 能基于用户指定的 dateFormat 来转换成 java.util.Date
【分片索引】2. 提供带状模式和环状模式两种模式
【分片索引】3. 带状模式以 sBeginDate(含)起,以 86400000 毫秒(24 小时整)为一份,每 sPartionDay 份为一个分片,理论上分片数量可以无限增长,但是出现 sBeginDate 之前的数据而且没有设定 defaultNode 的话,会路由失败(如果有 defaultNode,则路由至 defaultNode)
【分片索引】4. 环状模式以 86400000 毫秒(24 小时整)为一份,每 sPartionDay 份为一个分片,以 sBeginDate(含)到 sEndDate(含)的时间长度除以单个分片长度得到恒定的分片数量,但是出现 sBeginDate 之前的数据而且没有设定 defaultNode 的话,会路由失败(如果有 defaultNode,则路由至 defaultNode)
【分片索引】5. 无论哪种模式,分片索引字段的格式化字符串 dateFormat 由用户指定
【分片索引】6. 无论哪种模式,划分不是以日历时间为准,无法对应自然月和自然年,且会受闰秒问题影响
运维注意点
【扩容】1. 带状模式中,随着 sBeginDate 之后的数据出现,分片数量的增加无需再平衡
【扩容】2. 带状模式没有自动增添分片的能力,需要运维手工提前增加分片;如果路由策略计算出的分片并不存在时,会导致失败
【扩容】3. 环状模式中,如果新旧 [sBeginDate,sEndDate] 之间有重叠,需要进行部分数据迁移;如果新旧 [sBeginDate,sEndDate] 之间没有重叠,需要数据再平衡
配置注意点
【配置项】1. 在 rule.xml 中,可配置项为 propertyname="sBeginDate" 、 propertyname="sPartionDay" 、 propertyname="dateFormat" 、 propertyname="sEndDate" 和 propertyname="defaultNode"
【配置项】2.在 rule.xml 中配置 propertyname="dateFormat",符合 java.text.SimpleDateFormat 规范的字符串,用于告知 DBLE 如何解析sBeginDate和sEndDate
【配置项】3.在 rule.xml 中配置 propertyname="sBeginDate",必须是符合 dateFormat 的日期字符串
【配置项】4.在 rule.xml 中配置 propertyname="sEndDate",必须是符合 dateFormat 的日期字符串;配置了该项使用的是环状模式,若没有配置该项则使用的是带状模式
【配置项】5.在 rule.xml 中配置 propertyname="sPartionDay",非负整数,该分片策略以 86400000 毫秒(24 小时整)作为一份,而 sPartionDay 告诉 DBLE 把每多少份放在同一个分片
【配置项】6.在 rule.xml 中配置 propertyname="defaultNode" 标签,非必须配置项,不配置该项的话,用户的分片索引值没落在 mapFile 定义