MySQL高级---第一部分【索引优化】

1.MYSQL配置文件

1.1二进制日志log-bin  

主从复制及备份恢复(了解),(默认关闭)

1.2错误日志log-error

默认是关闭的,记录严重的警告和错误信息,每次启动和关闭的详细信息等。

1.3 查询日志

默认关闭,记录查询的sql语句,如果开启会境地mysql性能,因为记录日志也需要消耗资源

1.4数据文件

1.4.1 两系统:

windows:....\MySQLServer5.5\data目录下很多数据库文件

linux:默认路径:/var/lib/mysql。可在配置文件中更改 /usr/share/mysql/  下的 my-huge.cnf。每个目录代表一个同名的库

1.4.2 相关数据文件

  1. frm文件(framework):存放表结构
  2. myd文件(data):存放表数据
  3. myi文件(index):存放表索引

1.4.3 如何配置

windows:my.ini文件

linux:/etc/my.cnf文件

1.5 Mysql逻辑架构介绍 

1.5.1 总体概览

      和其它数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。

MySQL高级---第一部分【索引优化】_第1张图片

 1.连接层

 最上层是一些客户端和连接服务,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于tcp/ip的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。

2.服务层

2.1  Management Serveices & Utilities: 系统管理和控制工具  
2.2  SQL Interface: SQL接口
      接受用户的SQL命令,并且返回用户需要查询的结果。比如select from就是调用SQL Interface
2.3 Parser: 解析器
       SQL命令传递到解析器的时候会被解析器验证和解析。 
2.4 Optimizer: 查询优化器。
     SQL语句在查询之前会使用查询优化器对查询进行优化。 
     用一个例子就可以理解: select uid,name from user where  gender= 1;
     优化器来决定先投影还是先过滤。
2.5 Cache和Buffer: 查询缓存。
      如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。
      这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等
       缓存是负责读,缓冲负责写。

3.引擎层

  存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。后面介绍MyISAM和InnoDB。

4.存储层

 数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。

1.5.2 查询流程图:

 MySQL高级---第一部分【索引优化】_第2张图片

首先,mysql的查询流程大致是:

  • mysql客户端通过协议与mysql服务器建连接,发送查询语句,先检查查询缓存,如果命中(一模一样的sql才能命中),直接返回结果,否则进行语句解析,也就是说,在解析查询之前,服务器会先访问查询缓存(query cache)——它存储SELECT语句以及相应的查询结果集。如果某个查询结果已经位于缓存中,服务器就不会再对查询进行解析、优化、以及执行。它仅仅将缓存中的结果返回给用户即可,这将大大提高系统的性能。
  • 语法解析器和预处理:首先mysql通过关键字将SQL语句进行解析,并生成一颗对应的“解析树”。mysql解析器将使用mysql语法规则验证和解析查询;预处理器则根据一些mysql规则进一步检查解析数是否合法。
  • 查询优化器当解析树被认为是合法的了,并且由优化器将其转化成执行计划。一条查询可以有很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。。
  • 然后,mysql默认使用的BTREE索引,并且一个大致方向是:无论怎么折腾sql,至少在目前来说,mysql最多只用到表中的一个索引。

2. mysql存储引擎

 2.1查看存储引擎

  • 看你的mysql现在已提供什么存储引擎::show engines;

MySQL高级---第一部分【索引优化】_第3张图片

  • 看你的mysql当前默认的存储引擎::show variables like '%storage_engine%';

MySQL高级---第一部分【索引优化】_第4张图片

2.2 两种存储引擎的对比

MySQL高级---第一部分【索引优化】_第5张图片

 innodb 索引 使用 B+TREE myisam 索引使用 b-tree
innodb 主键为聚簇索引,基于聚簇索引的增删改查效率非常高。 

3.索引优化分析

3.1 性能下降sql慢,执行时间长,等待时间长

查询语句写得烂、索引失效、关联查询太多join、服务器调优及各个参数设置(缓冲、线程数等)

3.2 常见的join查询

3.2.1 sql执行顺序

机器从from开始执行

MySQL高级---第一部分【索引优化】_第6张图片

3.2.2 七种join

3.2.2.1 图示

MySQL高级---第一部分【索引优化】_第7张图片

3.2.2.2代码实例

(1)内连接

MySQL高级---第一部分【索引优化】_第8张图片

(2).左连接

MySQL高级---第一部分【索引优化】_第9张图片

(3)右连接

MySQL高级---第一部分【索引优化】_第10张图片

