目录
写在前面
一般开发一个系统,都是先设计表结构,表结构必须满足业务需求,然后写代码,代码都写好之后,再根据你的代码如何查询表的,来设计里面的索引,考虑设计几个索引,是不是联合索引,选择哪些字段,字段顺序如何排列,才能让查询语句都用上索引。
1、SQL语句的执行计划和性能优化有什么关系?
执行计划:
2、以MySQL单表查询为例,执行计划包含哪些内容(1)?
const:
- 聚簇索引
- 二级索引 (唯一索引)+ 聚簇索引
对于select * from table where id=x,或者select * from table where name=x的语句,直接就可以通过聚簇索引或者二级索引+聚簇索引回表,轻松查到你要的数据,这种根据索引直接可以快速查找数据的过程,在执行计划里称之为const,意思是性能超高的常量级。
ref:
- 二级索引(普通索引)+ 聚簇索引
- 联合索引(从索引最左侧开始连续多个列都是等值比较)
- 即使name是主键或者唯一索引,当用name IS NULL这种语法的话,还是只能走ref方式
- 二级索引里搜你要的值以及是NULL的值,然后再回表到聚簇索引里查
select * from table where name=x and name IS NULL
- 看到了const,肯定是通过主键或者唯一索引的访问,速度超高。
- 看到了ref,就是用了普通的索引,或者用主键/唯一索引搞了一个IS NULL/IS NOT NULL。
- 只要你看到const或者ref,那恭喜你,说明起码这部分执行速度是很快的!
3、以MySQL单表查询为例,执行计划包含哪些内容(2)?
range:
- range就是SQL里有范围查询的时候就会走该方式。
- 只要遍历二级索引就可以拿到你想要的数据,而不需要回源到聚簇索引的访问方式
select x1,x2,x3 from table where x2=xxx
- 聚簇索引的叶子节点放的是完整的数据页,里面包含完整的一行一行的数据。
- 联合索引的叶子节点放的也是页,但是页里每一行就x1、x2、x3和主键的值!
4、重温写出各种SQL语句的时候,会用什么执行计划(1)
- const、ref和range,本质都是基于索引查询,基于索引树的二分查找和多层跳转来查询,只要索引查出来的数据量不是特别大,一般性能都极为高效。
- index稍微次一点,需要遍历某个二级索引,但是因为二级索引比较小,所以遍历性能也还可以的。
- all最次,就是全表扫描,扫描你的聚簇索引的所有叶子节点,也就是一个表里一行行数据扫描,如果一个表就几百条数据还好,如果有几万或几十万,几百万数据。全表扫描就跪了。
select * from table where x1=xx or x2>=xx
该SQL语句要查一个表,用了x1和x2两个字段,此时有人觉得要是对x1和x2建了一个联合索引,那不就直接可以通过索引去扫描了?但万一要是你建的索引是两个呢?比如(x1,x3),(x2,x4),你建了两个联合索引,此时你这个SQL只能选择其中一个索引去用,此时会选择哪个呢?
MySQL负责生成执行计划的查询优化器,一般会选择在索引里扫描行数比较少的那个条件。
select * from table where x1=xx and c1=xx and c2>=xx and c3 IS NOT NULL
5、重温写出各种SQL语句的时候,会用什么执行计划(2)
一般一个SQL语句只能用到一个二级索引,但是有一些特殊的情况下,可能会对一个SQL语句用到多个二级索引。
select * from table where x1=xx and x2=xx
- 假设该SQL语句,比如你x1和x2两个字段,如果你先查x1字段的索引,一下子弄出来上万条数据,这上万条数据都回表到聚簇索引查完整数据,再根据x2来过滤,效果不是太好。
- 如果说同时从x2的索引树里也查一波数据做一个交集,一下就可以让交集数据量变成几十条再回表查询速度就很快。一般来说,查索引树都很快,但到聚簇索引回表查询会慢一些。
- 所以如果同时查两个索引树取一个交集后,数据量很小,然后再回表到聚簇索引去查,此时会提升性能。
- 但是如果要在一个SQL里用多个索引,那有很多硬性条件的要求,比如说如果有联合索引,你必须把联合索引里每个字段都放SQL里,而且必须都是等值匹配;
- 或者是通过主键查询+其他二级索引等值匹配,也有可能会做一个多索引查询和交集。
select * from table where x1=xx or x2=xx
也可能会用多个索引,只不过查多个大索引树之后,会取一个并集。
6、重温写出各种SQL语句的时候,会用什么执行计划(3)
- const、ref、range都是性能最好的方式,说明在底层直接基于某个索引树快速查找了数据 ,但有时可能在用了索引之后,还会回表到聚簇索引里查完整数据,接着根据其他条件过滤。
- index方式是扫描二级索引,就是说不通过索引树的根节点开始快速查找,而是直接对 二级索引的叶子节点遍历和扫描,这种速度还是比较慢的,大家尽量还是别出现这种情况。
- all就是直接全表扫描了,也就是直接扫描聚簇索引的叶子节点,那是相当的慢,index虽然扫描的是二级索引的叶子节点,但是起码二级索引的叶子节点数据量比较小,相对all要快一些。
- 可能一个SQL里用多个索引,意思就是可能对多个索引树进行查找,接着用 intersection交集、union并集的方式来进行合并。
7、多表关联的SQL语句到底是如何执行的?(1)
多表关联查询的执行原理:
select * from t1,t2 where t1.x1=xxx and t1.x2=t2.x2 and t2.x3=xxx
- 首先,如果你在 from 字句后直接来了两个表名,这意思就是要针对两个表进行查询了,而且会把两个表的数据给关联起来,假设你要是没有限定什么多表连接条件,那么可能会搞出一个笛卡尔积的东西。
- 举个例子,假设t1表有10条数据,t2表有5条数据,那么此时select * from t1,t2,其实会查出来50条数据,因为t1表里的每条数据都会跟t2表里的每条数据连接起来返回给你,那么不就是会查出来10 * 5 = 50条数据吗?这就是笛卡尔积。通常都会在多表关联语句中的WHERE子句里引入一些关联条件。
WHERE 子句: where t1.x1=xxx and t1.x2=t2.x2 and t2.x3=xxx
- 首先 t1.x1=xxx,是针对t1表的数据筛选条件,本质就是从t1表里筛选一波数据出来再跟t2表做关联的意思。然后t2.x3=xxx,也不是关联条件,他也是针对t2表的筛选条件。
- 真正的关联条件是t1.x2=t2.x2,必须要让t1表里的每条数据根据自己的x2字段的值去关联上t2表里的某条记录,要求是t1表里这条数据的x2值和t2表里的那条数据的x2字段值是相等的。
- 举个例子,假设t1表里有1条数据的x2字段的值是265,然后t2表里有2条数据的x2字段的值也是265那么此时就会把t1表里的那条数据和t2表的2条数据分别关联起来,最终会返回给你两条关联后的数据。
- 首先,根据t1.x1=xxx这个筛选条件,去t1表里查出来一批数据,此时可能是const、ref、index或者all,具体看索引如何建的,他会挑一种执行计划访问方式。
- 然后,假设从t1表里按照t1.x1=xxx条件筛选出2条数据,接着对这两条数据,根据每条数据的x2字段的值,以及t2.x3=xxx这个条件,去t2表里找x2字段值和x3字段值都匹配的数据,比如说t1表第一条数据的x2字段的值是265,此时就根据t2.x2=265和t2.x3=xxx这俩条件,找出来一波数据,比如找出来2条吧。
- 此时就把t1表里x2字段为265的那个数据跟t2表里t2.x2=265和t2.x3=xxx的两条数据,关联起来,就可以。
8、多表关联的SQL语句到底是如何执行的?(2)
假设我们有一个员工表,还有一个产品销售业绩表,员工表里包含了id(主键)、name(姓名)、department(部门),产品销售业绩表里包含了id(主键)、employee_id(员工id)、产品名称(product_name)、销售业绩(saled_amount)。
select e.name,e.department,ps.product_name,ps.saled_amount from employee e,product_saled pa where e.id=pa.employee_id
员工 部门 产品 业绩张三 大客户部 产品 A 30 万张三 大客户部 产品 B 50 万张三 大客户部 产品 C 80 万李四 零售部 产品 A 10 万李四 零售部 产品 B 12 万
外连接outer join,分为左外连接和右外连接。
- 左外连接:在左侧的表里的某条数据,如果在右侧的表里关联不到任何数据,也得把左侧表这个数据给返回出来。
- 右外连接:在右侧的表里如果关联不到左侧表里的任何数据,得把右侧表的数据返回出来。
- 内连接,那么连接条件是可以放在where语句里的。
- 外连接一般是把连接条件放在ON字句里的。
SELECTe.name,e.department,ps.product_name,ps.saled_amountFROM employee e LEFT OUTER JOIN product_saled paON e.id=pa.employee_id
员工 部门 产品 业绩张三 大客户部 产品 A 30 万张三 大客户部 产品 B 50 万张三 大客户部 产品 C 80 万李四 零售部 产品 A 10 万李四 零售部 产品 B 12 万王五 零售部 NULL NULL
9、多表关联的SQL语句到底是如何执行的?(3)
嵌套循环关联(nested-loop join):假设有两个表要一起执行关联,此时会先在一个驱动表里根据他的where筛选条件找出一波数据,比如说找出10条数据吧。接着呢,就对这10条数据走一个循环,用每条数据都到另外一个被驱动表里去根据ON连接条件和WHERE里的被驱动表筛选条件去查找数据,找出来的数据就进行关联。依次类推,假设驱动表里找出来10条数据,那么就要到被驱动表里去查询10次!
上面那伪代码其实就是3个表关联的伪代码,用的就是最笨的嵌套循环关联方法。不知道大家有没有发现上面那种多表关联方法的问题在哪里?
- 问题1: 就是我们往往从驱动表里查出来一波数据之后,要对每一条数据都循环一次去被驱动表里查询数据,所以万一你要是被驱动表的索引都没建好,总不能每次都全表扫描吧?
- 问题2;刚开始对你的驱动表根据WHERE条件进行查询的时候,也总不能全表扫描吧?
针对多表查询的语句,尽量给两个表都加上索引,索引要确保从驱动表里查询也是通过索引去查找,对被驱动表查询也通过索引去查找 。如果做到这一点,多表关联语句性能就会很高!
10、MySQL是如何根据成本优化选择执行计划的?(上)
全表扫描执行计划的成本计算方法:
MySQL是如何对一个查询语句的多个执行计划评估他的成本的?如何根据成本评估选择一个成本最低的执行计划,保证最佳的查询速度?
MySQL里的成本是什么意思?
就是跑一个SQL语句,一般成本是两块:
- IO成本:首先是那些数据如果在磁盘里,从磁盘读数据到内存就是IO成本,而且MySQL里都是一页一页读的,读一页的成本的约定为1.0。
- CPU成本:你拿到数据之后,是不是要对数据做一些运算?比如验证他是否符合搜索条件了,或者是搞一些排序分组之类的事,这些都是耗费CPU资源的,属于CPU成本,一般约定读取和检测一条数据是否符合条件的成本是0.2。
select * from t where x1=xx and x2=xx
show table status like " 表名 "
11、MySQL是如何根据成本优化选择执行计划的?(中)
索引的成本计算方法:
因为除了全表扫描之外,还可能多个索引都可以使用,但是当然同时一般只能用一个索引,所以不同索引的使用成本都得计算一下。
12、MySQL是如何根据成本优化选择执行计划的?(下)
多表关联查询是如何选择执行计划的:
因为要在一个查询执行之前,就可以针对不同的访问方法精准计算他的成本,那是根本不现实的,最后只能是根据一些相对较为简单粗暴的办法,大致估算一下,估算结果可能不是太准确,但是也没办法了,反正算出来也就这么比较就是了。
select * from t1 join t2 on t1.x1=t2.x1 where t1.x2=xxx and t1.x3=xxx and t2.x4=xxx and t2.x5=xxx
13、MySQL是如何基于各种规则去优化执行计划的?(上)
MySQL在执行一些相对较为复杂的SQL语句的时候是如何对查询进行重写来优化具体的执行计划的,因为他有时候可能会觉得你写的SQL一点都不好,直接按你的SQL生成的执行,计划效率还是不够高,需要自动帮你改改。
- 首先,要是MySQL觉得你的SQL里有很多括号,那么无关紧要的括号他会给你删除了。
- 其次比如你有类似于i = 5 and j > i这样的SQL,就会改写为i = 5 and j > 5,做一个常量替换。
- 还有比如x = y and y = k and k = 3这样的SQL,都会给你优化成x = 3 and y = 3 and k = 3,本质也是做个常量替换。
- 或者是类似于什么b = b and a = a这种一看就是乱写的SQL,一看就是没意义的,就直接给你删了。
select * from t1 join t2 on t1.x1=t2.x1 and t1.id=1
select t1 表中 id=1 的那行数据的各个字段的常量值 , t2.* from t1 join t2 on t1 表里 x1 字段的常量值=t2.x1
记住:其实你写的SQL语句真正执行时,可能Mysq是会对SQL进行各种改动的。
子查询是如何执行的**,以及他的执行计划是如何优化的**
select * from t1 where x1 = (select x1 from t2 where id=xxx)
- 第一个步骤先执行子查询,也就是:select x1 from t2 where id=xxx,直接根据主键定位出一条数据的x1字段的值。
- 接着再执行select * from t1 where x1=子查询的结果值,这个SQL语句。
还有一种子查询,就是:
select * from t1 where x1 = (select x1 from t2 where t1.x2=t2.x2)
select * from t1 where x1 in (select x2 from t2 where x3=xxx)
- 这个可能大家会想当然的认为先执行子查询,然后对t1表再进行全表扫描,判断每条数据是否在这个子查询的结果集里,但是这种方式其实效率是非常低下的。
- 所以其实对于上述的子查询,执行计划会被优化为,先执行子查询,也就是select x2 from t2 where x3=xxx这条SQL语句,把查出来的数据都写入一个临时表里,也可以叫做物化表,意思就是说,把这个中间结果集进行物化。
- 这个物化表可能会基于memory存储引擎来通过内存存放,如果结果集太大,则可能采用普通的b+树聚簇索引的方式放在磁盘里。但是无论如何,这个物化表都会建立索引,所以大家要清楚,这波中间结果数据写入物化表是有索引的。
15、MySQL是如何基于各种规则去优化执行计划的?(下)
MySQL里对子查询的执行计划进行优化的一种方式,就是semi join,也就是半连接
select * from t1 where x1 in (select x2 from t2 where x3=xxx)
select t1.* from t1 semi join t2 on t1.x1=t2.x2 and t2.x3=xxx
注意:
在互联网公司里,我们比较崇尚的是尽量写简单的SQL,复杂的逻辑用Java系统来实现就可以了,SQL能单表查询就不要多表关联,能多表关联就尽量别写子查询,能写几十行SQL就别写几百行的SQL,多考虑用Java代码在内存里实现一些数据就的复杂计算逻辑,而不是都放SQL里做。