「数据库」和「数据库索引」这两个东西是在服务器端开发领域应用最为普遍的两个概念,熟练使用数据库和数据库索引是开发人员在行业内生存的必备技能程序员
使用索引很简单,只要能写建立表的语句,就确定能写建立索引的语句,要知道这个世界上是不存在不会建立表的服务器端程序员的。然而, 会使用索引是一回事, 而深刻理解索引原理又能恰到好处使用索引又是另外一回事,这彻底是两个天差地别的境界(我本身也尚未达到这层境界)。很大一部份程序员对索引的了解仅限于到“加索引能使查询变快”这个概念为止。数据库
为何要给表加上主键?
为何加索引后会使查询变快?
为何加索引后会使写入、修改、删除变慢?
什么状况下要同时在两个字段上建索引?
这些问题他们可能不必定能说出答案。知道这些问题的答案有什么好处呢?若是开发的应用使用的数据库表中只有1万条数据,那么了解与不了解真的没有差异, 然而, 若是开发的应用有几百上千万甚至亿级别的数据,那么不深刻了解索引的原理, 写出来程序就根本跑不动,就比如若是给货车装个轿车的引擎,这货车还能拉的动货吗?缓存
接下来就讲解一下上面提出的几个问题,但愿对阅读者有帮助。服务器
网上不少讲解索引的文章对索引的描述是这样的「索引就像书的目录, 经过书的目录就准确的定位到了书籍具体的内容」,这句话描述的很是正确, 但就像脱了裤子放屁,说了跟没说同样,经过目录查找书的内容天然是要比一页一页的翻书找来的快,一样使用的索引的人难到会不知道,经过索引定位到数据比直接一条一条的查询来的快,否则他们为何要建索引。数据结构
想要理解索引原理必须清楚一种数据结构「平衡树」(非二叉),也就是b tree或者 b+ tree,重要的事情说三遍:“平衡树,平衡树,平衡树”。固然, 有的数据库也使用哈希桶做用索引的数据结构 , 然而, 主流的RDBMS都是把平衡树当作数据表默认的索引数据结构的。性能
咱们平时建表的时候都会为表加上主键, 在某些关系数据库中, 若是建表时不指定主键,数据库会拒绝建表的语句执行。 事实上, 一个加了主键的表,并不能被称之为「表」。一个没加主键的表,它的数据无序的放置在磁盘存储器上,一行一行的排列的很整齐, 跟我认知中的「表」很接近。若是给表上了主键,那么表在磁盘上的存储结构就由整齐排列的结构转变成了树状结构,也就是上面说的「平衡树」结构,换句话说,就是整个表就变成了一个索引。没错, 再说一遍, 整个表变成了一个索引,也就是所谓的「汇集索引」。优化
这就是为何一个表只能有一个主键, 一个表只能有一个「汇集索引」,由于主键的做用就是把「表」的数据格式转换成「索引(平衡树)」的格式放置。3d
上图就是带有主键的表(汇集索引)的结构图。图画的不是很好, 将就着看。其中树的全部结点(底部除外)的数据都是由主键字段中的数据构成,也就是一般咱们指定主键的id字段。最下面部分是真正表中的数据。 假如咱们执行一个SQL语句:blog
select * from table where id = 1256;
1
首先根据索引定位到1256这个值所在的叶结点,而后再经过叶结点取到id等于1256的数据行。 这里不讲解平衡树的运行细节, 可是从上图能看出,树一共有三层, 从根节点至叶节点只须要通过三次查找就能获得结果。以下图索引
假如一张表有一亿条数据 ,须要查找其中某一条数据,按照常规逻辑, 一条一条的去匹配的话, 最坏的状况下须要匹配一亿次才能获得结果,用大O标记法就是O(n)最坏时间复杂度,这是没法接受的,并且这一亿条数据显然不能一次性读入内存供程序使用, 所以, 这一亿次匹配在不经缓存优化的状况下就是一亿次IO开销,以如今磁盘的IO能力和CPU的运算能力, 有可能须要几个月才能得出结果 。
若是把这张表转换成平衡树结构(一棵很是茂盛和节点很是多的树),假设这棵树有10层,那么只须要10次IO开销就能查找到所须要的数据, 速度以指数级别提高,用大O标记法就是O(log n),n是记录总树,底数是树的分叉数,结果就是树的层次数。换言之,查找次数是以树的分叉数为底,记录总数的对数,用公式来表示就是
用程序来表示就是Math.Log(100000000,10),100000000是记录数,10是树的分叉数(真实环境下分叉数远不止10), 结果就是查找次数,这里的结果从亿降到了个位数。所以,利用索引会使数据库查询有惊人的性能提高。
然而, 事物都是有两面的, 索引能让数据库查询数据的速度上升, 而使写入数据的速度降低,缘由很简单的, 由于平衡树这个结构必须一直维持在一个正确的状态, 增删改数据都会改变平衡树各节点中的索引数据内容,破坏树结构, 所以,在每次数据改变时, DBMS必须去从新梳理树(索引)的结构以确保它的正确,这会带来不小的性能开销,也就是为何索引会给查询之外的操做带来反作用的缘由。
讲完汇集索引 , 接下来聊一下非汇集索引, 也就是咱们平时常常提起和使用的常规索引。
非汇集索引和汇集索引同样, 一样是采用平衡树做为索引的数据结构。索引树结构中各节点的值来自于表中的索引字段, 假如给user表的name字段加上索引 , 那么索引就是由name字段中的值构成,在数据改变时, DBMS须要一直维护索引结构的正确性。若是给表中多个字段加上索引 , 那么就会出现多个独立的索引结构,每一个索引(非汇集索引)互相之间不存在关联。 以下图
每次给字段建一个新索引, 字段中的数据就会被复制一份出来, 用于生成索引。 所以, 给表添加索引,会增长表的体积, 占用磁盘存储空间。
非汇集索引和汇集索引的区别在于, 经过汇集索引能够查到须要查找的数据, 而经过非汇集索引能够查到记录对应的主键值 , 再使用主键的值经过汇集索引查找到须要的数据,以下图
无论以任何方式查询表, 最终都会利用主键经过汇集索引来定位到数据, 汇集索引(主键)是通往真实数据所在的惟一路径。
然而, 有一种例外能够不使用汇集索引就能查询出所须要的数据, 这种非主流的方法 称之为「覆盖索引」查询, 也就是平时所说的复合索引或者多字段索引查询。 文章上面的内容已经指出, 当为字段创建索引之后, 字段中的内容会被同步到索引之中, 若是为一个索引指定两个字段, 那么这个两个字段的内容都会被同步至索引之中。
先看下面这个SQL语句
//创建索引 create index index_birthday on user_info(birthday); //查询生日在1991年11月1日出生用户的用户名 select user_name from user_info where birthday = '1991-11-1'
这句SQL语句的执行过程就会变为:
经过非汇集索引index_birthday_and_user_name查找birthday等于1991-11-1的叶节点的内容,然而, 叶节点中除了有user_name表主键ID的值之外, user_name字段的值也在里面, 所以不须要经过主键ID值的查找数据行的真实所在, 直接取得叶节点中user_name的值返回便可。 经过这种覆盖索引直接查找的方式, 能够省略不使用覆盖索引查找的后面两个步骤, 大大的提升了查询性能,以下图