MySQL(三)——SQL执行计划分析

目录

一、SQL执行计划概述

二、explain字段详解

    1.id

    2.select_type

    3.possible_keys

    4.key

    5. key_len

    6.ref

    7.rows

    8.Extral


一、SQL执行计划概述

我们在上一节MySQL(二)——MySQL的逻辑分层-存储引擎-SQL优化中的最后一块简单的提到了MySQL的执行计划,可能大家对SQL执行计划的概念还有点模糊,今天在继续简单的说说。

在实际的项目开发过程中,我们一般都是写个SQL语句发送给了mysql,但实际的查询过程中我们并不知道数据库里到底发生了什么,MySQL是怎么扫描表、怎样使用索引的等这些我们都不知道,我们能感知到的只有查询运行的时间。当要查询的数据规模不是很大的时候,查询几乎是瞬间的,我们也感受不到它所花费的时间;但当数据规模很大的时候,我们运行同样的一条SQL查询语句有时候却迟迟得不到响应结果。那么为了避免这种情况,我们在进行查询前能不能提前预估一下这个SQL查询究竟需要运行多长时间、查询过程中要涉及哪些表、用到哪些索引......呢?答案是能的,mysql提供了相应的功能来帮我们实现这个需求。这种提前预估SQL执行效率的功能就可以简单的理解为SQL的执行计划。我们在上一节提到了查询sql执行计划的语法就是简单的explain+SQL语句;比如我要看一下查询一张名为tb表的执行计划,直接输入:explain select * from tb;就可以了,执行完成后控制台显示效果如下:

上表中的各个字段就是SQL语句的各种性能指标, 下面对每一列的具体含义做简单的说明,对于在平时项目中比较常用的几个值我会在后面结合具体的例子给出解释,现在不必要死扣每个具体字段的含义,大体有个了解即可。

id编号,这是select语句的查询序列号,相当于select语句的识别符。
select_type

select语句的类型,有如下几种值:

  • SIMPLE
  • PRIMARY
  • UNION
  • DEPENDENT UNION
  • UNION RESULT
  • DEPENDENT SUBQUERY
  • DERIVED
table显示这个select查询的数据是关于哪张表的
type

type表示连接(我们向mysql发送SQL语句必须先与mysql建立连接)使用了何种类型,有如下几种类型:

  • system
  • const
  • eq_ref
  • ref
  • ref_or_null
  • index_merge
  • unique_subquery
  • index_subquery
  • range
  • index
  • ALL
possible_keys表示MySQL可能使用该表中的哪个索引来得到查询结果
key表示MySQL实际使用表中的那个索引索引进行的查询。如果没有选择索引则为NULL值
ken_len表示MySQL实际所使用的某个索引的长度
ref表示MySQL使用了哪些列或常数与索引一起完成了查询工作
rows表示MySQL认为它在执行查询时所必须要检查的行数
Extra一些额外的信息

二、explain字段详解

(一)数据准备

为了更加形象的说明explain中各个字段的含义我们需要简单在数据库中创建一些数据,模拟一下场景,具体数据如下:

创建课程表并插入数据

create table cource(
    cid int(3),
    cname varchar(20),
    tid int(3)
);
insert into cource values(1,'java',1);
insert into cource values(2,'html',1);
insert into cource values(3,'sql',2);
insert into cource values(4,'web',3);

创建老师表并插入数据

create table teacher(
    tid int(3),
    tname varchar(20),
    tcid int(3)
);
insert into teacher values(1,'tz',1);
insert into teacher values(2,'tw',2);
insert into teacher values(3,'tl',3);

创建老师信息表并插入数据

create table teacherCard(
    tcid int(3),
    tcdesc varchar(200)
);
insert into teacherCard values(1,'tzdecs');
insert into teacherCard values(2,'twdecs');
insert into teacherCard values(3,'tldecs');

(二)字段解释

现在有了这些数据后,我们结合具体的SQL语句来理解字段id、select_type、type的详细含义。

1.id

关于id的具体意义,在上面的“SQL执行计划概述”的表中已经做了解释,它就是一个编号,是select语句的查询序列号,相当于select语句的识别符,没有其他别的特殊含义。但有如下几点我们需要具体的注意一下:

 A:id值相同,表的查询顺序是从上往下顺序执行

