Mysql索引及优化

MySQL索引原理及SQL优化

 

索引(Index)

MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。索引的创建对于MySQL的高效运行是很重要的,索引能够大大提升MySQL的检索速度。mysql

拿汉语字典的目录页(索引)打比方,咱们能够按拼音、笔画、偏旁部首等排序的目录(索引)快速查找到须要的字。sql

索引分单列索引和组合索引。单列索引,即一个索引只包含单个列,一个表能够有多个单列索引,但这不是组合索引。组合索引,即一个索引包含多个列。数据库

建立索引时,你须要确保该索引是应用在 SQL 查询语句的条件(通常做为 WHERE 子句的条件)。缓存

实际上,索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录。markdown

索引的原理

索引用于快速查找具备特定列值的行。若是没有索引,MySQL必须从第一行开始,而后读取整个表以查找相关行。表越大,成本越高。若是表中有相关列的索引,MySQL能够快速肯定要在数据文件中间寻找的位置,而无需查看全部数据。这比按顺序读取每一行要快得多。数据结构

MySQL经常使用的是B+ Tree索引,下面详细介绍。函数

b+树

b+树

如上图,是一颗b+树,浅蓝色的块咱们称之为一个磁盘块,能够看到每一个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P一、P二、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即三、五、九、十、1三、1五、2八、2九、3六、60、7五、7九、90、99。非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如1七、35并不真实存在于数据表中。post

上图中,若是要查找数据项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,显然成本很是很是高。性能

经过上面的分析,咱们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每一个磁盘块的数据项的数量是m,则有

公式

当数据量N必定的状况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,若是数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为何每一个数据项,即索引字段要尽可能的小,好比int占4字节,要比bigint8字节少一半。

当b+树的数据项是复合的数据结构,常见的就是组合索引,好比咱们给某个表添加个组合索引,包括姓名、年龄和性别三列(name,age,sex),b+数是按照从左到右的顺序来创建搜索树的,好比查询(where name=‘马云’ and age=18 and sex=1),b+树会优先比较name来肯定下一步的检索方向,若是name相同再依次比较age和sex,最后获得检索的数据;但若是咱们查询(where age=18 and sex= 1),此时索引是不生效的,由于创建搜索树的时候name就是第一个比较因子,必需要先根据name来搜索才能知道下一步去哪里查询。好比当查询(where name='张三' and sex=2)的时候,b+树能够用name来指定搜索方向,但下一个字段age的缺失,因此只能把名字等于张三的数据都找到,而后再匹配性别是2的数据了, 这个是很是重要的性质,即索引的最左匹配特性。

MySQL如何使用索引