(4)左外连接

MySQL高级---第一部分【索引优化】_第11张图片

(5)右外连接

MySQL高级---第一部分【索引优化】_第12张图片

(6)全连接

MySQL高级---第一部分【索引优化】_第13张图片

(7)全外连接

MySQL高级---第一部分【索引优化】_第14张图片

3.3 索引

3.3.1简介

1. 是什么:、

       mysql官方对索引的定义为:index是帮助nysql高效获取数据的数据结构

       本质:是一种数据结构,排序好的快速查找数据结构

       在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,
这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。下图就是一种可能的索引方式示例:

MySQL高级---第一部分【索引优化】_第15张图片

                              左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址
        为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。

       二叉树弊端之一:二叉树很可能会发生两边不平衡的情况。
B-TREE: (B:balance)  会自动根据两边的情况自动调节,使两端无限趋近于平衡状态。可以使性能最稳定。(myisam使用的方式)
        B-TREE弊端:(插入/修改操作多时,B-TREE会不断调整平衡,消耗性能)从侧面说明了索引不是越多越好。
B+TREE:Innodb 所使用的索引

        结论:数据本身之外,数据库还维护者一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引。

       索引往往以索引文件的形式存储的磁盘上。

       我们平常所说的索引,如果没有特别的说明,都是指B树(多路搜索树,并不一定是二叉树)结构组织的索引。其中聚集索引,次要索引,覆盖索引,符合索引等都是B+树索引。除了B+树索引之外,还有哈希索引。

2.优势

     提高查询数据的效率,降低数据库的IO成本

      通过索引对数据进行排序,降低数据排序的成本,降低cpu的消耗。

3.劣势

      索引列也是要占用空间的

     虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息

3.3.2 索引分类

单值索引:既一个索引只包含单个列,一个表可以有多个单值索引

唯一索引:索引列的值必须唯一,但是允许有空值

复合索引:既一个索引包含多个单列

主键索引:设定为主键后数据库会自动建立索引,innodb为聚簇索引

创建:

create [unique] index index_name on mytable(colName(length));(char和varchar可不用指定长度,boolean和text需要指定长度)

alter mytable add [unique] index [indexName] on (colName(length));

删除:drop index [indexName] on mytable;

查看:show index from tableName;

使用alter命令:

有四种方式来添加数据表的索引:
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list): 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
 
ALTER TABLE tbl_name ADD UNIQUE index_name (column_list): 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。
 
ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引,索引值可出现多次。
 
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT ,用于全文索引。

 

3.3.3 mysql索引结构

Btree索引、hash索引、full-text索引、R-Tree索引

B树索引原理图:

MySQL高级---第一部分【索引优化】_第16张图片

【初始化介绍】 
 一颗b树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),
如磁盘块1包含数据项17和35,包含指针P1、P2、P3,
P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。
真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。
非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
 
【查找过程】
如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。
 
真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。

B+树和B-树

 MySQL高级---第一部分【索引优化】_第17张图片

B+TREE 第二级的 数据并不能直接取出来,只作索引使用。在内存有限的情况下,查询效率高于 B-TREE
B-TREE 第二级可以直接取出来,树形结构比较重,在内存无限大的时候有优势。

B+Tree与B-Tree 的区别:结论在内存有限的情况下,B+TREE 永远比 B-TREE好。无限内存则后者方便
 
 1)B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+树叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。(一次查询可能进行两次i/o操作)
  2)在B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看B-树的性能好像要比B+树好,而在实际应用中却是B+树的性能要好些。因为B+树的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比B-树多,树高比B-树小,这样带来的好处是减少磁盘访问次数。尽管B+树找到一个记录所需的比较次数要比B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中B+树的性能可能还会好些,而且B+树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有文件,一个表中的所有记录等),这也是很多数据库和文件系统使用B+树的缘故。 
 
思考:为什么说B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引? 
1) B+树的磁盘读写代价更低 
  B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B- 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。 
2) B+树的查询效率更加稳定 
  由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

聚簇索引与非聚簇索引:

     聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。
     术语‘聚簇’表示数据行和相邻的键值紧促的存储在一起。
     如下图,左侧的索引就是聚簇索引,因为数据行在磁盘的排列和索引排序保持一致

MySQL高级---第一部分【索引优化】_第18张图片

 聚簇索引的好处:

  • 按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多个数据块中提取数据,所以节省了大量的io操作。

