首页 > 数据库技术 > 详细

MySQL学习(四)Join 等开发常用的操作 --- 2019年2月

时间:2019-02-21 14:54:50      阅读:182      评论:0      收藏:0      [点我收藏+]

1、查数据太多不会把内存用光

  InnoDB 的数据是保存在主键索引上,然后索引树分割保存在数据页上,数据页存在内存中/磁盘。change buffer 就是先把修改操作记录,然后读数据的时候,内存没有就从磁盘拿,把 change buffer 的改动应用的从磁盘读取的数据页上,返回给请求。并且此时数据页变成脏页,又会刷脏页。

  全盘扫描实际上就是扫描表的主键索引。查到符合条件的就放在结果集,然后返回给客户端。

  结果集:

  一块内存 net_buffer 写入符合条件的结果,重复获取写入到 net_buffer(默认大小16K) 直到写满,调用网络接口发出。发送成功就清空 net_buffer,然后继续获取下一行继续写到 net_buffer 中。

  然后发送函数返回 EAGAIN 或 WSAEWOULDBLOCK,表示本地网络栈(socket send buffer) 写满了,进入等待,直到本地栈重新可写,再继续发送。客户端去读 socket receive buffer 的内容,不堵塞的话,socket send buffer 就会继续把数据发送给 socket receive buffer (服务端的网络栈) 。

  这个流程就是 "边读边发",所以并不会200G的表就会用到200G的内存。 如果客户端接收的很慢,会导致 MySQL 服务端由于结果发不出去,这个事务的执行时间变长。

  show processlist 如果看到 State 的值一直处于 " Sending to client" ,就表示服务端的 socket receive buffer 写满了(网络端查询拿数据多/拿数据慢,所以写满),net_buffer 就会放满并且还有查询结果没写入到 net_buffer 中。如果想减少处于这个状态的线程,将 net_buffer_length 参数设置为一个更大的值也是可以的。

  如果客户端使用 -quick 参数,会使用 mysql_use_result 方法。 这个方法是读一行处理一行。如果有一个业务逻辑比较复杂,每读一行数据都要处理的逻辑很慢,就会导致客户端要过很久才会去取下一行数据。

  对于正常的线上业务,一个查询返回的结果不会太多(常常如此),建议使用 mysql_store_result

  这个接口,直接把查询结果保存到本地内存。

  Sending data 的意思只是 正在执行 sql 语句,并不是发送数据。

  全表扫描对 InnoDB 的影响

  WAL 机制,InnoDB 的 redo log pool buffer 内存,是保存更新的动作,配合 redo log,避免了随机写盘。

  内存的数据页是在 Buffer Pool(BP) 中管理的,在 WAL 机制里,Buffer Pool 还起到加速更新的作用。还有更重要的作用,加速查询。一般修改都保存在内存数据页,还没有落盘,此时查询,就会先查询内存(不用查磁盘),得到最新数据就返回了。

  Buffer Pool 对查询的加速效果,依赖一个重要的指标:内存命中率。

  执行命令: show engine innodb status 可以看到 Buffer pool hit rate 就是内存命中率,稳定服务的线上系统,内存命中率在99%以上。

  InnoDB Buffer Pool 的大小由参数 innodb_buffer_pool_size 决定。如果 Buffer Pool 满了,而又要从磁盘读入一个数据页,那肯定是要淘汰一个旧数据页的。

InnoDB 引擎之前用的 LRU(最少使用淘汰)算法,然后进行了改进。把 LRU 链表分为了 young 和 old 区域。5/8 是 young 区域,靠近链表尾部的 3/8 就是 old 区域。

  全盘扫描过程中,新插入的数据页是放在 old 区域开头,该数据页第一次和最后一次访问的时间间隔不超过1秒,就继续留在 old 区域, 超过1秒,就移动到 young 区域开头。 再扫描之后的数据,该数据页不会被访问到,所以很快被淘汰。

  这个策略最大收益是全盘扫描的时候,虽然用到了 Buffer Pool,但是 young 区域完全没影响,从而保证了 Buffer Pool 响应正常业务的查询命中率。

2、怎么使用 join

  select * from t1 straight_join t2 on (t1.a=t2.a);

  当使用 join 的时候,一个 SQL 语句的流程如下:

  1)、从表 t1 中读入一行数据 R;

  2)、从数据行 R 中,取出 on 条件里的 a 字段到表 t2 里去查找;

  3)、取出表 t2 中满足条件的行,跟 R 组成一行,做为结果集的一部分;

  4)、重复执行步骤1、3,知道表 t1 的末尾循环结束。

  Join 中,对 t1 表(驱动表)做了全盘扫描,去 t2 (被驱动表)查找走的是树搜索过程,所以只需要找到值符合的行就可以。

  算法:假如被搜索的行数是M,每次在被驱动表查一行数据,要先搜索索引A,再查主键索引。在被驱动表查一行的时间复杂度是 2*log2M。驱动表是N行,整个过程的复杂度是:N + N*2*log2M。

  所以 N 越小越好。

  注意事项:以小表驱动大表(小表:两个表按照各自的条件过滤,过滤完后,计算参与join的各个字段的总数据量,数据量小的那个表,就是"小表")。

  注意事项: t2 的 a 字段是 t2 表中的索引(用上被驱动表的索引,on 后的条件字段是 索引),并且称之为 "Index Nested-Loop Join"。

  如果 t2 表中的 a 字段不是索引,那么每次去 t2 表匹配就要做一次全盘扫描,复杂度就是 M*N。

  当然MySQL也没有用这个方法,而是用的 "Block Nested-Loop Join"方法,简称BNL 。

  BNL 流程如下:

  1)、把 t1 表的数据读入线程内存 join_buffer 中,扫描整个表的N行;

  2)、扫描表 t2 ,把表 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足条件就作为结果集返回。

  这个算法里,两个表都做了一次全盘扫描,扫描总行数:M+N,内存中判断次数 M*N。

  join_buffer 大小由参数 join_buffer_size 设定,默认值 256K,如果放不下驱动表的整个表的数据,那就分段放。就是一部分满足条件返回后,清空 join_buffer 后继续放。但是 join_buffer_size 的越大,可以放入的行越多,分段就越少,对驱动表的全盘扫描次数就越少。

  总结:可以使用 Index Nested-Loop Join 算法(用上被驱动表的索引)是可以用 join的,使用Block Nested-Loop Join 算法,的话,join 就尽量不要用。

就是看 explain 结果的 Extra 字段,出现"Block Nested Loop"或"Index Nested Loop"。

  

该文章只是笔记,记录的是我自己大概的总结。

如果想看完整的知识点,可以去 极客时间app 上找 MySQL实战45讲 --- 林晓斌 老师的课程。

链接:https://time.geekbang.org/column/139

MySQL学习(四)Join 等开发常用的操作 --- 2019年2月

原文:https://www.cnblogs.com/AlmostWasteTime/p/10412485.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!