本文目录一览:
MySQL——关于索引的总结
首先说说索引的 优点 :最大的好处无疑就是提高查询效率。有的索引还能保证数据的唯一性,比如唯一索引。
而它的 坏处 也很明显:索引也是文件,我们在创建索引时,也会创建额外的文件,所以会占用一些硬盘空间。其次,索引也需要维护,我们在增加删除数据的时候,索引也需要去变化维护。当一个表的索引多了以后,资源消耗是很大的,所以必须结合实际业务再去确定给哪些列加索引。
再说说索引的基本结构。一说到这里肯定会脱口而出:B+树!了解B+树前先要了解二叉查找树和二叉平衡树。 二叉查找树 :左节点比父节点小,右节点比父节点大,所以二叉查找树的中序遍历就是树的各个节点从小到大的排序。 二叉平衡树 :左右子树高度差不能大于1。B+树就是结合了它们的特点,当然,不一定是二叉树。
为什么要有二叉查找树的特点?? 因为查找效率快,二分查找在这种结构下,查找效率是很快的。 那为什么要有平衡树的特点呢? 试想,如果不维护一颗树的平衡性,当插入一些数据后,树的形态有可能变得很极端,比如左子树一个数据没有,而全在右子树上,这种情况下,二分查找和遍历有什么区别呢?而就是因为这些特点需要去维护,所以就有了上面提到的缺点,当索引很多后,反而增加了系统的负担。
接着说B+树。 它的结构如下 :
可以发现,叶子节点其实是一个 双向循环链表 ,这种结构的好处就是,在范围查询的时候,我只用找到一个数据,就可以直接返回剩余的数据了。比如找小于30的,只用找到30,其余的直接通过叶子节点间的指针就可以找到。再说说其他特点: 数据只存在于叶子节点 。当叶子节点满了,如果再添加数据,就会拆分叶子节点,父节点就多了个子节点。如果父节点的位置也满了,就会扩充高度,就是拆分父节点,如25 50 75拆分成:25为左子树,75为右子树,50变成新的头节点,此时B+树的高度变成了3。它们的扩充的规律如下表,Leaf Page是叶子节点,index Page是非叶子节点。
再说说B树 ,B树相比较B+树,它所有节点都存放数据,所以在查找数据时,B树有可能没到达叶子节点就结束了。再者,B树的叶子节点间不存在指针。
最后说说Hash索引 ,相较于B+树,Hash索引最大的优点就是查找数据快。但是Hash索引最大的问题就是不支持范围查询。试想,如果查询小于30的数据,hash函数是根据数据的值找到其对应的位置,谁又知道小于30的有哪几个数据。而B+树正好相反,范围查询是它的强项。
附录: Hash到底是啥?? 哈希中文名散列,哈希只是它的音译。 为啥都说Hash快?? 首先有一块哈希表(散列表),它的数据结构是个数组,一个任意长度的数据通过hash函数都可以变成一个固定长度的数据,叫hash值。然后通过hash值确定在数组中的位置,相同数据的hash值是相同的,所以我们存储一个数据以后,只需O(1)的时间复杂度就可以找到数据。 那hash函数又是啥?? 算术运算或位运算,很多应用里都有hash函数,但实际运算过程大不一样。这是Java里String的hashCode方法:
publicint hashCode() {
}
还有一个问题,hash函数计算出来的hash值有可能存在碰撞,即两个不同的数据可能存在相同的hash值,在MySQL或其他的应用中,如Java的HashMap等,如果存在碰撞就会以当前数组位置为头节点,转变成一个链表。
说到这里也清楚了为啥Java中引用类型要同时重写hashCode和equals了。两个对象,实例就算一模一样,它们的hash值也不相等, 为啥不相等?? 默认的Object的hashCode方法会根据对象来计算hash值的,实例相同,但它们还是两个不同的对象啊,所以我们重写hashCode时,最简单的方法就是调用Object的hashCode方法,然后传入该引用类型的属性,让hashCode方法只根据这几个属性来计算,那么实例相同的话,它们的hash值也会相等。等hashCode比较完后,如果相等再比较实例内容,也就是equals,确保不是hash碰撞。
索引的分类
如果我们指定了一个主键,那么这个主键就是主键索引。如果我们没有指定,Mysql就会自动找一个非空的唯一索引当主键。如果没有这种字段,Mysql就会创建一个大小为6字节的自增主键。如果有多个非空的唯一索引,那么就让第一个定义为唯一索引的字段当主键,注意,是第一个定义,而不是建表时出现在前面的。
对于辅助索引来说,它们的B+树结构稍微有点特殊,它们的叶子节点存储的是主键,而不是整个数据。所以在大部分情况下,使用辅助索引查找数据,需要二次查找。但并不是所有情况都需要二次查找。比如查找的数据正好就是当前索引字段的值,那么直接返回就行。这里提一句,B+树的key就是对应索引字段的内容。
而辅助索引又有一些分类:唯一索引:不能出现重复的值,也算一种约束。普通索引:可以重复、可以为空,一般就是查询时用到。前缀索引:只适用于字符串类型数据,对字符串前几个字符创建索引。全文索引:作用是检测大文本数据中某个关键字,这也是搜索引擎的一种技术。
注意,聚集索引、非聚集索引和前面几个索引的分类并不是一个层面上的。上面的几个分类是从索引的作用来分析的。聚集、非聚集索引是从索引文件上区分的。主键索引就属于聚集索引,即索引和数据存放在一起,叶子节点存放的就是数据。数据表的.idb文件就是存放该表的索引和数据。
辅助索引属于非聚集索引,说到这也就明白了。索引和数据不存放在一起的就是非聚集索引。在MYISAM引擎中,数据表的.MYI文件包含了表的索引, 该表的 叶子节点存储索引和索引对应数据的指针,指向.MYD文件的数据。
索引的几点使用经验
经常被查询的字段;经常作为条件查询的字段;经常用于外键连接或普通的连表查询时进行相等比较字段;不为null的字段;如果是多条件查询,最好创建联合索引,因为联合索引只有一个索引文件。
经常被更新的字段、不经常被查询的字段、存在相同功能的字段
MySQL前缀索引
前缀索引顾名思义,定义字符串的一部分当做索引,而不是把整个字符串当做索引。默认地,如果你创建索引的语句不指定前缀长度,那么索引就会包含整个字符串。
假设一张表有 id,name,email 2个字段
1.创建email列的普通索引应该是: alter table T add index idx_email1( email )
2.前缀索引的创建规则为: alter table table T add index idx_email2( email(6) )
当然第一索引包含是的整个字符串,第二个是该字段前6个字节(注意是字节)
对于这2中索引,B+树怎么存储呢?
INSERT INTO T (email) VALUES ('瞎子','zhangsh1234@163.com'), ('剑圣','lisi1998883@163.com'), ('露娜','zhangssxyz@163.com'), ('李白','zhangsy1998@163.com'), ('韩信','zhaq5481993@163.com'), ('百里玄策','hhaq5481993@163.com');
【谁还不是个野王啊】
普通索引存储为:
是的你没看错,前缀索引那颗树上的存储的是email的前6位字节,也就是你创建前缀索引时指定的前缀字节长度。2种树相比,前缀索引存储了更少的数据,那么他所耗费的空间也就相比较少,这正是他的一个优点。同样的也就相对的增加了扫描行数。
什么增加了扫描行数???? 这是为什么呢?
那么小朋友咱们一起来看下吧。
假设SQL如此这般: select id,name,email from T where email = 'zhangsh1234@163.com'
那么这2个SQL,应该怎么操作呢。
idx_email1:
2.到主键上查到主键为ID1的,判断email值是否正确【为什么判断呢,其实我理解是为了二次判断保证数据一致性吧,比较官方的解释尚未找到】,正确放入结果集
3.取 idx_email1 索引树上刚刚查到的位置的下一条记录,如此往复。
循环过程中,需要回主键取1次数据,所以系统可以认为只扫描了一行【1次是数第一棵树数出来的】
idx_email2:
1.从 索引数上找到满足索引值为 'zhangs'的该记录,取得 ID1的值
2.到主键上查到主键值是 ID1 的行,判断出 email 的值是’ zhangsh1234@xxx.com ’,这行记录放入结果集【不是要的值,丢弃,进行下一步】
3.取 idx_email2 上刚刚查到的位置的下一条记录,重复以上步骤
在这个过程中,要回主键索引取 3 次数据,也就是扫描了 3 行。通过这个对比,你很容易就可以发现,使用前缀索引后,可能会导致查询语句读数据的次数变多。
但是,对于这个查询语句来说,如果你定义的 idx_email2 不是 email(6) 而是 email(8),也就是说取 email 字段的前 8 个字节来构建索引的话,即满足前缀’zhangsh’的记录只有一个,也能够直接查到 ID1,只扫描一行就结束了。也就是说使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。
那么问题来了,到底定义多长才算是合理呢?
一般的定义原则是 count(distinct(columnName))/count(*) ,当前缀索引【count(distinct(columnName(length))),length是你想要创建列的前缀字节长度】越接近此值越好,当有多个前缀字节都一样且都等于这个值时怎么选择呢,当然是 字节越少越好了哈,字节越少越省空间。索引选取的越长,占用的磁盘空间就越大,相同的数据页能放下的索引值就越少,搜索的效率也就会越低。
count(distinct(columnName(length))) 翻译到SQL 为: count(dictinct(left(colunmName, length)))
前面我们说了使用前缀索引可能会增加扫描行数,这会影响到性能。其实,前缀索引的影响不止如此,我们再看一下另外一个场景。
来呀,上SQL: select id,email from T where email='zhangsh1234@163.com'
如果按照email全字段索引,那么此SQL 是不需要回表的【为什么不需要回表?兄嘚,这个相当于覆盖索引了哈】
那么如果按照前缀索引是否需要回表呢?答案是的。
因为当判断前6个字节相等后,需要拿到id 回表拿到email的全部内容进行比较,如果不相同,丢弃这行,否则加入结果集。
那么有人会问了,我把长度放大点,包含所有字节不就好了吗?
那么此时会有如下问题。
1.当你此时的长度是囊括了全字段,但是系统是不知道的,他还是需要回表再次判断的,去确定前缀索引的定义是否截断了完整信息。
2.此时长度是够了,那么能肯定因为业务日后不会增加长度吗?
3.尽可能的加长长度,还不如直接建立全字段索引呢
综上,使用前缀索引就用不上覆盖索引对查询性能的优化了,这也是你在选择是否使用前缀索引时需要考虑的一个因素。
前面说到的是,可以根据字段前面几个字节进行查询的,那么对于身份证这种,一共 18 位,其中前 6 位是地址码,所以同一个县的人的身份证号前 6 位一般会是相同的。
或许你会说,多弄几个字节不就好吗?那么请问下自己为什么使用前缀索引呢,不就是为了节省空间吗?
那么这么做合适吗? 不合适对吗? 乖~,快去反省下吧
那么采用前缀索引显示是不行的,那么如果用前缀索引怎么办呢,聪明的你应该已经猜到了,采用倒叙存储,然后建立前缀索引。
放到SQL 中就应该是这样的: select field_list from t where id_card = reverse('id_card_string');
当然了,这种逻辑建议放到业务逻辑中实现,而不是放到SQL 中。
按照上述第4节的内容,有人或许会有另一个想法,还倒叙建立前缀索引复杂不,hash索引或者hash字段不香吗?
有人会问了,为什么要在创建一个值来存储hash值呢,如果不存储你知道原值是什么吗? 同时hash算法是有一定重复可能的(hash值碰撞)
【可以了解下partition算法哦:[ 】。如果重复了,不存储原值,你是无法判断出正确数据的。
注:【hash字段不代表hash索引,hash索引原理正在快马加鞭】,简单说下hash索引,hash索引不需要创建一个值来存储hash值,而是有hasn表来存储【hash值碰撞时,由一个链表来搞定了】,存储的内容为 hash值和每行的行指针 。
说回来啊,跑题了
查询时: select field_list from t where id_card_crc=crc32('id_card_string') and id_card='id_card_string'
不过有个问题相信你也想到了,不管是hash存储值还是hash索引都是不支持范围查询的。
来总结下这2个优缺点吧
1.从占用空间来看呢,倒叙索引不需要额外开辟存储空间,而hash字段需要额外的一个字段,所以从这点上看倒叙索引更胜一筹,NO!并不准确,如果前缀长度过长,那么这2个情况额外的空间也就相差无几了
3.从查询效率上看,使用 hash 字段方式的查询性能相对更稳定一些。因为 crc32 算出来的值虽然有冲突的概率,但是概率非常小,可以认为每次查询的平均扫描行数接近 1。而倒序存储方式毕竟还是用的前缀索引的方式,也就是说还是会增加扫描行数
1.全字段完整索引比较占空间,但是而走覆盖索引
2.前缀索引,节省空间,但会增加扫描 次数 并且不能使用覆盖索引【每次都需回表校验】
3.倒序存储,再创建前缀索引,用于绕过字符串本身前缀的区分度不够的问题。【倒叙方法建立放到业务逻辑中】
4.hash字段索引,相比前缀索引性能较为稳定,但是有额外的存储空间和计算消耗,同时也 不 支持范围查询
「Mysql索引原理(七)」覆盖索引
通常大家都会根据查询的WHERE条件来创建合适的索引,不过这只是索引优化的一个方面。设计优秀的索引应该考虑到整个查询,而不单单是WHERE条件部分。索引确实是一种查找数据的高效方式,但是MySQL也可以使用索引来直接获取列的数据,这样就不再需要读取数据行。如果索引的叶子节点中已经包含要查询的数据,那么还有什么必要再回到表中查询呢? 如果一个索引覆盖所有需要查询的字段的值,我们就称之为“覆盖索引”。
覆盖索引是非常有用的工具,能够极大地提高性能:
在所有这些场景中,在索引中满足查询的成本一般比查询行要小得多。
不是所有类型的索引都可以成为覆盖索引。覆盖索引必须要存储索引列的值,而哈希索引、空间索引和全文索引都不存储索引列的值,所以MySQL只能使用B+Tree索引所覆盖索引。另外,不同的存储引擎实现覆盖索引的方式也不同,而且不是所有的引擎都支持覆盖索引。
当发起一个呗索引覆盖的查询是,在EXPLAIN的Extra列可以看到“Using index”的信息。
如: explain select col1 from layout_test where col2=99
索引覆盖查询还有很多陷阱可能会导致无法实现优化。MySQL查询优化器会在执行查询前判断是否有一个索引能进行覆盖。假设索引覆盖了wehre条件中的字段,但不是整个查询涉及的字段。mysql5.5和更早的版本也总是会回表获取数据行,尽管并不需要这一行且最终会被过滤掉。
如: EXPLAIN select * from people where last_name='Allen' and first_name like '%Kim%'
这里索引无法覆盖该查询,有两个原因:
这条语句只检索1行,而之前的 like '%Kim%'要检索3行。
也有办法解决上面所说的两个问题,需要重写查询并巧妙设计索引。
这种方式叫做延迟关联,因为延迟了对列的访问。在查询第一个阶段MySQL可以使用覆盖索引,因为索引包含了主键id的值,不需要做二次查找。
在FROM子句的子查询中找到匹配的id,然后根据这些id值在外层查询匹配获取需要的所有列值。虽然无法使用索引覆盖整个查询,但总算比完全无法利用索引覆盖的好吧。
数据量大了怎么办?
这样优化的效果取决于WHERE条件匹配返回的行数。假设这个people表有100万行,我们看一下上面两个查询在三个不同的数据集上的表现,每个数据集都包含100万行。
实例1中 ,查询返回了一个很大的结果集,因此看不到优化的效果。大部分时间都花在读取和发送数据上了。
实例2中 ,经过索引过滤,尤其是第二个条件过滤后只返回了很少的结果集,优化的效果非常明显:在这个数据及上性能提高了很多,优化后的查询效率主要得益于只需读取40行完整数据行,而不是原查询中需要的30000行。
实例3中 ,子查询效率反而下降。因为索引过滤时符合第一个条件的结果集已经很小了,所以子查询带来的成本反而比从表中直接提取完整行更高。
在大多数存储引擎中,覆盖索引只能覆盖那些只访问索引中部分列的查询。不过,可以更进一步优化InnoDB。回想一下,InnoDB的二级索引的叶子节点都包含了主键的值,这意味着InnoDB的二级索引可以有效地利用这些额外的主键列来覆盖查询。
例如,people表中last_name字段有一个二级索引,虽然该索引的列不包括主键id,但也能够用于对id做覆盖查询:
select id,last_name from people where last_name='hua'