聚簇索引的限制:

  • 对于mysql数据库目前只有innodb数据引擎支持聚簇索引,而Myisam并不支持聚簇索引。
  • 由于数据物理存储排序方式只能有一种,所以每个Mysql的表只能有一个聚簇索引。一般情况下就是该表的主键。
  • 为了充分利用聚簇索引的聚簇的特性,所以innodb表的主键列尽量选用有序的顺序id,而不建议用无序的id,比如uuid这种。(参考聚簇索引的好处。)

 这里说明了主键索引为何采用自增的方式:1、业务需求,有序。2、能使用到聚簇索引

3.3.4 那些情况需要创建索引

1.主键自动建立唯一索引

2.频繁作为查询条件的字段应该建立索引

3.查询中与其他表关联的字段,外键关系建立索引

4.频繁更新的字段不适合建立索引

5.where条件里面用不到的字段不创建索引

6.创建复合索引

7. 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度

8.查询中统计或者分组字段

3.3.4 那些情况不需要创建索引

1.表记录太少

2.经常增删改的表

Why:提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。
因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件

3.包含很多重复的字段

4.Where条件里用不到的字段不创建索引

3.4 性能分析

1. mysql常见瓶颈

CPU:CPU饱和的时候一般发生在数据存入内存或从磁盘读取数据

IO:磁盘I/O瓶颈发生在装入数据远大于内存容量的时候

服务器硬件的性能瓶颈:top,free, iostat和vmstat来查看系统的性能状态

2.Explain

  2.1 是什么(查看执行计划)?

       使用explain关键字可以模拟优化器执行sql查询语句,从而知道mysql是如何处理你的sql语句的,分析你查询语句或是表结构的性能瓶颈。

   2.2 能干吗

  • 表的读取顺序
  • 哪些索引可以使用
  • 数据读取操作的操作类型
  • 哪些索引被实际使用
  • 表之间的引用
  • 每张表有多少行被优化器查询

     2.3 怎么玩?

         Explain+sql语句

         MySQL高级---第一部分【索引优化】_第19张图片

       2.4 各个字段解释

id:         

select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序。

三种情况:

  • id相同,执行顺序由上至下

      MySQL高级---第一部分【索引优化】_第20张图片

          id相同,执行顺序由上至下  
          此例中 先执行where 后的第一条语句 t1.id = t2.id 通过 t1.id 关联 t2.id 。 而  t2.id 的结果建立在 t2.id=t3.id 的基础之上。

  • id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行

     MySQL高级---第一部分【索引优化】_第21张图片

      id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行

  •  id相同不同,同时存在

   MySQL高级---第一部分【索引优化】_第22张图片

    id如果相同,可以认为是一组,从上往下顺序执行;
    在所有组中,id值越大,优先级越高,越先执行
    衍生表 = derived2 --> derived + 2 (2 表示由 id =2 的查询衍生出来的表。type 肯定是 all ,因为衍生的表没有建立索引)
 

select_type:

有哪些: 

MySQL高级---第一部分【索引优化】_第23张图片

查询的类型,主要是用于区别 普通查询、联合查询、子查询等的复杂查询

  • SIMPLE

    简单的 select 查询,查询中不包含子查询或者UNION

   MySQL高级---第一部分【索引优化】_第24张图片

  • PRIMARY

   查询中若包含任何复杂的子部分,最外层查询则被标记为Primary 

   MySQL高级---第一部分【索引优化】_第25张图片

  • DERIVED

     在FROM列表中包含的子查询被标记为DERIVED(衍生),MySQL会递归执行这些子查询, 把结果放在临时表里。

     MySQL高级---第一部分【索引优化】_第26张图片

  • SUBQUERY 

     在SELECT或WHERE列表中包含了子查询 

     MySQL高级---第一部分【索引优化】_第27张图片

  • DEPENDENT SUBQUERY 

    在SELECT或WHERE列表中包含了子查询,子查询基于外层

  MySQL高级---第一部分【索引优化】_第28张图片

     dependent subquery 与 subquery 的区别

     依赖子查询 :

     dependent subquery :子查询结果为 多值
     subquery :子查询:查询结果为 单值 

  • UNION

     若第二个SELECT出现在UNION之后,则被标记为UNION;
     若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED

   MySQL高级---第一部分【索引优化】_第29张图片

    从UNION表获取结果的SELECT

    MySQL高级---第一部分【索引优化】_第30张图片

  • UNION RESULT 

table:

显示这一行的数据是关于哪张表的

type:

     

