mysql 索引
1.索引介绍
索引按数据结构分可分为哈希表,有序数组,搜索树,跳表:
哈希表适用于只有等值查询的场景
有序数组适用于有等值查询和范围查询的场景,但有序数组索引的更新代价很大,所以最好用于静态数据表
搜索树的搜索效率稳定,不会出现大幅波动,而且基于索引的顺序扫描时,也可以利用双向指针快速左右移动,效率非常高
跳表可以理解为优化的哈希索引
innodb 使用了 B+ 树索引模型,而且是多叉树。虽然二叉树是索引效率最高的,但是索引需要写入磁盘,如果使用二叉树磁盘 io 会变得很频繁。在 innodb 索引中分为主键索引(聚簇索引也叫聚集索引)和非主键索引(二级索引也叫辅助索引)。主键索引保存了该行数据的全部信息,二级索引保存了该行数据的主键;所以使用二级索引的时候会先查出主键值,然后回表查询出数据,而使用主键索引则不需要回表。
对二级索引而言可使用覆盖索引来优化 sql,看下面两条 sql
select * from table where key=1;
select id from table where key=1;
key 是一个二级索引,第一条 sql 是先查询出 id ,然后根据 id 回表查询出真正的数据。而第二条查询索引后直接返回数据不需要回表。第二条 sql 索引 key 覆盖了我们的查询需求,称作覆盖索引
2. 普通索引和唯一索引
innoDB 是按数据页来读写数据的,当要读取一条数据的时候是先将本页数据全部读入内存,然后找到对应数据,而不是直接读取,每页数据的默认大小为 16KB。
当一个数据页需要更新的时候,如果内存中有该数据页就直接更新,如果没有该数据页则在不影响数据一致性的前提下将;更新操作先缓存到 change buffer
中,在下次查询需要访问这个数据页的时候再写入更新操作除了查询会将 change buffer
写入磁盘,后台线程线程也会定期将 change buffer
写入到磁盘中。对于唯一索引来说所有的更新操作都要先判断这个操作是否会违反唯一性约束,因此唯一索引的更新无法使用 change buffer
而普通索引可以,唯一索引更新比普通索引更新多一个唯一性校验的过程。
3.联合索引
两个或更多个列上的索引被称作联合索引(复合索引)。联合索引可减少索引开销,以联合索引 (a,b,c) 为例,建立这样的索引相当于建立了索引 a、ab、abc 三个索引—— Mysql 从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分,而且当最左侧字段是常量引用时,索引就十分有效,这就是最左前缀原则。由最左前缀原则可知,组合索引是有顺序的,那么哪个索引放在前面就比较有讲究了。对于组合索引还有一个知识点——索引下推,假设有组合索引(a,b,c)有如下 sql:
selet * from table where a=xxx and b=xxx
这个 sql 会进行两次筛选第一次查出 a=xxx
数据 再从 a=xxx
中查出 b=xxx
的数据。使用索引下推和不使用索引下推的区别在于不使用索引下推会先查出 a=xxx
数据的主键然后根据查询出的主键回表查询出全行数据,再在全行数据上查出 b=xxx
的数据;而索引下推的执行过程是先查出 a=xxx
数据的主键,然后在这些主键上二次查询 b=xxx
的主键,然后回表。
索引下推的特点:
innodb 引擎的表,索引下推只能用于二级索引
索引下推一般可用于所查询字段不全是联合索引的字段,查询条件为多条件查询且查询条件子句字段全是联合索引。
4 优化器与索引
在索引建立之后,一条语句可能会命中多个索引,这时,就会交由优化器来选择合适的索引。优化器选择索引的目的,是找到一个最优的执行方案,并用最小的代价去执行语句。那么优化器是怎么去确定索引的呢?优化器会优先选择扫描行数最少的索引,同时还会结合是否使用临时表、是否排序等因素进行综合判断。MySQL 在开始执行 sql 之前,并不知道满足这个条件的记录有多少条,而只能根据 mysql 的统计信息来估计,而统计信息是通过数据采样得出来的。
5 其他索引知识点
有时候需要索引很长的字符列,这会让索引变得很大很慢还占内存。通常可以以开始的部分字符作为索引,这就是前缀索引。这样可以大大节约索引空间,从而提高索引效率,但这样也会降低索引的选择性。
脏页对数据库的影响:
当内存数据页和磁盘的数据不一致的时候我们称这个内存页为脏页,内存数据写入磁盘后数据一致,称为干净页。当要读入数据而数据库没有内存的时候,这个时候需要淘汰内存中的数据页——干净页可以直接淘汰掉,而脏页需要先刷入磁盘再淘汰。如果一个查询要淘汰的脏页太多会导致查询的时间变长。为了减少脏页对数据库性能影响,innodb 会控制脏页的比例和脏页刷新时机。
索引
MySQL索引使用的数据结构主要有BTree索引 和 哈希索引 。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。
MySQL的BTree索引使用的是B数中的B+Tree,但对于主要的两种存储引擎的实现方式是不同的。
MyISAM: B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。
InnoDB: 其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。
mysql 语法分析及优化
1 count(*)
count(*)
对 innodb 而言,它需要把数据从磁盘中读取出来然后累计计数;而 MyISAM 引擎把一个表的总行数存在了磁盘上,所以执行 count(*)
会直接返回这个数,如果有 where 条件则和 innodb一样。那么如何优化 count(*)
?一个思路是使用缓存,但是需要注意双写一致的问题(双写一致性后文缓存章节会做介绍)。还可以专门设计一张表用以存储 count(*)
。
对于 count(主键 id )来说,InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server 层拿到 id 后,判断是不可能为空的,就按行累加。对于 count(1) 来说,InnoDB 引擎遍历整张表,但不取值。server 层对于返回的每一行,放一个数字“1” 进去,判断是不可能为空的,按行累加。单看这两个用法的差别的话,你能对比出来,count(1) 执行得要比 count(主键 id)快。因为从引擎 返回 id 会涉及到解析数据行,以及拷贝字段值的操作。对于 count(字段)来说:如果这个“字段”是定义为 not null 的话,一行行地从记录里面读出这个字段,判断不能为 null,按行累加;如果这个“字段”定义允许为 null,那么执行的时候,判断到有可能是 null,还要把值取出来再 判断一下,不是 null 才累加。而对于 count(*) 来说,并不会把全部字段取出来,而是专门做了优化,不取值,按行累加。所以排序效率:
count(*)=count(1)>count(id)>count(字段)
2 order by
Mysql 会给每个线程分配一块内存用于做排序处理,称为 sort_buffer
,一个包含排序的 sql 执行过程为:申请排序内存 sort_buffer
,然后一条条查询出整行数据,然后将需要的字段数据放入到排序内存中,染回对排序内存中的数据做一个快速排序,然后返回到客户端。当数据量过大,排序内存盛不下的时候就会利用磁盘临时文件来辅助排序。当我们排序内存盛不下数据的时候,mysql 会使用 rowid
排序来优化。rowid 排序相对于全字段排序,不会把所有字段都放入 sort_buffer,所以在 sort buffer 中进行排序之后还得回表查询。在少数情况下,可以使用联合索引+索引覆盖的方式来优化 order by。
3 join
在了解 join
之前我们应该先了解驱动表这个概念——当两表发生关联的时候就会有驱动表和被驱动表之分,驱动表也叫外表(R 表),被驱动表也叫做内表(S 表)。一般我们将小表当做驱动表(指定了联接条件时,满足查询条件的记录行数少的表为「驱动表」,未指定联接条件时,行数少的表为「驱动表」;MySQL 内部优化器也是这么做的)。
假设有这样一句 sql(xxx 为索引):
select * from table1 left join tablet2 on table1.xxx=table2.xxx
这条语句执行过程是先遍历表 table1 ,然后根据从表 table1 中取出的每行数据中的 xxx 值,去表 table2 中查找满足条件的 记录。这个过程就跟我们写程序时的嵌套查询类似,并且能够用上被驱动表的索引,这种查询方式叫 NLJ
。当 xxx 不是索引的时候,再使用 NLJ
的话就会对 table2 做多次的全表扫描(每从 table1 取一条数据就全表扫描一次 table2),扫描数暴涨。这个时候 mysql 会采用另外一个查询策略。Mysql 会先把 table1 的数据读入到一个 join_buffer
的内存空间里面去,然后依次取出 table2 的每一行数据,跟 join_buffer
中的数据做对比,满足 join 条件的作为结果集的一部分返回。
我们在使用 join
的时候,要遵循以下几点:
小表驱动大表。
被驱动表走索引的情况下(走
NLJ
查询方式)的时候才考虑用join
4 sql 的优化
1) 在 mysql 中,如果对字段做了函数计算,就用不上索引了。
如以下 sql(data 为索引):
select * from tradelog where month(data)=1;
优化器对这样的 sql 会放弃走搜索树,因为它无法知道 data 的区间。
2)隐式的类型转换会导致索引失效。
如以下 sql:
select * from table where xxx=110717;
其中 xxx 为 varchar
型,在 mysql 中,字符串和数字做比较的话,将字符串转换成数字再进行比较,这里相当于使用了 CAST(xxx ASsigned)
导致无法走索引。
3)索引列参与了计算不会走索引
4)like %xxx 不会走索引,like xxx% 会走索引
5)在 where 子句中使用 or ,在 innodb 中不会走索引,而 MyISAM 会。