关于mysql的索引及原理
一、MySQL中索引的语法和注意事项
注意事项:
1、索引需要占用磁盘空间,因此在创建索引时要考虑到磁盘空间是否足够
2、创建索引时需要对表加锁,因此实际操作中需要在业务空闲期间进行
创建索引在创建表的时候添加索引
CREATE TABLE test( ID INT NOT NULL, username VARCHAR(16) NOT NULL, INDEX [indexName] (username(length)) );
在创建表以后添加索引
ALTER TABLE test ADD [UNIQUE] INDEX index_name(column_name); 或者 CREATE INDEX index_name ON test(column_name);
删除索引
DROP INDEX my_index ON test; 或者 ALTER TABLE test DROP INDEX index_name;
查看索引
SHOW INDEX FROM test;
二、索引的优缺点
优点:
1、大大加快数据的查询速度
2、使用分组和排序进行数据查询时,可以显著减少查询时分组和排序的时间
3、创建唯一索引,能够保证数据库表中每一行数据的唯一性
4、在实现数据的参考完整性方面,可以加速表和表之间的连接
缺点:
1、创建索引和维护索引需要消耗时间,并且随着数据量的增加,时间也会增加
2、索引需要占据磁盘空间
3、对数据表中的数据进行增加,修改,删除时,索引也要动态的维护,降低了维护的速度
三、索引的分类
常见的索引类型有:主键索引、唯一索引、普通索引、组合索引、全文索引
1、主键索引:根据主键建立索引,不允许重复,不允许空值; ALTER TABLE ‘test‘ ADD PRIMARY KEY index_name(‘id_index‘); 2、唯一索引:用来建立索引的列的值必须是唯一的,允许空值 ALTER TABLE ‘test‘ ADD UNIQUE index_name(‘name_index‘); 3、普通索引:用表中的普通列构建的索引,没有任何限制 ALTER TABLE ‘test‘ ADD INDEX index_name(‘age_index‘); 4、组合索引:用多个列组合构建的索引,这多个列中的值不允许有空值 ALTER TABLE ‘test‘ ADD INDEX index_name(‘colum1‘,‘colum2‘,‘colum3‘); 遵循“最左前缀”原则,把最常用作为检索或排序的列放在最左,依次递减,组合索引相当于建立了colum1,colum1colum2,colum1colum2colum3三个索引,而colum2或者colum3是不能使用索引的。 在使用组合索引的时候可能因为列名长度过长而导致索引的key太大,导致效率降低,在允许的情况下,可以只取colum1和colum2的前几个字符作为索引 ALTER TABLE ‘test‘ ADD INDEX index_name(colum1(4),colum2(3)); 表示使用colum1的前4个字符和colum2的前3个字符作为索引
四、索引失效的情况
1、where 条件的 in / or 查询,索引无效的,特别是 or ,能不用就不用,可以使用 union 联合查询来替代,union 下面 索引是有效的。
2、where 条件的 xxx != xx 这种情况 ,索引是无效的, 和 = 不一样的
3、如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
4、单列索引不存储null值,复合索引不存储全为null的值
5、不使用<>操作 ,<>操作将进行全表扫描,id<>3则可使用id>3 or id<3来代替。
6、使用like ‘%%‘会全表扫描,like ‘aaa%‘可以,但是不是每个字段都会前面几个固定
7、组合索引,没有用到第一个索引,会失效
8、在索引字段上使用函数
9、当全表扫描速度比索引速度快时,mysql会使用全表扫描,此时索引失效。
索引失效分析工具:
可以使用explain命令加在要分析的sql语句前面,在执行结果中查看key这一列的值,如果为NULL,说明没有使用索引。
五、索引原理
索引的目的在于提高查询效率,跟我们看书一个道理,找文章的时候去看目录定位到章,再找到小节,找到页数,然后翻过去
本质都是通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据
数据库也一样,只不过稍微复杂了一点,因为不仅面临着等值查询,还有范围查询(>、<、between、in)、模糊查询(like)、并集查询(or)等等。数据库应该选择怎么样的方式来应对所有的问题呢?我们回想字典的例子,能不能把数据分成段,然后分段查询呢?最简单的如果1000条数据,1到100分成第一段,101到200分成第二段,201到300分成第三段......这样查第250条数据,只要找第三段就可以了,一下子去除了90%的无效数据。但如果是1千万的记录呢,分成几段比较好?稍有算法基础的同学会想到搜索树,其平均复杂度是lgN,具有不错的查询性能。但这里我们忽略了一个关键的问题,复杂度模型是基于每次相同的操作成本来考虑的。而数据库实现比较复杂,一方面数据是保存在磁盘上的,另外一方面为了提高性能,每次又可以把部分数据读入内存来计算,因为我们知道访问磁盘的成本大概是访问内存的十万倍左右,所以简单的搜索树难以满足复杂的应用场景。
所以考虑到磁盘IO是非常高昂的操作,计算机操作系统做了一些优化,当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次IO读取的数据我们称之为一页(page)。具体一页有多大数据跟操作系统有关,一般为4k或8k,也就是我们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计非常有帮助。
如上图,是一颗b+tree,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
b+树的查找过程
如图所示,如果要查找数据项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,显然成本非常非常高。
b+tree性质
1、索引字段要尽量的小:通过上面的分析,我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高,当数据项等于1时将会退化成线性表。
2、索引的最左匹配特性:当b+树的数据项是复合的数据结构,比如(name,age,course)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,90)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和course,最后得到检索的数据;但当(20,90)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,90)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配分数是90的数据了, 这个是非常重要的性质,即索引的最左匹配特性。
六、mysql5.7引擎支持
欢迎评论交流