type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是: 
 
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range(尽量保证) > index > ALL 

一般来说,得保证查询至少达到range级别,最好能达到ref。

显示查询使用了何种类型,
从最好到最差依次是:
system>const>eq_ref>ref>range>index>AL

  • system

     表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个也可以忽略不计

  • const

    表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快
    如将主键置于where列表中,MySQL就能将该查询转换为一个常量

   MySQL高级---第一部分【索引优化】_第31张图片

  • eq_ref

   唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描

   MySQL高级---第一部分【索引优化】_第32张图片

  • ref

    非唯一性索引扫描,返回匹配某个单独值的所有行.本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会      找到多个符合条件的行,所以他应该属于查找和扫描的混合体

    MySQL高级---第一部分【索引优化】_第33张图片

    MySQL高级---第一部分【索引优化】_第34张图片

  • range

      只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引,一般就是在你的where语句中出现了between、<、          >、  in等的查询。这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部        索引。

     MySQL高级---第一部分【索引优化】_第35张图片

     MySQL高级---第一部分【索引优化】_第36张图片

  • index

    Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。(也就是说虽      然all和Index都是读全表,但index是从索引中读取的,而all是从硬盘中读的)。

   MySQL高级---第一部分【索引优化】_第37张图片

  • all

   Full Table Scan,将遍历全表以找到匹配的行

   MySQL高级---第一部分【索引优化】_第38张图片

possible_keys:

显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用

key:

实际使用的索引。如果为NULL,则没有使用索引

查询中若使用了覆盖索引,则该索引和查询的select字段重叠

对比下图两个 sql 语句。和 key 的值:当查询具体某一字段时,且那个字段有索引时,key 值会显示为索引。 
MySQL高级---第一部分【索引优化】_第39张图片

key_len:

表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。 在不损失精确性的情况下,越短越好。

MySQL高级---第一部分【索引优化】_第40张图片

key_len字段显示的值为索引字段的最大可能长度,并非实际长度,既key_len是根据表定义算出的,不是通过表内检索出的。

ref:

显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值

MySQL高级---第一部分【索引优化】_第41张图片

rows:

rows列显示MySQL认为它执行查询时必须检查的行数。越少越好

MySQL高级---第一部分【索引优化】_第42张图片

Extra:

包含不适合在其他列中显示但十分重要的额外信息

  • Using filesort 

   说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称        为“文件排序”

MySQL高级---第一部分【索引优化】_第43张图片

  优化后,不再出现filesort的情况:(给 ename 加上了索引)

MySQL高级---第一部分【索引优化】_第44张图片

   查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
   分情况:当通过前面的查询语句 筛选大部分条件后,只剩下很少的数据。using filesort 性能影响不大。需要综合考虑

  •  Using temporary   

   使了用临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。 

   优化前存在 using  temporary 和 using  filesort

MySQL高级---第一部分【索引优化】_第45张图片

  建立索引?  在 group by 的情况下发生的。
  create index idx_deptno_ename on emp(deptno,ename) 后解决
  优化前存在的 using  temporary 和 using  filesort 不在,性能发生明显变化:

MySQL高级---第一部分【索引优化】_第46张图片

  • USING index

表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!
如果同时出现using where,表明索引被用来执行索引键值的查找;
如果没有同时出现using where,表明索引只是用来读取数据而非利用索引执行查找。

覆盖索引(Covering Index)

索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。①一个索引 ②包含了(或覆盖了)[select子句]与查询条件[Where子句]中 ③所有需要的字段就叫做覆盖索引。

上句红字理解:

  select id , name from t_xxx where age=18;
 有一个组合索引  idx_id_name_age_xxx 包含了(覆盖了),id,name,age三个字段。查询时直接将建立了索引的列读取出来   了,而不需要去查找所在行的其他数据。所以很高效。
 注意:
  如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select *,
  因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。

  • Using where

  表明使用了where过滤

  • using join buffer

   使用了连接缓存

MySQL高级---第一部分【索引优化】_第47张图片

出现在当两个连接时
驱动表(被连接的表,left join 左边的表。inner join 中数据少的表) 没有索引的情况下。
给驱动表建立索引可解决此问题。且 type 将改变成 ref

  • impossible where

  where子句的值总是false,不能用来获取任何元组

MySQL高级---第一部分【索引优化】_第48张图片

  • select tables optimized away

在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。

  • distinct

在找到第一匹配的元组既停止找同样值的操作

 

你可能感兴趣的:(mysql)