简单学习了解下mysql中的一些小知识点

113 阅读6分钟

本文正在参加「技术专题19期 漫谈数据库技术」活动

in和exists

建表:
  create table student(id int, class_name varchar(10));
  create table class (class_name varchar(10));

插入数据:
  insert into student values
    (1, "5-1"),(2, "6-1"),(3, "4-1"),
    (4, "5-1"),(5,"6-1"),(6,"4-1"),(7,"6-1");
  insert into class values("4-1"),("5-1");

查询语句:
  select * from student where class_name in (select class_name from class);
  select * from student where exists ( select 1 from class where class._class_name = student.class_name )
分析:
  以上两条语句的执行结果都是一样的, 并且两条语句的语义都是为了等到student表中class_name值存在于
  class表的所有数据, 可以根据explain语句看到, class表是被优先加载的, 然后才加载了student表,
  对于以上的两种查询, 当class表的数据少于student表的数据时, in语句的执行效率会更高, 反之则exists
  语句的执行效率会更高

对于exists语句, 可以理解为将主查询的数据, 放到子查询中做条件验证, 根据验证结果(true/false)来决定
主查询的数据结果是否得以保留

orderby语句(group by类似)

order by语句中有两种排序方式, 一种是通过索引进行排序, 一种是通过filesort文件内排序, 下面我们来谈一
下会使用索引排序的情况:
<1> 查询字段是索引的顺序字段, 并且没有where条件, orderby的顺序和索引顺序相同, 例如:
    索引为idx_age_birth, 查询语句为select age, birth from test order by age, birth;
<2> whereorderby语句构成了索引的顺序使用, 并且where语句中没有使用索引的范围, 例如:
    索引为idx_age_birth, 查询语句为select * from test where age = xxx order by birth;
    如果age不是使用常量, 而是使用age > xxx的形式, 则会导致birth索引失效, 从而order by的执行会出现
    文件内排序
<3> whereorderby语句中构成了索引的顺序使用, 并且索引使用的个数不是复合索引的全部, 例如:
    索引为idx_age_birth, 查询语句为select * from test where age > 18 order by age;
    因为age索引生效了, 虽然age使用范围导致了birth索引失效, 但是orderby中并没有出现birth, 而是age,
    所以orderby会使用到索引
<4> 满足查询字段是索引字段或者where语句和orderby语句构成了索引的顺序使用的情况下, orderby必须统一
    为升序或者降序, 如下:
    explain select age, birth from test order by age desc,birth desc;

不会出现索引排序而是使用filesort排序的情况:
<1> 查询其它字段的情况下, orderby并没有跟where语句构成顺序使用:
    索引为idx_age_birth, 查询语句为select id,age from test order by age;
    上述会造成索引的失效, 造成文件内排序
<2> 混合排序, desc和asc一起使用:
    explain select age,birth from test order by age asc,birth desc;

总结:
  key(a, b, c)
  <1> order by能使用索引最左前缀
      order by a
      order by a, b, c
      order by a desc, b desc, c desc
  <2> where中使用了索引常量, 并且满足最左前缀的情况, 则order by能使用索引
      where a = "xxx" order by a, b, c
      where a = "xxx" order by b, c
  <3> 不能使用索引进行排序
      order by a asc, b desc, c desc;      // 排序不一致
      where g = xxx order by b, c;         // 丢失a索引
      where a = xxx order by c;            // 丢失b索引
      where a = xxx order by a, d;         // d不是索引的一部分
      where a in (xx,xx,xx) order by b, c; // a是范围, 范围后面的索引失效, 索引导致b,c失效

filesort

当没有在索引列上进行排序的时候, 就会导致filesort文件内排序, 对于文件内排序来说, 有两种算法, 分别是
单路排序算法和双路排序算法:

双路排序:
  MySQL4.1之前是使用双路排序, 字面意思就是两次扫描磁盘, 最终得到数据, 读取行指针和orderby列, 对他们
  进行排序, 然后扫描已经排序号的列表, 按照列表中的值重新从列表中读取对应的数据, 读取排序字段, 在buffer
  中进行排序, 再从磁盘取其它字段(进行了两次磁盘扫描)

单路排序:
  从磁盘读取查询需要的所有列, 按照orderby列在buffer对他们进行排序, 然后扫描排序后的列表进行输出, 它
  的效率更快一些, 避免了第二次读取数据, 并且把随机IO变成了顺序IO, 但是它会使用更多的空间, 因为它把
  每一行都保存在内存中了

单路排序的问题:
  在sort_buffer中, 方法B比方法A要多占用很多空间, 因为方法B是把所有字段都取出, 所以有可能取出的数据
  的总大小超出了sort_buffer的容量, 导致每次只能取sort_buffer容量大小的数据, 进行排序(创建temp文件,
  多路合并), 排序完再取出sort_buffer容量大小, 再继续排序, 从而多次IO, 单路排序是为了省一次排序,但是
  在该情况下反而导致了大量的I/O操作, 反而得不偿失, 总的来说, 就是单路排序一下子就会把所有数据进行
  读取, 而不是只读取一部分

优化策略:
  <1> 增大sort_buffer_size参数的值
  <2> 增大max_length_for_sort_data参数的值

提高order by的速度
1order byselect *是一个大忌, 应该只取query需要的字段, 因为在单路排序中会将select字段中的值所
  有取出来
  1.1 当query的字段大小总和小于max_length_for_sort_data而且排序字段不是text/blob类型时, 会用改进
      后的算法-单路排序, 否则用老算法-多路排序
  1.2 两种算法的数据都有可能超出sort_buffer的容量, 超出之后, 会创建tmp文件进行合并排序, 导致多次
      I/O, 但是用单路排序算法的风险会更大一些, 所以要提高sort_buffer_size

2、尝试提高sort_buffer_size
  不管用哪种算法, 提高这个参数都会提高效率, 当然, 要根据系统的能力去提高, 因为这个参数是针对每个进程的

3、尝试提高max_length_for_sort_data
  提高这个参数, 会增加用改进算法的概率, 因为当查询的字段的大小低于该字段值时则用单路排序, 但是如果
  设置的太高, 数据总容量超出sort_buffer_size的概率就增大, 明显症状是高的磁盘I/O活动和低的处理器使
  用率

group by: 等同于order by
  group by实质是先排序后进行分组, 按照索引键的最佳左前缀
  当无法使用索引列, 增大max_length_fro_data参数的设置 + 增大sort_buffer_size参数的设置
  where高于having, 能写在where限定的条件就不要去having限定了