这句话是什么意思呢?我们通过具体的例子来看一下:现在我要在上面三张表的基础上做一个简单的三表关联的查询操作——>查询课程号为2或教师证标号为3的老师信息?

SQL语句如下:

select t.* from teacher t,cource c,teacherCard tc where t.tid=c.tid and t.tcid=tc.tcid and (c.cid=2 or tc.tcid=3);

加上explain查看一下这条SQL语句的执行计划:

explain select t.* from teacher t,cource c,teacherCard tc where t.tid=c.tid and t.tcid=tc.tcid and (c.cid=2 or tc.tcid=3);

从上面的结果我们可以看出,三项的id值都相同,这里就要提到我们刚才说的那个结论了:id值相同,从上往下顺序执行。所以,以上三表联查的SQL语句中,在具体执行的时候它的查询顺序是先查teacher表t,再查老师信息表tc,最后查课程表c(teacher->teacherCard->cource)。

这里还有 一点需要我们注意,就是当id相同,但每张表中的数据量不同时,各个表的查询顺序又是怎样的呢?

那么现在我们修改一下数据,往teacher表中在插入三条数据,现在变为teacher表有6条数据,teacherCard中有3条数据,cource中有4条数据。

insert into teacher values(4,'ta',4);
insert into teacher values(5,'tb',5);
insert into teacher values(6,'tc',6);

继续执行SQL计划:explain select t.* from teacher t,cource c,teacherCard tc where t.tid=c.tid and t.tcid=tc.tcid and (c.cid=2 or tc.tcid=3);

可以看到id值依然相同,但在我们往teacher表中又插入了三条数据后,表的查询顺序由原来的teacher->teacherCard->cource变为了现在的teacherCard->cource->teacher。为什么会出现这种情况呢?为什么表的执行顺序会因为表中数据量的不同而不一样呢?这就与“笛卡尔积”有关了。关于笛卡尔积不明白的可以参考一下这篇文章https://blog.csdn.net/csdn_hklm/article/details/78394412。为了更好的理解,我们举个具体的例子,假如现在有a、b、c三张表,表中分别有2、3、4条数据,现在做一下a、b、c的三表联查,

如果查询顺序是a->b-c,那么它的笛卡尔积就是2*3=6*4=24;

如果查询顺序是c->a->b,那么它的笛卡尔积就是4*3=12*2=24;

可以看到虽然最后的笛卡尔都是24,但他们中间的笛卡尔积却一个是6,一个是12,也就是说在产生最终的查询结果前,上面的两种情况会分别产生出数据量为6和12的中间查询结果,因为MySQL会把具体的查询结果(不管是中间结果还是最终结果)都要保存到内存中去,所以MySQL会优先选择中间结果数据量比较小的顺序进行,所以如果在做a、b、c的三表联查时,MySQL就会选择以a->b-c的顺序进行查询。这也就解释了为什么在给teacher表中增加了3条数据后,表的查询顺序会发生改变。在进行多表联查的时候,MySQL一般会选择数据量最小的表进行优先查询,而数据量大的放到最后。

B:id值不同,表的查询顺序是id值越大越优先查询(本质:在嵌套子查询时,先查内层,再查外层)

我们再拿具体的例子来说明,还是在那三张表的基础上查询教授SQL老师的具体信息(desc字段)

SQL语句如下:

select tc.tcdesc from teacherCard tc,cource c,teacher t where t.tid=c.tid and t.tid=tc.tcid and c.cname='sql';

我们把上面的表查询转换为子查询的形式(为了方便看id不同的情况):

select tc.tcdesc from teacherCard tc where tcid =(select tcid from teacher t where t.tid=(select c.tid from cource c where c.cname='sql'));

查看一下这条SQL语句的执行计划:

explain select tc.tcdesc from teacherCard tc where tcid =(select tcid from teacher t where t.tid=(select c.tid from cource c where c.cname='sql'));

可以看到上面的id值不同了,分别是1、2、3,这个时候表的查询顺序是id值越大越优先查询。所以本次表的查询顺序是先查课程表c,再查老师表t,最后查老师信息表tc。其实这也好理解,我们上边的子查询最内层的select语句就是查的课程表c,最外层是信息表c,只有最内层执行完得到具体的结果才能传递给下一层,最后一层一层的传递完成。

C:id值有相同有不同,先执行id值大的,剩余id相同的按顺序从上往下执行。

