mysq索引
索引是什么?索引本质上是对数据的目录
1.索引的分类
我们可以从四个方面对索引进行分类:
- 按[数据结构]划分:B+Tree 、Hash、Full-text索引
- 按[物理存储]划分:聚簇索引、二级索引
- 按[字段特性]划分:主键索引、唯一索引、普通索引、前缀索引
- 按[字段个数]划分:单列索引、联合索引
1.1 B+Tree
我们知道mysql底层有三种执行引擎:
Innodb | Memory | MyISAM |
---|---|---|
✅ | ✅ | ✅ |
三种执行引擎都是支持B+Tree索引的 |
接下来我们详细介绍一下该索引
Innodb默认的索引就是B+Tree,在创建表时有三种策略:
- 如果主键,会默认将主键作为索引值
- 如果没有主键,则会选择第一个不包含null的字段作为索引值
- 在以上两者都没有的情况下,则会创建一个隐式自增的字段作为索引值
B+树是一个多叉树,其节点可以分为叶子节点和非叶子节点,其中叶子节点存放具体数据,非叶子节点存放索引
B+树在数据量很大(千万级别),仍能保持三~四层树高,因此能保证较低的磁盘IO开销
其中叶子节点还需要区分主键索引和二级索引,主键🆚二级:
- 主键索引:叶子节点中的val包含了完整的行数据
- 二级索引:叶子节点的val只包含了主键值
这就代表这二级索引往往需要回表操作:
1 | select * from user where age = 10 and salary > 20000 |
对于这一行sql语句而言,假设我们建立了二级索引(age, salary)我们可以知道,这个索引是会生效的(索引失效是后话),对于该二级索引,对应的B+树结构应该是:
1 | key:(age, salary) |
在这种情况下,select要求的是所有字段的数据,二级索引找到后还需要根据主键id进行回表查找,返回整个行数据
但是不是所有二级索引都需要回表的,假如我们把上述的sql改成
1 | select age, salary from user where age = 10 and salary > 20000 |
这种情况下就不需要回表了,因为二级索引查找到后已经能够返回所有数据了。
B+Tree 🆚 BTree:
- 区别一:B+Tree的非叶子节点只存放索引,这样可以让树更加扁平,降低磁盘io开销
- 区别二:B+Tree的叶子节点之间由双向链表组织,在范围查询时可以直接通过这个链表遍历,而BTree只能通过树的遍历方式
1.2 联合索引的最左匹配原则
对于联合索引而言,存在最左匹配原则
1 | # 对于索引(a, b, c)而言 |
具体原因分析:
对于联合索引而言,索引键排序是按照设定的联合索引顺序依次比较的,也就是说对于联合索引(a, b, c)是在a相同的情况下比较b,b相同的情况下比较c,这里和字符串比较有些相近
因此我们可以认为b和c这两个字段是局部有序的
接下来我们介绍索引失效的情况:
首先我们需要理解where是怎么完成过滤的,如where a = 1 and b = 2,我们会先找出所有满足a = 1的记录(比如10条),接下来我们再在这十条里找b = 2的记录
因此对于(a, b)这个联合索引
1 | select * from user where a = 1 and b = 2 |
首先通过索引先找到a = 1的位置,因为对于a = 1的情况下,b 也是有序的,可以根据索引继续快速定位b
1 | select * from user where a > 1 and b = 2 |
先根据a > 1快速定位a,但是由于a > 1的情况下 b并不是有序的,比如(2, 3), (3, 2), (4, 1) 因此无法根据索引快速定位b,因此我们称该联合索引部分命中
我们可以简单理解成范围查询处会中断联合索引,只有等值查询才可以继续使用索引
我们再来看一种情况
1 | select * from user where a >= 1 and b = 2 |
这种情况下也是用到了索引,因为a = 1的时候可以根据索引快速定位b
1 | select * from user where name like 'j%' and b = 2 |
这种情况下也是能命中索引,其实这里的原理和=相同,第一条记录是’j‘在这种情况下b是有序的因此也能使用索引
这里可以和索引下推起来思考,索引下推就是弥补这种情况出现的
1.3 索引区分度
1 | 区分度 = distinct(column) / count(*) |
比如性别只有男/女,因此它的区分度特别低,一般不考虑做索引
2. 索引的建立
那么什么时候我们需要索引,什么时候我们不需要索引呢?
索引的缺点:
- 需要占用物理空间,数量越大,空间越大
- 创建索引和维护索引需要消耗时间,这个时间随着数据量增大而增大
- 会降低表的增删改效率,当每次增删改索引时,B+树需要动态维护
那么什么时候我们需要创建索引呢?
- 字段具有唯一性特征
- 经常用于WHERE的字段,如果能提高查询效率可以添加索引
- 需要GROUP BY和ORDER BY的字段,这样就不用再排序了
什么时候不需要索引?
- 表的数据量不大
- 字段的区分度不高
- 表需要经常进行更新
3. 优化索引的方法
- 前缀索引优化
顾名思义就是根据字符串的前几个字符建立索引,而不是全部,这样能够减少一个索引的内存,可以有效提高索引的查询效率 - 覆盖索引优化
建立覆盖索引,减少二级索引的回表次数 - 主键索引自增
尽量使用自增的字段建立主键索引,这样每次插入一条新数据的时候都是,追加操作,不用重新移动数据,加入使用非自增的主键,有可能导致新的数据插入到某个数据页的中间,导致页分裂,降低效率 - 防止索引失效
尽量避免索引失效的查询,具体可见索引失效的常见
4. 索引失效场景
首先我们介绍一下MyISAM和InnoDB两种引擎的B+树区别:
- MyISAM的B+树存放的是数据的地址
- InnoDB的B+树存放的是实际数据
使用左模糊匹配
我们知道,B+树的索引是根据索引值进行大小比较的,如果采用左模糊匹配,则无法利用索引的有序性对索引值进行了表达式或者函数
索引是对字段的原始值建立的,一旦对索引值进行了表达式或者函数的计算之后,它就无法对索引发生了隐式变化
Mysql中的字符串与数字进行比较时,会自动将字符转化为数字然后进行比较,因此这里与索引字段表达式或函数计算一般,发生失效1
2select * from user where phone = 12345678910;
select * from user where id = "1";这里两条sql语句哪一条会发生索引失效?答案是第一条,因为会把varchar的phone做一次隐式转化,导致索引失效了
不遵循索引最左匹配原则
字面意思,上文已经介绍过了WHERE子句中的OR
其实这个比较好理解1
select * from user where age = 15 or name = "Li Lei";
简单理解就是,如果只有一个age有索引的话对这个结果没有用,因为根据name还是要全表扫描,只有两个字段都有索引不会使得索引失效
深度分页问题
1 | select * from user order by id limit 0, 10; |
对于这么一条sql语句我们称之为分页查询,它的底层原理是什么?
- 根据主键索引, 找到对应的行数据,全部返回给server层
- server层跳过前offset条记录,读取n条记录
因此我们可以发现一旦offset变得很大的时候,他会带来很大的开销,同时如果更为一下情况
1 | select * from user order by user_name limit 60000, 10; |
我们可以给user_name建立二级索引,但是这样相比与主键而言又多了回表的开销,因此性能非常低
如何解决?
- 尽量从业务层面减少深度分页的情况
- 每次从上次的最大下标处开始select 比如 where id > ?