索引一种数据结构,其目的是为了更快的查询数据,在数据量很大的表中,创建良好的索引可以提高极大的性能。mysql
由于数据库存储数据量大,是不可能存储在内存中以供查询的,因此对于数据的查询必然会跟磁盘打交道,因此只有了解了磁盘io和预读的基本知识,咱们才能真正的理解索引的原理。算法
磁盘读取数据靠的是机械运动,每次读取数据花费的时间可 以分为寻道时间、旋转延迟、传输时间三个部分,寻道时间指的是磁臂移动到指定磁道所须要的时间,主流磁盘通常在5ms如下;旋转延迟就是咱们常常据说的磁 盘转速,好比一个磁盘7200转,表示每分钟能转7200次,也就是说1秒钟能转120次,旋转延迟就是1/120/2 = 4.17ms;传输时间指的是从磁盘读出或将数据写入磁盘的时间,通常在零点几毫秒,相对于前两个时间能够忽略不计。那么访问一次磁盘的时间,即一次磁盘 IO的时间约等于5+4.17 = 9ms左右,听起来还挺不错的,但要知道一台500MIPS的机器每秒能够执行5亿条指令,由于指令依靠的是电的性质,换句话说执行一次IO的时间能够执行40万条指令,数据库动辄十万百万乃至千万级数 据,每次9毫秒的时间,显然是个灾难。考 虑到磁盘IO是很是高昂的操做,计算机操做系统作了一些优化,当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,由于局 部预读性原理告诉咱们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次IO读取的数据咱们称之为一页(page)。具体一页 有多大数据跟操做系统有关,通常为4k或8k,也就是咱们读取一页内的数据时候,实际上才发生了一次IO。sql
以前说到了磁盘读取数据的方式,那么咱们的索引是如何配合这个方式更快的搜索到数据呢?首先,咱们要了解索引的数据结构。咱们这里讲到的索引B+TREE的索引,由于这个索引咱们最经常使用,实际上索引还有哈希索引,空间数据索引,全文索引。数据库
首先咱们来理解B+TREE的数据结构: B+Tree是一种多路搜索树(并非二叉的):缓存
B+的特性:数据结构
B+TREE的深度最可能是O(log[M/2]N),在路径上的每一个节点须要用O(logM)s时间复杂度来肯定是哪一个分支(使用二分查找),inset和delete可能须要O(M)的工做量来调整该节点上的全部信息,因此insert和delete的运行时间最坏状况是O(Mlog[M]N),而每次的查询只须要花费O(logN)。从刚刚的时间复杂度能够看出当在内存中查询时,Mzuihaode选择是3或者4,再增大时速度就会增长。可是咱们的数据存储是在磁盘中的,相比读取一个存储器所花费的时间,M增大所增长的时间花费不值一提。此时M的值选择为使得一个内部节点可以装入一个磁盘区块的最大值,因此M取值范围为[32,256],这样当一片树叶上元素是满的,并且树叶是满的那么硬盘上一个区块就被装满了。这样意味着,一个记录总能够在不多的磁盘访问中被找到,由于此时B树深度只有2或3,而根能够直接加载到内存中,因此整个访问速度就会很快。app
因此咱们再来看上图: 若是要查找数据项30,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确 定29在28和65之间,锁定磁盘块1的P2指针,内存时间由于很是短(相比磁盘的IO)能够忽略不计,经过磁盘块1的P2指针的磁盘地址把磁盘块3由磁 盘加载到内存,发生第二次IO,30在28和35之间,锁定磁盘块3的P2指针,经过指针加载磁盘块8到内存,发生第三次IO,同时内存中作二分查找找到 30,结束查询,总计三次IO。真实的状况是,3层的b+树能够表示上百万的数据,若是上百万的数据查找只须要三次IO,性能提升将是巨大的,若是没有索引,每一个数据项都要发生一次IO,那么总共须要百万次的IO,显然成本很是很是高。函数
有些查询不当的使用索引,使得Mysql没法使用已有的索引。好比查询中列不是独立的,则Mysql不会使用索引。独立的列指的是:索引不能是表达式的一部分,更不能是函数的参数,例如:工具
select app_id from app where app_id + 1 = 5;
复制代码
MySQL没法解析app_id + 1这个表达式,因此没法使用索引。性能
有时候索引的字段特别的长,这会让索引变得又大又慢。这种状况能够经过只取出字段前面几个字符来作索引,这样能够节约索引空间,从而提升索引的效率,可是这样会减小索引的选择性。索引的选择性,指的是不重复的索引值(基数)跟数据表的总数的比值。索引选择性越高查询的效率就越快,由于这样索引能帮助Mysql查找时过滤掉更多的行。好比主键和惟一索引,这种时候性能最好。因此在选择索引长度时要同时考虑索引选择性才能达到性能最优化。
大多数对于索引理解不够的,因此容易犯如下两个:
聚簇索引并非一个单独的索引形式,而是Mysql在B+TREE索引上的数据存储形式。InnoDB的聚簇索引实现就是在统一结构里面保存了B+TREE索引和数据行如图:
从上面几张图能够看出,非聚簇索引表的主键索引跟普通的索引没有区别,他直接就是索引里有个指向数据所在指针的形式,可是聚簇索引表的主键索引“就是”一张表,因此不须要非聚簇索引表那样数据的独立行储存。咱们直接来看二者的对比图:
了解了他们的区别咱们接着来讨论聚簇索引的优势:
下面咱们来看看聚簇索引的缺点:
覆盖索引指的是包含了一个查询全部须要查询字段的值。覆盖索引是一个很是有用的工具,可以极大的提高效率,由于索引的叶子节点已经包含了全部须要的数据,无需再去读取数据行。其优势以下:
由于覆盖索引是索引中存储了列的值,因此覆盖索引的适用范围仅适用于B+TREE的时候.
扫描索引自己很快,由于只须要一条索引记录移动到下一条索引记录就好了。可是若是索引不能覆盖查询的全部列,那就只能每一条索引记录都要去查询一次对应的行。这基本上都是随机IO,因此按索引顺序读取数据的速度反而要比顺序的全表扫描慢,尤为是IO密集型的工做负载时。 只有当索引的列顺序和order by的字句顺序彻底一致,而且全部列的排序方向(正序倒序)都同样时,Mysql才能使用索引来对结果作排序。固然若是最左前缀在where条件中已是一个常量了,能够不用知足最左前缀的order by子句。
索引可让查询锁定更少的行。好比以前咱们遇到过得for update语句若是用了主键的索引,那么就只锁定一行,而其余的就会锁表。还有不少状况查询的时候只锁定索引查出来的行,这样的话能减小不少锁的开销。还有个InnoDB的细节须要注意: InnoDB在二级索引(辅助索引)上用的是共享锁(读锁),可是访问主键索引就用的是排他锁(写锁)(其实也就是以前咱们工做中遇到的那个用主键的索引就是行锁,可是其余索引就是表锁)。这种状况就无法使用以前讲到的覆盖索引(二级索引访问主键索引),而且使用 for update 比share in share mode或非锁定查询要慢得多。
数据库的优化是一个很是重要的工做,不少时候不能犯教条主义错误,最主要的方式仍是要经过本身操做来验证到底该如何优化。极可能一次优化在100M的数据量的时候起做用了,可是1G的时候又会变成慢查询,这种状况就要去思考如何才能避免再出这样的问题。数据库能够存储一些历史数据做为记录,可是大多数状况咱们须要的是最近的数据或者是少数的几个热数据,这种状况下就须要用高速缓存的方式来完成这方面的工做,而不是一味的只用数据库。这才设计构思的时候很重要。还有索引不是万能的,千万不要乱建索引,有时候还会形成速度变得更低,并且还浪费了空间,再创建索引的时候也要好好思考一下这个索引的必要性和用处。另外优化慢查询有时候也未必非要加索引,不少时候经过一些语句的构造也能实现优化的目的。以上内容主要来自我以前阅读的一本书籍《高性能MySQL》,配合一些算法知识再加上我自身的一些经验,写在这里只是起到抛砖引玉的做用,数据库优化的路还很长,坑还不少,但愿与你们一块儿学习,共同进步。