这个我们就不写具体的例子来操作说明了,这种情况就是上面两种情况的结合,理解了上面的两种情况,这种情况理解起来就不难。下面看一下另外一个字段把。

2.select_type(select语句类型)

关于select_type中的值有很多,我们只说以下几个比较常用到了。

PRIMARY:在子查询SQL语句中的主查询,也就是最外面的那层查询。就拿我们上面那个“查询教授SQL老师的具体信息”为例,查看以一那个三层嵌套的子查询的执行计划:explain select tc.tcdesc from teacherCard tc where tcid =(select tcid from teacher t where t.tid=(select c.tid from cource c where c.cname='sql'));可以看到PRIMARY对应的那张表正是在最外层的teacherCard教师信息表。

SUBQUERY:在子查询SQL语句中的主查询,也就是除了最外层的查询,内层的所有查询都是SUBQUERY类型的。还是通过上面的例子我们可以看出,除了最外层的teacherCard表,其他在内层的两张表都是SUBQUERY类型的。

SIMPLE:简单查询,像这种只查一张表的就属于简单查询 select * from teacher(不包含子查询,不包含联合查询union)

DERIVED:衍生查询,就是在得到最终查询结果的时候会用到临时表。这种类型有分为两种情况

    a.在from子查询中只有一张临时表

    b.在from子查询中有两张(或多张)表,并且它们做了union关联,如:table1 union table2

先说第一种from后面只有一张表的情况。我们看这个SQL语句:select cr.cname from (select * from cource where tid in (1,2)) cr;from后面的这句select * from cource where tid in (1,2)其实最终的执行结果也是一张表,就是老师编号为1或2的老师教授了那些课程,这就是一张临时表。所以这种查询类型就属于DERIVED。

