索引由来
性能下降SQL慢 、执行时间长 、等待时间长:
- 查询语句写的烂
- 索引失效
- 关联查询太多join(设计缺陷或不得已的需求)
- 服务器调优及各个参数设置(缓冲\线程数等)
SQL执行顺序:
常见通用的join查询:
索引简介
索引简介:
-
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高校获取数据的数据结构。 ①可以得到索引的本质:索引是数据结构。 ②索引的目的在于提高查询效率,可以类比字典, ③你可以简单理解为"排好序的快速查找数据结构" 。 -
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。下图就是一种可能的索引方式示例: ①为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引健值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快逮的检索出符合条件的记录。 -
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以文件形式存储在硬盘上。索引是物理数据页存储,在数据文件中(InnoDB,ibd文件),利用数据页(page)存储。 -
我们平时所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉树)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种类型的索引之外,还有哈希索引(hash index)等。 -
索引优势: ①类似大学图书馆建书目索引,提高数据检索效率,降低数据库的IO成本 ②通过索引列对数据进行排序,降低数据排序成本,降低了CPU的消耗 -
索引劣势: ①实际上索引也是一张表,该表保存了主键和索引字段,并指向实体表的记录,所以索引列也是要占用空间的 ②虽然索引大大提高了查询速度,同时却会降低更新表的速度,如果对表INSERT,UPDATE和DELETE。 因为更新表时,MySQL不仅要不存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。 ③索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立优秀的索引,或优化查询语句
mysql索引分类:
-
普通索引(NORMAL): ①单值索引 : MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值,纯粹为了查询数据更快一 点。 ②复合索引: 用户可以在多个列上建立索引,这种索引叫做组复合索引(组合索引),复合索引可以代替多个单一索引,相比多个单一索引,复合索引所需的开销更小 <1>窄索引:窄索引是指索引列为1-2列的索引 <2>宽索引:索引列超过2列的索引 <3>设计索引的一个重要原则就是能用窄索引不用宽索引,因为窄索引往往比组合索引更有效 <4>复合索引使用注意事项: 1、何时使用复合索引,要根据where条件建索引,注意不要过多使用索引,过多使用会对更新操作效率有很大影响 2、如果表已经建立了(col1,col2),就没有必要再单独建立(col1); 3、如果现在有(col1)索引,如果查询需要col1和col2条件,可以建立(col1,col2)复合索引,对于查询有一定提高 -
唯一索引(UNIQUE): 索引列的值必须唯一,但允许有空值。(唯一约束就是一个唯一索引 ) ①主键索引:是一种特殊的唯一索引 ,不允许有空值。(主键约束就是一个主键索引 ) -
全文索引: ①全文索引主要用来查找文本中的关键字,而不是直接与索引中的值相比较。 ②查询操作在数据量比较少时,可以使用like模糊查询,但是对于大量的文本数据检索,效率很低。如果使用全文索引,查询速度会比like快很多倍。但也很少用到。 <1>注意:在MySQL 5.6 以前的版本,只有MyISAM存储引擎支持全文索引,从MySQL 5.6开始MyISAM和InnoDB存储引擎均支持 -
空间索引: ①空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。MYSQL使用SPATIAL关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MYISAM的表中创建 -
建议: ①一张表索引不要超过5个且优先考虑复合索引 ②当索引的列名太长可以转换成前缀索引。 -
链接:索引的类型分类、区别、优缺点
MySQL创建表时建立索引和在已存在表中添加索引:
- 创建表时建立索引:
①UNIQUE | FULLTEXT | SPATIAL| NORMAL 这三个是可选项,UNIQUE 为唯一索引,FULLTEXT为全文索引,SPATIAL 为空间索引,其中SPATIAL InnoDB 存储引擎不支持,MyISAM存储引擎支持 ,Normal表示普通索引,大多数情况下都可以使用。 ②INDEX 和 KEY 为同义词,创建索引时使用它们中任何一个都可以,INDEX_NAME 为索引名称,可选参数,如果不指定,缺省(默认值)为COLUMN_NAME , ③LENGTH 为可选参数,可以指定索引的长度,需注意的是,只有字符串类型的列才能指定索引长度,ASC 或 DESC 指定升序或者降序的索引值存储。
CREATE TABLE Y1 (
COLUMN_NAME DATA_TYPE SCHEME,
[UNIQUE | FULLTEXT | SPATIAL |NORMAL] [INDEX | KEY ] INDEX_NAME([LENGTH],.....)
[ASC | DESC]
)
第一种:
ALTER TABLE TABLE_NAME ADD [UNIQUE | FULLTEXT | SPATIAL|NORMAL] [INDEX | KEY ]
INDEX_NAME(COLUMN_NAME(LENGTH),.....) [ASC | DESC]
第二种:
CREATE [UNIQUE | FULLTEXT | SPATIAL|NORMAL] [INDEX | KEY ] INDEX_NAME ON
TABLE_NAME(COLUMN_NAME(LENGTH),.....) [ASC | DESC]
索引分类
唯一索引,前缀索引,全文索引:
唯一索引: ①普通索引允许被索引的数据列包含重复的值。比如说,因为人有可能同名,所以同一个姓名在同一个“员工个人资料”数据表里可能出现两次或更多次。 ②如果能确定某个数据列将只包含彼此各不相同的值,在为这个数据列创建索引的时候就应该用关键字UNIQUE把它定义为一个唯一索引。 ③这么做的好处:一是简化了MySQL对这个索引的管理工作,这个索引也因此而变得更有效率;二是MySQL会在有新记录插入数据表时,自动检查新记录的这个字段的值是否已经在某个记录的这个字段里出现过了;如果是,MySQL将拒绝插入那条新记录。也就是说,唯一索引可以保证数据记录的唯一性。 ④事实上,在许多场合,人们创建唯一索引的目的往往不是为了提高访问速度,而只是为了避免数据出现重复。前缀索引: ①当要索引的列字符很多时,索引则会很大且变慢。(对于 BLOB、TEXT 和 VARCHAR 类型的列 ) ②因此可以只用索引列开始的部分字符串 ,节约索引空间,从而提高索引效率。 ③索引的选择性,是指不重复的索引数量除以总记录数,范围是(0,1]。 ④但前提要保证截取后的索引列与截取前选择性是一样的! ⑤链接:前缀索引详细链接全文索引: ①通过数值比较、范围过滤等就可以完成绝大多数我们需要的查询,但是,如果希望通过关键字的匹配来进行查询过滤,那么就需要基于相似度的查询,而不是原来的精确数值比较。全文索引就是为这种场景设计的。 ②你可能会说,用 like + % 就可以实现模糊匹配了,为什么还要全文索引?like + %在文本比较少时是合适的,但是对于大量的文本数据检索,是不可想象的。全文索引在大量的数据面前,能比 like + % 快 N倍,速度不是一个数量级,但是全文索引可能存在精度问题。 ③你可能没有注意过全文索引,不过至少应该对一种全文索引技术比较熟悉:各种的搜索引擎。虽然搜索引擎的索引对象是超大量的数据,并且通常其背后都不是关系型数据库,不过全文索引的基本原理是一样的。 ④使用 字符串列的索引规范中的语法,您可以创建仅使用列首字符的索引 。以这种方式仅索引列值的前缀可以使索引文件小得多。为a 或 column 编制索引时 , 必须为索引指定前缀长度。例如:col_name(N)NBLOBTEXT ⑤链接:全文索引详解
按索引底层的数据结构分类mysql索引:
- 链接:MySQL索引数据结构
- 正如上文中说到,索引是提高查询效率的数据结构,而能够提高查询效率的数据结构有很多,如二叉搜索树,红黑树,跳表,哈希表(散列表)等,而MySQL中用到了
B+Tree 和散列表(Hash表) 作为索引的底层数据结构。 Hash索引: ①需要注意的是,MySQL并没有显式支持Hash索引,而是作为内部的一种优化,对于热点的数据会自动生成Hash索引,也叫自适应Hash索引。 ②Hash索引在等值查询中,可以O(1)时间复杂度定位到数据,效率非常高,但是不支持范围查询。在许多编程语言以及数据库中都会用到这个数据结构,如Redis支持的Hash数据结构。具体结构如下: B+Tree索引: ①提到B+Tree首先不得不提B-Tree ,B-Tree(多路搜索树,并不是二叉的)是一种常见的数据结构。使用B-tree结构可以显著减少定位记录时所经历的中间过程,从而加快存取速度。 ②B+ 树是基于B-Tree升级后的一种树数据结构 ,通常用于数据库和操作系统的文件系统中。B+ 树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+ 树元素自底向上插入,这与二叉树恰好相反。 ③MySQL索引的实现也是基于这种高效的数据结构。具体数据结构如下: ④首先要声明一下,不要将B树,B-Tree以及B+Tree弄混淆 。首先,B-Tree就是B树 ,中间的“-”是一个中划线,而不是减号,并不存在"B减树"这种数据结构。其次,就是B+Tree和B-Tree实现索引时有两个区别:B+Tree只在叶子节点存储数据 ,而B-Tree的数据存储在各个节点中 。具体可见下图:
索引原理与数据结构:
- 索引涉及的理论知识:二分查找法、Hash和B+Tree,B-Tree
- 索引数据结构:MySQL数据库索引采用的是B+Tree结构,在B-Tree结构上做了优化改造
B+Tree结构: ①非叶子节点不存储data数据,只存储索引值,这样便于存储更多的索引值 ②叶子节点包含了所有的索引值和data数据 ③叶子节点用指针连接,提高区间的访问性能 ④B+树进行范围查找时,只需要查找定位两个节点的索引值,然后利用叶子节点的指针进行遍历即可 - 为什么MySQL的索引要使用B+树而不是其它树或B树?
①因为B树不管叶子节点还是非叶子节点,都会保存数据,这样导致在非叶子节点中能保存的指针数量变少(有些资料也称为扇出),这就意味着数据量相同的情况下B+树比B树更加的”矮胖“ ②指针少的情况下要保存大量数据,只能增加树的高度,导致IO操作变多,查询性能变低; - 为何不采用hash方式?
①等值查询时可以,但无法满足范围查找 ②哈希表是一种以key-value存储数据的结构,所以多个数据在存储关系上是完全没有任何顺序关系的,所以,对于区间查询是无法直接通过索引查询的,就需要全表扫描。 ③哈希索引不支持多列联合索引的最左匹配规则 如果有大量重复键值得情况下,哈希索引的效率会很低,因为存在哈希碰撞问题
用B+树查找案例:
- 先将数据记录按主键进行排序,分别存放在不同的页中(为了便于理解这里一个页中只存放3条记录,实际情况可以存放很多)
- 除了存放数据的页以外,还有存放键值+指针的页,如图中page number=3的页,该页存放键值和指向数据页的指针,这样的页由N个键值+指针组成。
- 当然它也是排好序的。这样的数据组织形式,我们称为索引组织表。
- 现在来看下要查找一条数据,如:select * from user where id=5;
- 这里id是主键,通过这棵B+树来查找,首先找到根页,其实每张表的根页位置在表空间文件中是固定的,即page number=3的页。
- 找到根页后通过二分查找法,定位到id=5的数据应该在指针P5指向的页中,那么进一步去page number=5的页中查找,同样通过二分查询法即可找到id=5的记录。
- 链接:Mysql的B数与B+树
Hash索引:
- 哈希表也为散列表,又直接寻址改进而来。在哈希的方式下,一个元素k处于h(k)中,即利用哈希函数h,根据关键字k计算出槽的位置。函数h将关键字域映射到哈希表T[0…m-1]的槽位上。
- 上图中哈希函数h有可能将两个不同的关键字映射到相同的位置,这叫做碰撞,在数据库中一般采用链接法来解决。在链接法中,将散列到同一槽位的元素放在一个链表中,如下图所示:
BTree索引和哈希索引的区别:
-
Hash索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位,不像B-Tree索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以Hash索引的查询效率要远高于B-Tree索引。 -
Hash索引仅仅能满足"=",“IN"和”<=>"查询,不能使用范围查询。哈希索引只支持等值比较查询,包括=、 IN 、<=> (注意<>和<=>是不同的操作)。 也不支持任何范围查询,例如WHERE price > 100。 由于Hash索引比较的是进行Hash运算之后的Hash值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的Hash算法处理之后的Hash值的大小关系,并不能保证和Hash运算前完全一样。 -
Hash索引无法被用来避免数据的排序操作。 由于Hash索引中存放的是经过Hash计算之后的Hash值,而且Hash值的大小关系并不一定和Hash运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算; -
Hash索引不能利用部分索引键查询。 对于组合索引,Hash索引在计算Hash值的时候是组合索引键合并后再一起计算Hash值,而不是单独计算Hash值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash索引也无法被利用。 -
Hash索引在任何时候都不能避免表扫描。 前面已经知道,Hash索引是将索引键通过Hash运算之后,将 Hash运算结果的Hash值和所对应的行指针信息存放于一个Hash表中,由于不同索引键存在相同Hash值,所以即使取满足某个Hash键值的数据的记录条数,也无法从Hash索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果。 -
Hash索引遇到大量Hash值相等的情况后性能并不一定就会比BTree索引高。 对于选择性比较低的索引键,如果创建Hash索引,那么将会存在大量记录指针信息存于同一个Hash值相关联。这样要定位某一条记录时就会非常麻烦,会浪费多次表数据的访问,而造成整体性能低下。
按数据存储方式分类Mysql索引:
- 聚簇索引(聚集索引)
①聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。 这个特性决定了索引组织表中数据也是索引的一部分,每张表只能拥有一个聚簇索引。 ②Innodb通过主键聚集数据,如果没有定义主键,innodb会选择非空的唯一索引代替。如果没有这样的索引,innodb会隐式的定义一个主键来作为聚簇索引。 ③优点: 1.数据访问更快,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快 2.聚簇索引对于主键的排序查找和范围查找速度非常快 ④缺点: 1.插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键 2.更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新。 3.二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。 - 辅助索引(非聚簇索引)
①在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找。辅助索引叶子节点存储的不再是行的物理位置,而是主键值。 通过辅助索引首先找到的是主键值,再通过主键值找到数据行的数据页,再通过数据页中的PageDirectory找到数据行。 ②Innodb辅助索引的叶子节点并不包含行记录的全部数据,叶子节点除了包含键值外,还包含了相应行数据的聚簇索引键。 ③辅助索引的存在不影响数据在聚簇索引中的组织,所以一张表可以有多个辅助索引。在innodb中有时也称辅助索引为二级索引。 - 详细链接:
聚簇索引和非聚簇索引详解 聚簇索引和非聚簇索引比较 - 案例:
①InnoDB使用的是聚簇索引,将主键组织到一棵B+树中,而行数据就储存在叶子节点上,若使用"where id = 14" 这样的条件查找主键,则按照B+树的检索算法即可查找到对应的叶节点,之后获得行数据。 ②若对Name列进行条件搜索,则需要两个步骤:第一步在辅助索引B+树中检索Name, 到达其叶子节点获取对应的主键 。第二步使用主键 在主索引B+树种再执行一次B+树检索操作,最终到达叶子节点即可获取整行数据。(重点在于通过其他键需要建立辅助索引) - 总结:
① 聚簇索引和非聚簇索引: :B+Tree的叶子节点存放主键索引值和行记录就属于聚簇索引;如果索引值和行记录分开存放就属于非聚簇索引 ②主键索引和辅助索引: :B+Tree的叶子节点存放的是主键字段值就属于主键索引;如果存放的是非主键值就属于辅助索引(二级索引)
MyISAM索引和InnoDB索引总结:
- Innobd存储引擎中的索引:
<1>Innobd中的主键索引是一种聚簇索引, <2>Innobd非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引。 - MyISAM存储引擎中,数据文件和索引文件是单独分离的在MyISAM中,无论是主键索引还是辅助索引,都是非聚簇索引,
在结构上两者没有什么区别 ,它们各自存放对应主键或辅助键,最后指向对应地址的表数据。不过,主键索引中key是唯一的,而辅助索引中key是可重复的。 - MyISAM,不支持数据库ACID事务,也不支持行级锁和外键的约束,但是会保存表的行数。
索引分析
Explain:
- 简介:
使用EXPLAIN关键字可以模拟优化器执行SQL语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是结构的性能瓶颈。 - 能干嘛:
①表的读取顺序 ②数据读取操作的操作类型 ③哪些索引可以使用 ④哪些索引被实际使用 ⑤表之间的引用 ⑥每张表有多少行被优化器查询 - 怎么玩:
① Explain+SQL语句 ② 执行计划包含的信息:
Explain各个字段解释:
- id :
选择标识符 - select_type // 表示查询的类型
- table // 输出结果集的表
- partitions // 匹配的分区
- type // 表示表的连接类型,
- possible_keys :
表示查询时,可能使用的索引 - key:
表示实际使用的索引 - key_len :
索引字段的长度 - ref // 列与索引的比较
- rows :
扫描出的行数(估算的行数) - filtered // 按表条件过滤的行百分比
- Extra :
执行情况的描述和说明
id:
- 简介:select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序。
- 三种情况:
①id相同,执行顺序由上至下 ②id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行 ③id相同不同,同时存在
select_type:
- 简介:
查询的类型,主要用于区别普通查询、联合查询、子查询等的复杂查询 SIMPLE: 简单的select查询,查询中不包含子查询或者UNIONPRIMARY: 查询中若包含任何复杂的子部分,最外层查询则被标记为SUBQUERY: 在SELECT或者WHERE列表中包含了子查询DERIVED: 在FROM列表中包含的子查询被标记为DERIVED(衍生)MySQL会递归执行这些子查询,把结果放在临时表里。UNION: 若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVEDUNION RESULT: 从UNION表获取结果的SELECT
table:
type:
- 简介:
访问类型排列。 - type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是: system > const > eq_ref > ref > fultext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index >ALL
- 一般来说,得保证查询至少达到range级别,最好能达到ref。
- 详解:
①system: 表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,这个也可以忽略不计。 ②const: 表示通过索引一次就找到了,const用于比较primary key或者unique索引 。因为只匹配一行数据,所以很快。如将主键至于where列表中,MySQL就能将该查询转换为一个常量。(用在单表查询时) ③eq_ref: 唯一性索引,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描。(用在多表查询时) const和ref_eq区别 ④ref: 非唯一索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体 ⑤range: 只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是在你的where语句中出现了between、<、>、in等的查询。这种范围扫描索引扫描比全表扫描要好,因为他只需要开始索引的某一点,而结束语另一点,不用扫描全部索引。 ⑥index: Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。(也就是说虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘中读的) ⑦all: FullTable Scan,将遍历全表以找到匹配的行
其他四个字段:
possible_keys: ①显示可能应用在这张表中的索引,一个或多个。 ②查询涉及的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。key: ①实际使用的索引。如果为null则没有使用索引。 ②查询中若使用了覆盖索引,则索引和查询的select字段重叠。key_len: ①表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好。 ②key_len显示的值为索引最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。ref: ①显示索引那一列被使用了,如果可能的话,是一个常数。那些列或常量被用于查找索引列上的值。rows: ①根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。
Extra:
- Using filesort:说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成排序操作成为“文件排序”
- Using temporary:使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序order by 和分组查询 group by
- USING index:表示相应的select操作中使用了覆盖索引(Coveing Index),避免访问了表的数据行,效率不错!如果同时出现using where,表明索引被用来执行索引键值的查找;如果没有同时出现using where,表明索引用来读取数据而非执行查找动作。
- Using where:表明使用了where过滤,表示不用读取表中所有信息,仅通过索引就可以获取所需数据,即使用列覆盖索引。
using join buffer: 使用了连接缓存impossible where: where子句的值总是false,不能用来获取任何元组select tables optimized away: 在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。distinct: 优化distinct,在找到第一匹配的元组后即停止找同样值的工作- 链接:
①MySQL执行计划extra中的using index 和 using where using index 的区别 ②MySQL中explain执行计划中额外信息字段(Extra)详解
索引延伸
回表查询与索引覆盖:
- 回表查询:
①通过索引查询主键值,然后再去聚簇索引查询记录信息。 ②也就是无法在一课索引树上查询得到所有信息的查询就是回表查询。 - 索引覆盖:
①explain的输出结果Extra字段为Using index时,能够触发索引覆盖。 ②只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。 ③实现索引覆盖最常见的方法就是:将被查询的字段,建立到组合索引 - 链接:回表查询与索引覆盖
- 回表查询案例:
MySQL常见瓶颈:
- CPU:CPU在饱和的时候一般发生在数据装入在内存或从磁盘上读取数据时候
- IO:磁盘I/O瓶颈发生在装入数据远大于内存容量时
- 服务器硬件的性能瓶颈:top,free,iostat和vmstat来查看系统的性能状态
子查询和连接查询对比:
连接查询 ①优点是可以用尽可能少的SQL进行查询。简化了应用和数据库之间的IO调用。 ②缺点是如果表设计不好,SQL写得差,会造成数据库大量的内部IO操作,特别是大量没必要的全表扫描。使用这种方式必须要么是确实要读取的数据量非常大,要么是能够通过索引等方式控制住全表扫描的数量。全表扫描在连接情况下的消耗可以说是指数性的升高的。子查询 ①缺点是应用和数据库之间的IO调用比较多,损耗了数据库的带宽。 ②优点是对原来的被驱动表来说数据是明确的,可以通过大量的索引,特别是主键索引避免全表扫描。- 总结:
①用哪种没有一定之规,要看读取的数据量、表设计结构、数据库规模、程序设计等多种因素综合考虑。
索引优化案例分析:
COUNT(*)与COUNT(1)与COUNT(字段)与COUNT(主键)对比:
全表扫表: 在数据库中,对无索引的表进行查询一般称为全表扫描。全表扫描是数据库服务器用来搜寻表的每一条记录的过程,直到所有符合给定条件的记录返回为止。 ①InnoDB的数据是保存在主键索引上,全表扫描实际上是直接扫描表的主键索引上的行记录。 ②注意:全表扫描是直接扫描主键索引上的行记录,而不是通过主键值。而索引扫描是通过对应索引上的字段扫描。 ③此外对于索引全扫描也是索引扫描,例如SELECT 字段 from 表,SELECT count(字段) FROM 表。 - 无论是 COUNT( * )还是 COUNT ( pk_col ) 都是因为有主键从而充分断定索取数据不为 NULL,这类 COUNT表达式可以用于获取当前可见的表行数。
- 如果该表只有一个主键索引,没有任何二级索引的情况下,那么COUNT(*)和COUNT(1)都是通过主键索引来统计行数的。如果该表有二级索引,则COUNT(1)和COUNT(*)都会通过占用空间最小的字段的二级索引进行统计。
- 用户层面对 InnoDB COUNT( * )的优化操作问题:扫描非空唯一键可得到表行数,但所涉及的字节数可能会少很多(在表的行长与主键、唯一键的长度相差较多时),相对的 IO代价小很多。
- 总结:
①COUNT(主键)只判断是否存在主键,其他字段是否为NULL不在乎。COUNT(字段)则判断该字段是否为NULL。 ①COUNT(*)和COUNT(1)没区别且都是通过主键索引来统计行数,COUNT(主键)也一样。 ②COUNT(字段)由于所占空间小,减少IO,因此速度会快一些,但是要保证字段为非空。 - 注意:
①使用主键索引时是根据主键字段 查询的则会使用上索引,若查询的字段只能使用主键索引且不是根据主键字段时 则会进行全表扫描。 ②分析索引时不仅要看查询的条件(where后面) 是啥还要看查询的内容(select后面) 是要啥 - 链接:
①高性能MySQL——Count(1) OR Count() ②MySQL 全表 COUNT() 查询时数据库底层是怎么执行的
为什么select * 比select 字段 效率低?
-
《阿里java开发手册(泰山版)》 中 MySQL 部分描述:4 - 1. 【强制】在表查询中,一律不要使用 *作为查询的字段列表,需要哪些字段必须明确写明: ①增加查询分析器解析成本。 ②增减字段容易与 resultMap 配置不一致。 ③无用字段增加网络 消耗,尤其是 text 类型的字段。 -
不需要的列会增加数据传输时间和网络开销 ①用“SELECT * ”数据库需要解析更多的对象、字段、权限、属性等相关内容,在 SQL 语句复杂,硬解析较多的情况下,会对数据库造成沉重的负担。 ②增大网络开销;* 有时会误带上如log、IconMD5之类的无用且大文本字段,数据传输size会几何增涨。如果DB和应用程序不在同一台机器,这种开销非常明显 ③即使 mysql 服务器和客户端是在同一台机器上,使用的协议还是 tcp,通信也是需要额外的时间。 -
对于无用的大字段,如 varchar、blob、text,会增加 io 操作 ①准确来说,长度超过 728 字节的时候,会先把超出的数据序列化到另外一个地方,因此读取这条记录会增加一次 io 操作。(MySQL InnoDB) -
失去MySQL优化器“覆盖索引”策略优化的可能性 ①SELECT * 杜绝了覆盖索引的可能性,而基于MySQL优化器的“覆盖索引”策略又是速度极快,效率极高,业界极为推荐的查询优化方式。 ②例如,有一个表为t(a,b,c,d,e,f),其中,a为主键,b列有索引。 那么,在磁盘上有两棵 B+ 树,即聚集索引和辅助索引(包括单列索引、联合索引),分别保存(a,b,c,d,e,f)和(a,b),如果查询条件中where条件可以通过b列的索引过滤掉一部分记录,查询就会先走辅助索引,如果用户只需要a列和b列的数据,直接通过辅助索引就可以知道用户查询的数据。 如果用户使用select *,获取了不需要的数据,则首先通过辅助索引过滤数据,然后再通过聚集索引获取所有的列,这就多了一次b+树查询,速度必然会慢很多。
索引什么时候失效:
- 链接:索引失效的十大杂症
- 注意:
索引是否失效判别标准是查询时只走索引,一丁点全表扫描都不行!!!! 查询条件包含or可能导致索引失效: ①若or的字段都有索引则会走。 ②若or的字段包含没有索引的,那么没有索引的字段会进行全表扫描。复合索引未用左列字段: ①在检索数据时从联合索引 的最左边开始匹配。所以当我们创建一个联合索引的时候,如(key1,key2,key3),相当于创建了(key1)、(key1,key2)和(key1,key2,key3)三个索引,这就是最左匹配原则。 like以%开头: ①因为如果以%开头也就意味着匹配字段的前面是啥都行。需要类型转换: ①因为类型转换后改变了值,索引的数据结构只能对原值做索引。 ②例如字符串不加单引号,会导致隐式转换导致索引失效。where中索引列有运算: ①因为运算后改变了值,索引的数据结构只能对原值做索引。where中索引列使用了函数: ①因为使用了函数后改变了值,索引的数据结构只能对原值做索引。索引字段上使用(!= 或者 < >,not in)时可能会导致索引失效: ①not in或者!=会导致索引失效并不是绝对的,对于数据较为均匀的场景是会失效的,但是如果业务数据严重不均的字段加了索引的话是不一定失效的,mysql自己会做判断,并不是绝对判定不使用索引。 ②比如表A性别列有男10000条女20条,当sex!=’男‘是可以使用索引的 同样的如果你sex='男’反而不会使用索引,mysql自己会选择最优的检索方式。索引字段上使用is null, is not null可能会导致索引失效: ①在MySQL中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。左连接查询或者右连接查询查询关联的字段编码格式不一样,可能导致索引失效: ①当底层编码方式不同时会失效。存储引擎不能使用索引中范围条件右边的列: ①意思是联合索引中范围之后的索引将会失效。 ②B+树排放顺序:会先按照第一层索引排序,如果第一层数据一样,则会按照第二层排序,如果第二层还是一样,则会继续按照第三层排序。 ③链接:Mysql索引底层原理以及为什么范围之后全失效mysql估计使用全表扫描要比使用索引快,则不使用索引: ①当表的索引被查询,会使用最好的索引,除非优化器使用全表扫描更有效。优化器优化成全表扫描取决与使用最好索引查出来的数据是否超过表的30%的数据。 ②不要给’性别’等增加索引。如果某个数据列里包含了均是"0/1"或“Y/N”等值,即包含着许多重复的值,就算为它建立了索引,索引效果不会太好,还可能导致全表扫描。 ③Mysql出于效率与成本考虑,估算全表扫描与使用索引,哪个执行快。这跟它的优化器有关,来看一下它的逻辑架构图:
如果MySQL表的某一列含有NULL值,那么包含该列的索引是否有效?
- 对MySQL来说,NULL是一个特殊的值,从概念上讲,NULL意味着“一个未知值”,它的处理方式与其他值有些不同。
- 比如:不能使用=,<,>这样的运算符,对NULL做算术运算的结果都是NULL,count时不会包括NULL行等,NULL比空字符串需要更多的存储空间等。
- NULL列需要增加额外空间来记录其值是否为NULL。对于MyISAM表,每一个空列额外占用一位,四舍五入到最接近的字节
- 虽然MySQL可以在含有NULL的列上使用索引,但NULL和其他数据还是有区别的,
不建议列上允许为NULL。最好设置NOT NULL,并给一个默认值,比如0和 ''空字符串等,如果是datetime类型,也可以设置系统当前时间或某个固定的特殊值,例如'1970-01-01 00:00:00'。
mysql 中!=(不等于) 到底走不走索引?
- 肯定的说该操作!= 是可以走索引的,但实际情况中都为啥都不走索引呢?首先我们要知道走索引与数据量和数据趋势(cardinality)有很大的关系,如果表本身就上百条记录,那走索引和表扫描区别不大,甚至在存在书签跳转情况下还不如表扫描更有效率,这个时候可能是不走索引的。
- 另外!=操作后获取的结果集在总结果集中占据的比例也是关键因素,
如果返回的结果集过大(大于20%),那么可能也不会走索引,而是选择更有效率的表扫描了 。
创建索引情况分析:
- 哪些情况需要创建索引:
①主键自动建立唯一索引 ②频繁作为查询的条件的字段应该创建索引 ③查询中与其他表关联的字段,外键关系建立索引 ④查询中统计或者分组字段 ⑤查询中排序的字段,排序字段若通过索引去访问将大大提高排序的速度 ⑥单间/组合索引的选择问题,who?(在高并发下倾向创建组合索引) - 哪些情况不要创建索引:
①表记录太少 ②经常增删改的表 ③数据重复且分布平均的表字段,因此应该只为经常查询和经常排序的数据列建立索引。注意:如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。 ④频繁更新的字段不适合创建索引,因为每次更新不单单是更新了记录还会更新索引,加重IO负担 ⑤Where条件里用不到的字段不创建索引
索引设计:
索引字段尽量使用数字型(简单的数据类型) ①若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了尽量不要让字段的默认值为NULL ①在MySQL中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。 ②索引不会包含有NULL值的列,只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。 ③所以我们在数据库设计时尽量不要让字段的默认值为NULL,应该指定列为NOT NULL,除非你想存储NULL。你应该用0、一个特殊的值或者一个空串代替空值。前缀索引和索引选择性 ①对串列进行索引,如果可能应该指定一个前缀长度。 ②对于BLOB、TEXT或者很长的VARCHAR类型的列,必须使用前缀索引,因为MYSQL不允许索引这些列的完整长度。 ③前缀索引是一种能使索引更小、更快的有效办法,但另一方面也有其缺点:MySQL无法使用前缀索引做order by和group by,也无法使用前缀索引做覆盖扫描。 ④一般情况下某个前缀的选择性也是足够高的,足以满足查询性能。 ⑤链接:前缀索引详解使用唯一索引 ①考虑某列中值的分布。索引的列的基数越大,索引的效果越好。 ②例如,存放出生日期的列具有不同值,很容易区分各行。而用来记录性别的列,只含有“ M” 和“F”,则对此列进行索引没有多大用处,因为不管搜索哪个值,都会得出大约一半的行。使用组合索引代替多个列索引 ①一个多列索引(组合索引)与多个列索引MySQL在解析执行上是不一样的,如果在explain中看到有索引合并(即MySQL为多个列索引合并优化),应该好好检查一下查询的表和结构是不是已经最优。注意重复/冗余的索引、不使用的索引 ①MySQL允许在相同的列上创建多个索引,无论是有意还是无意的。大多数情况下不需要使用冗余索引。 ②对于重复/冗余、不使用的索引:可以直接删除这些索引。因为这些索引需要占用物理空间,并且也会影响更新表的性能。
Mysql优化器是如何选择索引的?
- 概念:
①在索引建立之后,一条语句可能会命中多个索引,这时索引的选择就会交由优化器来选择合适的索引。 ②优化器选择索引的目的是找到一个最优的执行方案并用最小的代价去执行语句。 - 优化器选择索引的原则?
①在数据库里面,扫描行数是影响执行代价的因素之一。 ②扫描的行数越少,意味着访问磁盘数据的次数越少,消耗的 CPU 资源越少。 ③当然扫描行数并不是唯一的判断标准,优化器还会结合是否使用临时表、是否排序等因素进行综合判断。 - 优化器是如何判断扫描行数的?
①MySQL 在真正开始执行语句之前,并不能精确地知道满足这个条件的记录有多少条,而只能根据统计信息来估算记录数。 ②这个统计信息就是索引的“区分度”。 <1>显然一个索引上不同的值越多,这个索引的区分度就越好。 <2>而一个索引上不同的值的个数,我们称之为“基数”(cardinality)。 <3>也就是说这个基数越大,索引的区分度越好。 <4>可以使用 show index 方法,看到一个索引的基数。 ③在使用普通索引,因为都要回表到主键索引上查出整行数据,这个代价优化器也要算进去的。 - MySQL 是怎样得到统计信息的呢?
①使用采样统计 ②原理: <1>采样统计的时候,InnoDB 默认会选择 N 个数据页,统计这些页面上的不同值,得到一个平均值,然后乘以这个索引的页面数,就得到了这个索引的基数。 <2>而数据表是会持续更新的,索引统计信息也不会固定不变。所以,当变更的数据行数超过 1/M 的时候,会自动触发重新做一次索引统计。 ③为什么需要使用 采样统计? <1>因为把整张表取出来一行行统计,虽然可以得到精确的结果,但是代价太高了,所以只能选择“采样统计”。 ④在 MySQL 中,有两种存储索引统计的方式,可以通过设置参数 innodb_stats_persistent 的值来选择: <1>设置为 on 的时候,表示统计信息会持久化存储。这时,默认的 N 是 20,M 是 10。 <2>设置为 off 的时候,表示统计信息只存储在内存中。这时,默认的 N 是 8,M 是 16。 <3>由于是采样统计,所以不管 N 是 20 还是 8,这个基数都是很容易不准的。 - 索引选择异常的问题可以有哪几种处理方式?
①重新统计索引信息:既然是统计信息不对,那就修正。analyze table t 命令,可以用来重新统计索引信息。 ②指定使用索引:select * from table force index(index_name);
永远小表驱动大表类似嵌套循环Nested Loop:
- 优化原则:小表驱动大表,即小的数据集驱动大的数据集。
select * from A where id in (select id from B)
等价于:
for select id from B
for select * from A where A.id - B.id
- 当B表的数据集必须小于A表的数据集时,用in优于exists。
select * from A where exists (select 1 from B where B.id = A.id )
等价于
for select * from A
for select * from B where B.id = A.id
- 当A表的数据集系小于B表的数据集时,用exists优于in。
- 注意:A表与B表的ID字段应建立索引。
- EXISTS:SELECT … FROM table WwHERE EXISTS subqueryp
①该语法可以理解为:将主查询的数据,放到子查询中做条件验证,根据验证结果(TRUE或FALSE)来决定主查询的数据结果是否得以保留。 - 提示:
①EXISTS (subquery)只返回TRUE或FALSE,因此子查询中的SELECT’也可以是SELECT1l或select oK,官方说法是实际执行时会忽略SELECT洁单,因出没有区别 ②EXSTS子查询的实际执行过程可能经过了忧化而不是我们理解上的逐条对比,如果担忧效率问题,可进行实际检验以碗定是否有效率问题。 ③EXISTS子查询往往也可以用条件表达式、其他子查询或者JON来替代,何种最优需要具体问题具体分析
order by关键字优化:
- ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序
- 尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀
- 如果不在索引列上,filesort有两种算法:mysql就要启动双路排序和单路排序。
优化策略: ①Order by时select * 是一个大忌只Query需要的字段,这点非常重要。在这里的影响是: <1>当Query的字段天小总和小于max_length_for_sort_data而且排序字段不是TEXTIBLOB类型时,会用改进后的算法一—单络排序,否则用老算法——多路排序。 <2>两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次IVO,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size。 ②尝试提高sort_buffer_size: 不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的。 ③尝试提高max_length_for_sort_data: 提高这个参数,会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率.
索引与排序:
MySQL查询支持filesort和index两种方式的排序。 filesort是先把结果查出,然后在缓存或磁盘进行排序操作,效率较低 ,filesort有两种排序算法:双路排序和单路排序: ①双路排序:需要两次磁盘扫描读取,最终得到数据。第一次将排序字段读取出来,然后排序;第二次去读取其他字段数据。 ②单路排序:从磁盘查询所需的所有列数据,然后在内存排序将结果返回。如果查询数据超出缓存sort_buffer,会导致多次磁盘读取操作,并创建临时表,最后产生了多次IO,反而会增加负担。解决方案:少使用select *;增加sort_buffer_size容量和max_length_for_sort_data容量。使用index是指利用索引自动实现排序,不需另做排序操作,效率会比较高。 - 如果我们Explain分析SQL,结果中Extra属性显示Usingfilesort,表示使用了filesort排序方式,需要优化。如果Extra属性显示Usingindex时,表示覆盖索引,也表示所有操作在索引上完成,也可以使用index排序方式,建议大家尽可能采用覆盖索引
GROUP BY关键字优化:
group by实质是先排序后进行分组,遵照索引建的最佳左前缀。 - 当无法使用索引列,增大max_length_for_sort_data参数的设置+增大sort_buffer_size参数的设置
- where高于having,能写在where限定的条件就不要去having限定了。
索引总结:
- 解决like’%字符串%'索引不被使用的方法?
①可以使用主键索引 ②使用覆盖索引,查询字段必须是建立覆盖索引字段 ③当覆盖索引指向的字段是varchar(380)及380以上的字段时,覆盖索引会失效! - 优化总结口诀:
①全值匹配我最爱,最左前缀要遵守 ②带头大哥不能死,中间兄弟不能断 ③索引列上少计算,范围之后全失效 ④LIKE百分写最右,覆盖索引不写星 ⑤不等空值还有or,索引失效要少用; ⑥VAR引号不可丢,SQL高级也不难! - 一般性建议:
①对于单键索引,尽量选择针对当前query过滤性更好的索引 ②在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。 ③在选择组合索引的时候,尽量选择可以能包含当前query中的where子句中更多字段的索引 ④尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的 - 数据库中查询记录时是否每次只能使用一个索引?
①与其说是“数据库查询只能用到一个索引”,倒不是说是 和全表扫描/只使用一个索引的速度比起来,去分析两个索引二叉树更加耗费时间,所以绝大多数情况下数据库都是是用一个索引。 ②链接:mysql一次只能执行一个索引_数据库中查询记录时是否每次只能使用一个索引?
索引实际应用分析:
- 在使用索引时,若索引字段包含有order by或group by 后面的字段,会先按索引顺序排好序,再去回表根据主键查询(根据主键查询时便不用再order by或gruop by)。
- SQL的执行成本(cost)是 MySQL 优化器选择 SQL 执行计划时一个重要考量因素。当优化器认为使用索引的成本高于全表扫描的时候,优化器将会选择全表扫描,而不是使用索引。优化器选择全表扫描和索引范围扫描会有一个临界点。
①当优化器任务全表扫描成本更低的时候,就不会使用索引 ②并没有一个固定的数据量占比来决定优化器是否使用全表扫描(曾经是30%) ③Mysql查询返回结果 的响应时间,扫描的行数,返回的行数可以衡量查询开销 ④优化器在选择的时候会考虑更多的因素,如:表大小,行数量,IO块大小等
执行计划与资源消耗
MySQL 语句执行的神器-Optimizer Trace:
- Explain 命令的详细使用。但是它只能展示 SQL语句的执行计划,无法展示为什么一些其他的执行计划未被选择,比如说明明有索引,但是为什么查询时未使用索引等。
- 为此MySQL 提供了 Optimizer Trace 功能,让我们能更加详细的了解 SQL 语句执行的所有分析,优化和选择过程。
- Optimizer Trace 的基本使用:
①默认情况下,该功能是关闭的, 大家可以使用如下方式打开该功能,然后执行自己需要分析的 SQL 语句,然后再从 INFORMATION_SCHEMA 的 OPTIMIZER_TRACE中查找到该 SQL 语句执行优化的相关信息。
# 1. 打开optimizer trace功能 (默认情况下它是关闭的):
SET optimizer_trace="enabled=on";
SELECT ...; # 这里输入你自己的查询语句
SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
# 当你停止查看语句的优化过程时,把optimizer trace功能关闭
SET optimizer_trace="enabled=off";
- 这个 OPTIMIZER_TRACE 表有4个列,如下所示:
①QUERY:表示我们的查询语句。 ②TRACE:表示优化过程的JSON格式文本。(重点) ③MISSING_BYTES_BEYOND_MAX_MEM_SIZE:由于优化过程可能会输出很多,如果超过某个限制时,多余的文本将不会被显示,这个字段展示了被忽略的文本字节数。 ④INSUFFICIENT_PRIVILEGES:表示是否没有权限查看优化过程,默认值是0,只有某些特殊情况下才会是1,我们暂时不关心这个字段的值。 - TRACE 列的基本格式:
{
trace: {
steps:[
{ join_preparation: {}},<-->SQL的准备阶段,sql被格式化,对应函数J0IN: : prepare
{ join_optimization: {}},<--> SQL优化阶段,对应函数JOIN: :optimize
{ join_execution: {} }<-->SQL执行阶段,对应函数:J0IN: :exec
]
}
}
Show profiles概述:
- Show profiles是mysql提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优测量。
- 默认情况下,参数处于关闭状态,并保存最近15次的运行结果。
- 步骤:
- type:
①ALL:显示所有的开销信息 ②BLOCK IO:显示块IO相关开销 ③CONTEXT SWITCHES :上下文切换相关开销 ④CPU:显示CPU相关开销信息 ⑤ lPC:显示发送和接收相关开销信息 ⑤MEMORY:显示内存相关开销信息 ⑥PAGE FAULTS:显示页面错误相关开销信息 ⑦SOURCE:显示和Source_function,Source_file,Source_line相关的开销信息 ⑧SWAPS :显示交换次数相关开销的信息
|