MySQL使用索引进行这些操做:

  • WHERE快速 查找与子句匹配的行。

  • 若是在多个索引之间有选择,MySQL一般使用找到最小行数的索引。

  • 若是表具备多列索引,即组合索引,则优化程序可使用索引的任何最左前缀来查找行。例如,若是你有一个三列索引上(col1, col2, col3),你有索引的搜索功能(col1)(col1, col2)以及(col1, col2, col3)。

  • 在执行链接时从其余表中检索行。若是声明它们的类型和大小相同,MySQL能够更有效地使用列上的索引。在这种状况下, VARCHAR与 CHAR被认为是相同的,若是它们被声明为相同的大小。例如, VARCHAR(10)和 CHAR(10)大小相同,但 VARCHAR(10)与 CHAR(15)不是。

    对于非二进制字符串列之间的比较,两列应使用相同的字符集。例如,将utf8列与 latin1列进行比较会排除使用索引。

    不类似列的比较(例如,将字符串列与时间或数字列进行比较)可能会在没有转换的状况下没法直接比较值时阻止使用索引。对于给定的值,如1 在数值列,它可能比较等于在字符串列,例如任何数量的值 '1'' 1', '00001',或'01.e1'。这排除了对字符串列的任何索引的使用。

  • 查找特定索引列的值Min()或 Max()[`值key_col。这是由预处理器优化的,该预处理器检查您是否正在使用 索引以前出现的全部关键部分。在这种状况下,MySQL对每一个或 表达式执行单个键查找,并用常量替换它。

  • 对指定索引列进行排序或者分组,ORDER BY或者 GROUP BY

  • 在某些状况下,能够优化查询在不查询整行数据的状况下检索值。(为查询提供全部必要结果的索引称为 [覆盖索引])若是查询仅使用表中包含某些索引的列,则能够从索引树中检索所选值以得到更快的速度:好比

    SELECT key_part3 FROM tbl_name WHERE key_part1 = 1 

对于小型表或报表查询处理大多数或全部行的大型表的查询,索引不过重要。当查询须要访问大多数行时,顺序读取比经过索引更快。顺序读取能够最大限度地减小磁盘搜索,即便查询不须要全部行也是如此。

如何优化

  1. 主键优化

    表的主键表示您在最重要的查询中使用的列或列集。它具备关联的索引,以实现快速查询性能。查询性能受益于NOT NULL优化,由于它不能包含任何NULL值。使用InnoDB存储引擎,表数据在物理上进行组织,以根据主键或列进行超快速查找和排序。

    若是您的表很大且很重要,但没有明显的列或列集用做主键,则能够建立一个单独的列,其中包含自动增量值以用做主键。使用外键链接表时,这些惟一ID可用做指向其余表中相应行的指针。

  2. 外键优化

    若是一个表有不少列,而且您查询了许多不一样的列组合,那么将频率较低的数据拆分为每一个都有几列的单独表可能会颇有效,并经过外键将它们与主表关联起来。这样每一个小表均可以有一个主键来快速查找其数据,您可使用链接操做查询所需的列集。根据数据的分布方式,查询可能会执行较少的I / O并占用较少的高速缓存。(为了最大限度地提升性能,查询尝试从磁盘中读取尽量少的数据块)。

  3. 列索引

    最多见的索引类型涉及单个列,在数据结构中存储该列的值的副本,容许快速查找具备相应列值的行。B树数据结构可让索引快速查找特定值,一组值,或值的范围,例如where条件中=, >, BETWEENIN等。

    每一个存储引擎定义每一个表的最大索引数和最大索引长度。全部存储引擎支持每一个表至少16个索引,总索引长度至少为256个字节。

    • 索引前缀

      使用 字符串列的索引规范中的语法,能够建立仅使用列的前几个字符的索引 。以这种方式仅索引列值的前缀可使索引文件更小。索引 或 列时, 必须为索引指定前缀长度。

      若是搜索项超过索引前缀长度,则索引用于排除不匹配的行,并检查剩余的行以查找可能的匹配项。

    • FULLTEXT索引

      FULLTEXT索引用于全文搜索。只有InnoDB和 MyISAM存储引擎支持 FULLTEXT索引和仅适用于CHAR,VARCHAR和TEXT类型的列。索引始终发生在整个列上,而且不支持列前缀索引。

    • 空间索引(Spatial Index)

      您能够在空间数据类型上建立索引。 MyISAM和InnoDB 支持空间类型的R树索引。其余存储引擎使用B树来索引空间类型(除了 ARCHIVE)。

  4. 多列索引

    MySQL能够建立复合索引(即多列索引)。索引最多可包含16列。对于某些数据类型,您能够索引列的前缀。

    MySQL能够对测试索引中全部列的查询使用多列索引,或者只测试第一列,前两列,前三列等的查询。若是在索引定义中以正确的顺序指定列,则单个复合索引能够加速同一表上的多种查询。

    假设一个表具备如下规范:

    CREATE TABLE test ( id INT NOT NULL, last_name CHAR(30) NOT NULL, first_name CHAR(30) NOT NULL, PRIMARY KEY (id), INDEX name (last_name,first_name) );

    在last_namefirst_name列建立了一个组合索引,它既能够查询last_namefirst_name`组合的值,也能够仅查询last_name,由于该列是索引的最左前缀。所以,下面这些查询是能够用到该索引的:

    //只查询last_name
    SELECT * FROM test WHERE last_name='Jones'; //同时查 SELECT * FROM test WHERE last_name='Jones' AND first_name='John'; SELECT * FROM test WHERE last_name='Jones' AND (first_name='John' OR first_name='Jon'); SELECT * FROM test WHERE last_name='Jones' AND first_name >='M' AND first_name < 'N';

    可是,该索引 不能用于如下查询中的查找:

    SELECT * FROM test WHERE first_name='John'; SELECT * FROM test WHERE last_name='Jones' OR first_name='John';

    假设您写了如何SQL语句:

    SELECT * FROM tbl_name WHERE col1=val1 AND col2=val2;

    若是col1和col2存在组合索引,那么能够直接获取相应的行。若是col1和col2每列都存在单列索引,那么MySQL会优化合并索引,或者尝试经过肯定哪一个索引会排除更多的行来查找限制性最强的索引。

    若是表具备多列索引,则优化程序可使用索引的最左前缀来查找行。例如,若是你有一个三列索引上(col1, col2, col3),你有索引的搜索功能 (col1)(col1, col2)以及 (col1, col2, col3)

    若是SQL语句不适用索引的最左前缀,则MySQL没法使用索引执行查找。例如如下查询语句:

    //使用索引
    SELECT * FROM tbl_name WHERE col1=val1; //使用索引 SELECT * FROM tbl_name WHERE col1=val1 AND col2=val2; //不使用索引 SELECT * FROM tbl_name WHERE col2=val2; //不使用索引 SELECT * FROM tbl_name WHERE col2=val2 AND col3=val3;

    若是存在索引(col1, col2, col3),则只有前两个查询使用索引。第三和第四个查询确实包括索引的列,但不使用索引来进行查找,由于(col2)和 (col2, col3)不是的最左边的前缀 (col1, col2, col3)

索引虽好,不可滥用

尽管为查询中使用的每一个可能列建立索引颇有诱惑力,但没必要要的索引会浪费空间并浪费时间让MySQL肯定要使用哪些索引。索引还会增长插入,更新和删除的成本,由于必须更新每一个索引。您必须找到适当的平衡,以使用最佳索引集实现快速查询。

如何验证索引使用状况?

咱们建立了索引,可是咱们如何肯定mysql使用了索引? 答案是 使用explain语句。

下面会详细介绍Explain,以及如何优化SQL。

SQL优化

explain查询执行计划

举个例子,最基础的主键查询

EXPLAIN SELECT * FROM `subject` WHERE id = 1

执行结果以下:

再举个关联查询的例子

EXPLAIN SELECT a.* FROM `subject` a LEFT JOIN subject_role_0 b ON a.id = b.subject_id WHERE a.id < 3

执行结果以下:

属性 说明
id 查询的序列号
select_type 查询的类型
table 输出结果集的表
rows 扫描的行数
type 链接类型,all表示采用全表扫描的方式。
possible_keys 可能使用的索引
key 实际使用的索引
key_len 索引字段的长度
ref 列与索引的比较
Extra 额外信息,好比使用了where语句,使用了join buffer等

id

id是sql执行顺序的标识,按id从大到小的顺序执行,在id相同时,执行顺序是由上至下

select_type

select 查询的类型,主要是用于区别普通查询,联合查询,嵌套的复杂查询

类型 说明
simple 简单的select 查询,查询中不包含子查询或者union
primary 查询中若包含任何复杂的子查询,最外层查询则被标记为primary
subquery 在select或where 列表中包含了子查询
derived 在from列表中包含的子查询被标记为derived(衍生)MySQL会递归执行这些子查询,把结果放在临时表里。
union 若第二个select出如今union以后,则被标记为union,若union包含在from子句的子查询中,外层select将被标记为:derived
union result 从union表获取结果的select

table

查询的数据库的表的名称,若是没有给表指定别名,那么table值为表的名称;不然table值为你指定的别名

type

表示MySQL在表中找到所需行的方式,这是一个很是重要的参数,常见的有:all , index , range , ref , eq_ref , const , system , null 八个级别。

经常使用的类型有: all、index、range、 ref、eq_ref、const、system、null(从左到右,性能从差到好)

类型 说明
all 全表扫描找到匹配的行,性能最差
index 全索引扫描,从索引树找数据,比all性能好
range 只扫描指定范围的行,使用索引来匹配行,常见使用between,in,>,<等关键字
ref 非惟一性索引扫描,本质上也是一种索引访问,返回全部匹配某个单独值的行。
eq_ref 惟一性索引扫描,对于每一个索引键,表中有一条记录与之匹配。
const 表示经过索引一次就能够找到,const用于比较primary key 或者unique索引。由于只匹配一行数据,因此很快
system 表只有一条记录(等于系统表),这是const类型的特列,平时不会出现
null MySQL在优化过程当中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值能够经过单独索引查找完成。

possible_keys

指出MySQL能使用哪一个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不必定被查询使用(该查询能够利用的索引,若是没有任何索引显示 null)

key

key列显示MySQL实际决定使用的索引,必然包含在possible_keys中

key_len

显示索引中使用的字节数,可经过key_len计算查询中使用的索引长度。在不损失精确性的状况下索引长度越短越好。key_len 显示的值为索引字段的最可能长度,并不是实际使用长度,即key_len是根据表定义计算而得,并非经过表内检索出的。

ref

显示索引的哪一列或常量被用于查找索引列上的值。

rows

很重要的一个参数,根据表统计信息及索引选用状况,大体估算出找到所需的记录所须要读取的行数,值越大说明扫描的行数越多,性能越差

Extra

该列包含MySQL解决查询的详细信息,有如下几种状况:

说明
Using where 不用读取表中全部信息,仅经过索引就能够获取所需数据
Using temporary 表示须要使用临时表来存储结果集,常见于排序和分组查询,如group by ,order by
Using filesort 当查询中包含 order by 操做,且没法利用索引完成的排序操做称为“文件排序”
Using join buffer 在获取链接条件时没有使用索引,而且须要链接缓冲区来存储中间结果。若是出现了这个值,那应该注意,根据查询的具体状况可能须要添加索引来改进能
Impossible where 强调了where语句会致使没有符合条件的行
Select tables optimized away 这个值意味着仅经过使用索引,优化器可能仅从聚合函数结果中返回一行
No tables used Query语句中使用from dual 或不含任何from子句

优化数据库结构

优化数据大小

以最小占用磁盘空间来设计表,这样能够减小磁盘写入和读取来实现性能的提高。较小的表一般须要较少的主内存,同时索引也比较小,便于更快的处理。

经过使用此处列出的技术,您能够得到更好的表性能并最大限度地减小存储空间:

表列

  • 尽量使用最有效(最小)的数据类型。MySQL有许多专门的类型能够节省磁盘空间和内存。例如,若是可能,请使用较小的整数类型。mediumint一般是一个更好的选择,它比int使用的列空间减小25%。
  • 尽可能使用NOT NULL列,它经过更好地使用索引并避免测试每一个值是否为NULL来获取更快的速度。

索引

  • 表的主索引应尽量短。这使得每行的识别变得简单而有效。
  • 仅建立提升查询性能所需的索引。索引适用于检索,但会下降插入和更新操做的速度。若是您主要经过搜索列的组合来访问表,请在它们上建立单个复合索引,而不是为每列建立单独的索引。索引的第一部分应该是最经常使用的列。若是从表中选择时老是使用多列,则索引中的第一列应该是具备最多重复的列,以得到更好的索引压缩。
  • 若是长字符串列极可能在第一个字符数上有惟一的前缀,那么最好只使用MySQL支持在列的最左边部分建立索引来索引此前缀,较短的索引更快,不只由于它们须要更少的磁盘空间,并且由于它们还会在索引缓存中为您提供更多的命中,从而减小磁盘搜索次数。

Join

  • 在某些状况下,分红两个常常扫描的表多是有益的,若是它是动态格式表,则尤为如此,而且可使用较小的静态格式表,该表可用于在扫描表时查找相关行。
  • 在具备相同数据类型的不一样表中声明具备相同信息的列,能够加速链接。
  • 保持列名简单,以便您能够在不一样的表中使用相同的名称并简化链接查询。例如表customer,使用列名name而不是customer_name。

正常化

  • 一般,尽可能保持全部数据不冗余(第三范式),尽可能经过引用join子句中的ID来链接查询中的表。
  • 若是速度比磁盘空间更重要,而且保留多个数据副本,那么能够建立汇总表到得到更快的速度。

优化数据类型

对于惟一的id,首选使用数字列,而不是字符串,这是由于数字比字符串占用更少的字节,传输和比较速度更快,占用的内存更少。

优化字符和字符串类型

对于字符和字符串列,请遵循如下准则:

  • 比较来自不一样列的值时,请尽量声明具备相同字符集和排序规则的列,以免在运行查询时进行字符串转换。
  • 对于小于8KB的列值,请使用binary VARCHAR而不是 BLOB
  • 若是表包含字符串列(如名称和地址),但许多查询不检索这些列,请考虑将字符串列拆分为单独的表,并在必要时使用带有外键的链接查询。能够减小了常见查询的磁盘I / O和内存使用。

优化BLOB类型

  • 存储包含文本数据的大blob时,请考虑先压缩它。
  • 对于具备多个列的表,要减小使用BLOB列的查询,请考虑将BLOB列拆分为单独的表,并在须要时使用链接查询引用它。
相关文章
相关标签/搜索