可以看到DERIVED类型查询的id为2,所以它先执行,也就是上面SQL语句中括号内的(select * from cource where tid in (1,2)语句先执行,而这个执行结果是一张临时表,所以它的类型为DERIVED。而外层的查询属于PRIMARY类型,它对应的table那一列的值为<derivet2>,这是什么意思呢?就是在进行外层的select查询时,要用到内层生成的那个临时表这个临时表的id是2.

再看第二种情况,在from子查询中有两张表table1 union table2。这个时候则左边的table1就是衍生表DERIVED,右边的table2就是union。

看如下SQL语句的执行计划:explain select cr.cname from (select * from cource where tid=1 union select * from cource where tid=2) cr;

可以看到先执行最右边的table2表,也就是select * from cource where tid=2,它的类型为union,再执行左边的table1,也就是select * from cource where tid=1,类型为DERIVED。

UNION:见上例b情况

UNION RESULT:这种类型在开发中不是太常见,它的主要作用就是告知开发人员哪些表之间存在union查询,具体例子也可以参见上例中b的情况,如下图,告诉了我们id为2和3的两个select查询结果进行了union。

3.type:连接类型

type的类型有很多,在开发中我们常见的就下面几种,它代表的性能从左到右依次递减,system代表性能最好。但其实最前面的三种类型system、const、eq_ref都是理想型的,在实际开发中几乎遇不到这种类型的,我们通过拼死优化差不多最多也就能达到ref这一级别的。

system>const>eq_ref>ref<range>index>all

这里有一个前提,某张表如果没有创建索引,那么你对这张表所进行的所有SQL操作最终它的type类型肯定都是all,即最差的效果。所以我们想要对SQL的type类型进行优化,必须是在有索引的前提下。

system:这种情况只有在两种情况下才会出现

   1.只有一条数据的“系统表”。即这张表必须是系统自带的,并且它里面只有一条数据。这种情况下对其做查询操作时,其type类型才是system类型的。可见这种情况机会在实际开发中遇不到。

    2.衍生表只有一条数据的主查询。这种情况我们举个具体的例子:创建一张只有一条数据的表,并为其添加主键索引

create table test(
    tid int(3) primary key,
    tname varchar(20)
);
insert into test values(1,'a');

为了构造出“衍生表只有一条数据的主查询”这种情况,我们构造出如下SQL语句:select * from (select * from test)  where tid = 1;不必理会具体SQL的具体含义,我们这里主要是为了复现这种情况。现在看一下这条语句的执行计划:

可以看到type类型确实为system。

const:仅仅能查到一条数据的SQL语句,并且只能用于“主键索引”或者“唯一索引”。也就是说这张表中的索引必须是主键或者是唯一索引,并且查询的最终结果只有一条数据时,type的类型才会为const。

我们继续以上面的test表为例,因为它本身的tid就是主键索引(主键自带索引属性)并且只有一条数据,所以必然符合条件:

eq_ref:唯一性索引。对于每个索引键的查询,返回的结果必须与索引键唯一匹配(有且只有一个,不能多,也不能为0)。

这种情况可能有点不太好理解,我们用正反两个例子来进行说明,先看反例:

我们以上面的teacher表和teacherCard表为例,已知teacher表中有6条数据,teacherCard中有3条数据。现在把teacher表的tcid字段设置为“唯一索引”alter table teacher add constraint uk_tcid unique index(tcid);把teacherCard的tcid设为“主键索引”:alter table teacherCard add constraint uk_tccid primary key (tcid);后进行下面的查询:

select t.tcid from teacher t,teacherCard tc where t.tcid = tc.tcid;

以上我们以teacher表中tcid作为索引键进行查询,因为本来tcid的数据量原本有6条数据,现在由于where后面的限制,只查到了3条,虽然没有出现重复的情况,但另外3条没查出来,这就相当于其他3条出现了等于0的情况,因此这个SQL语句就不是eq_ref类型的。如下:

正面例子:现在我们把teacher表中的tcid为4、5、6的数据删除:delete from teacher where tcid > 3;后,继续查看同上面一样的SQL语句的执行计划:explain select t.tcid from teacher t,teacherCard tc where t.tcid = tc.tcid;

此时当teacher表中的tcid只有3项时,再进行相同的查询,3条数据都显示了出来,即没重复也没有0的情况,所以此刻是eq_ref.

注意:一般在SQL语句中,第一个跟在where后面的字段就是要用到的具体索引(当然前提是你为这个字段创建了索引)。

以上讨论的三个类型system、const、eq_ref,在实际开发中几乎很难遇到,虽然他们的性能高,但都是一种可遇不可求的状态,我们很少能优化到那种程度。就比如你为了能将优化效果达到eq_ref,而像上面那样删除表中的一部分数据吗?显然这是胡扯的,老板会把你砍了的。但对于我们下面要讨论的这种ref类型,只要你上点心,努努力还是可以达到的。

ref:非唯一性索引。这个就刚好和上面的eq_ref形成了互补的关系。eq_ref为不重复不为0的情况,而ref刚好为除了“不重复不为零”的情况外的其他情况。像我们在第一个反例中演示的那样,就属于等于0时的ref类型。

range:检索指定范围范围的行,如果where后面是一个范围查询(如between...and、<、>、>=等等,这里需要注意in有时候会失效)的话,那么type类型就为range。

注意:我们现在所涉及的所有字段都是基于它是索引的前提下,我们讨论SQL的优化也是基于对索引的优化,所以如果你在输入和我一样的SQL语句,结果最后出现的结果去却不一样时,很有可能就是有的字段你没有加索引导致的。

index:查询全部索引中的数据。比如现在teacher表中tid我们已经给他构建了索引,那么在查询tid的时候,它的type类型就是index:

我们把tid的索引给他删除再看一下:

以上我们可以看出在tid是索引的时候,它的type类型是index,当它不是索引的时候type类型就变成了all。这是因为当某个字段(比如tid)是索引的时候,我们在进行查询的时候,mysql只需要扫描索引表就可以了,而不是索引的话就需要扫描表中的所有数据。

all:对于all这种类型,它表示性能最差的,如果表中没有建立任何索引,那么所进行的任何查询它的type类型就是all的。

4.possible_key、key:两个一个是可能会用到的索引、一个是实际真正用到的索引。如果他们的值是NULL,就表示没有到索引。这两个比较简单不做详细说明了。

5.key_len:索引的长度。这个经常用来判断复合索引是否被完全使用。

关于什么是“复合索引是否被完全使用”,举个简单的例子,比如现在有一张班级的人员名单表,我们为这张表中的两个字段name和age构建了一个复合索引(name,age),现在要通过这个复合索引查询班级里一个名叫张三的18岁的人。如果班里只有一个叫张三的人,那么这个复合索引就没有被完全使用,因为它只用了一个name字段就查出来了。但如果班级里有两个叫张三的人,他们的年龄不同(假如一个19,一个18),那么现在想要查到年龄为18岁的张三,复合索引里的两个字段name和age就都会备用到,这就是复合索引被完全使用。

这里还要一个知识点:1.如果mysql用的是utf8编码,那么它为每个字符会分配3个字节的大小;2.如果一个字段的类型是可以为空的,那么mysql会为它额外的分别1个字节的空间进行标识,说明它可以为空;3.如果一个字段的类型是varchar类型,mysql会为它额外的分配2个字节的空间进行标识,以区别于char类型。

现在我们创建一张test02的表,其中有三个字段name(char类型、非空)、address(char类型、可为空)、flag(varchar类型,可为空),并且把它们三个构建为一个复合索引(name,address,flag)

create table test02(
    name char(20) NOT NULL,
    address char(20),
    flag varchar(20),
    index name_address_flag (name,address,flag)
);

现在分别看一下以下语句的执行计划:

explain select * from test02 where name='';

explain select * from test02 where address='';

explain select * from test02 where flag='';

通过上面的显示结果我们可以推测出:

第一种情况的大小为60=20*3,可以看出在复合索引(name,address,flag)中只用到了name,其他两个没用的到。

第二、三种情况的大小为184=20*3+20*3+1(address中可以为空的标识)+20*3+1(flag可为空标识)+2(flag为varchar)=184

可见根据key_len的具体指的大小,我们有时候是可以大体判断出复合索引是否被完全使用了。

6.ref:注意区分这个ref和type里面的ref。这个ref的作用主要是指明当前表所参照的字段。比如下面的这个SQL语句:

        select * from cource c,teacher t where c.tid=t.tid and t.tname='tw';

可以看到在teacher表中用到了t.tname='tw';所以这个teacher表参照的就是tw这个常量(常量用const表示),而cource表参照的是teacher表中的tid.

7.rows:就是指实际通过索引查询到的数据个数。这一个也比较好理解,不做过多说明。

8.Extral:这个就是表示一些额外的信息。它包含的值也很多,我们下面就列举项目中会碰到的,其他不常用的不做解释。

using filesort:如果Extral字段中出现了这种情况,表示你当前的SQL语句性能消耗比较大,需要优化。它表示在你当前的SQL查询语句中,除了必要的查询外,还需要一次额外的没有必要的查询。这种情况多出现在带有order by的SQL语句中。using filesort一般分两种情况来讨论:单索引复合索引

这里我们需要明白一点的是,不管排序的对象是什么(数组、集合、数据库的表等等),在进行排序前必须要先对待排序的数据进行查询,然后根据查询后结果的大小再对这些数据进行排序。说以排序其实涉及两个过程:1.查询——>2.排序。

我们结合实际例子来看一下,先创建一个test03的表,且为其中的a1、a2、a3字段构建索引,如下:

create table test03(
    a1 char(3),
    a2 char(3),
    a3 char(3),
    index a1_index(a1),index a2_index(a2),index a3_index(a3)
);

    单索引:对于单索引,如果如果查找和排序是同一个字段,则不会出现using filesort;如果查找和排序不是同一个字段则会出现using filesort。

explain select * from test03 where a1='' order by a1;  

explain select * from test03 where a1='' order by a2;  

我们先看第一个SQL语句,因为有where a1=''这个限定条件,所以这个SQL语句必须要查询a1这一列,这个查询是必要的;再看后面的order by a1; 需要对a1进行先查询后排序的操作,但对a1的查询在前面已经进行过了且由于where a1=''的限定,它是必要的,所以不会出现using filesort的情况。

再看后面的SQL语句,因为有where a1=''这个限定条件,这一句也要对a1进行排序,所以这个SQL语句必须要查询a1这一列,这个查询是必要的;但后面的这个order by a2;会对a2进行排序,在排序之前先要对a2进行查询,这个对a2的查询过程其实就是一种额外的查询操作,所以出现了using filesort的情况。

小结:对于单索引,为了避免产生using filesort,所以在实际开发中我们要尽量保持二者的一致,where后面是那些字段,那么在order by的时候也按那些字段来进行

    复合索引:不可跨列(最佳左前缀)

现在我们把上面a1_index,i a2_index, a3_index这三个单索引删了,构建一个包含(a1,a2,a3)的复合索引:

drop index a1_index on test03;
drop index a2_index on test03;
drop index a3_index on test03;
alter table test03 add index a1_a2_a3_index (a1,a2,a3);

现在看以下几个SQL的执行计划:

explain select * from test03 where a1='' order by a3;       //跨列 出现using filesort

explain select * from test03 where a2='' order by a3;       //跨列  出现using filesort

explain select * from test03 where a1='' order by a2;      //没跨列  未出现using filesort

小结:在复合索引中,为了避免产生using filesort,where和order by后的字段尽量按照复合索引的顺序来使用,不要跨列和无序使用。

using temporary:这种情况同using filesort一样,也是性能损耗比较大的一种情况,我们也要尽可能的把它优化掉。出现这种情况就表明在查询的过程中用到了“临时表”,而这种临时表对于整个SQL的查询结果其实是没有必要的一张额外表。这种情况一般出现在group by语句中(同上面using filesort常出现在order by类比)。

要很好的理解using temporary的过程,我们必须要熟悉SQL语句的解析过程。SQL语句的解析顺序:from->on->join->where->gruop by->having->select (dinstinct)->order by->limit。

继续通过案例来看一下:

explain select * from test03 where a2='' group by a2;

explain select * from test03 where a2='' group by a3;

通过上面的结果我们可以看到当where后面的字段和group by的字段一样时,不会出现using temporary,而不一样时则会出现。我们结合SQL语句的解析过程来分析一下第二种出现using temporary的情况:按照解析顺序,SQL语句中会先解析where,它的后面是a2='',所以mysql就会为a2建立一张表来存放结果,这个是必要的,等解析到group by的时候,它的后面是a3,这个时候mysql就不得不在建立一张表来存放按a3分组的结果,然后mysql最后再根据a3表来调整a2表中的结果,最后呈现出a2表的最终结果。可以看到在整个解析过程中,mysql用到了一张额外的a3临时表,所以最终结果就是using temporary。而第一种情况,因为SQL中where后面和group by后面都是字段a2,所以在按照a2进行分组的时候,直接在一张a2表上进行操作就可以,不会涉及到临时表。

小结:为了避免using temporary的情况出现,在做查询的时候,我们尽量查询那些列,就按那些列进行group by分组

using  index(索引覆盖):遇到这种情况的话,你应该感到高兴,因为这是好性能的体现。如果出现这个标识,就表明在进行SQL查询的时候,不需要读取原文件,只需要从索引文件中就能获取想要的数据,总结来说就是不需要进行回表查询。我们画个图里理解一下:

如上,假如现在有一张person表,我们为age列构建一个索引,那么在进行select age from person where age=18;这个查询的时候,因为查询只涉及到age,那么mysql就会到大小为1M的索引文件中去查找,而不会到到小为100M的原表中去查找,这样查询效率就会高出很多,也就是using index这种情况。因此,只要使用到的列全都在索引文件中,就是索引覆盖using index。

另外需要注意的一点就是当Extral为using index时,会对possible_key和key字段造成影响。

    1.如果SQL语句中没有where,则索引只出现在key中

    2,。如果SQL语句中有where,则索引在key和possible_key中都会出现

我们结合test03表在具体看一下这两种情况。看一下如下两条SQL语句的执行计划:

explain select a1 from test02;

explain select a1 from test02 where a1='';

从结果我们可以看出,符合上面的两条结论。

using where:这种情况结合着上面using index来看会更好理解。在using index中我们提到只要使用到的列全都在索引文件中,就是索引覆盖using index,而using where则表示使用到的列,在索引文件中没有全部包含,有一部分还需要到原表中查询;或者索引文件中都没有,需要全部都到原表中查询,这种情况下就会出现using where。

继续拿我们上图中的person表做例子,因为索引中现在只有age字段,那到我们进行诸如如下的查询时,就会到原表中进行查询,因为索引中只有age字段,没有name和id字段。所以以下的三个查询,它们的Extral类型都为using where。

select age,name from person where age=18 and name='zhangsan';

select name from person where name = 'zhangsan';

select id ,ame from person where id=5 and name = 'zhangsan';

impossible where:这种情况很少见到,它表示where子句永远为false,即where子句永远不能满足条件。比如如下SQL语句:

explian select * from test03 where a1='x' and a1='y';  //a1不可能满足即等于x的同时又满足等于y的情况。

现在为止,我们就把执行计划中的大部分字段都讲解完毕了,谢谢大家的耐心观看,我们一起进步!!!

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值