2、索引
索引基本概念
索引:数据的目录 InnoDB 存储引擎在创建表时:
- 有主键,默认使用主键作为聚簇索引的 key
- 没有
- 有不包含 NULL 的唯一列:选择第一个符合的列作为聚簇索引的 key
- 没有:自动生成一个隐式自增 id 作为聚簇索引的 key。
分类
- 数据结构:
- B+ 树索引
- Hash 索引
- Full-text 索引
- 物理存储 :
- 聚簇索引(主键索引):
- 叶子节点存放的实际的完整数据
- 一张表只有一个
- 二级索引(辅助索引):
- 叶子节点存放的是主键值
- 一张表可以有多个
- 聚簇索引(主键索引):
- 字段特性:
- 主键索引
- 唯一索引:插入数据时会直接读取磁盘数据,绕开 change buffer,否则报错,保证唯一性,非唯一索引直接把插入数据丢到 change buffer 就不管了。
- 普通索引
- 前缀索引:如通过关键字的前几个字符检索,但是一般通过 es 实现
- 字段个数:
- 单列索引
- 联合索引
- 需要满足最左匹配原则,前一个字段相同的情况下,后一个字段才是有序的,后面的字段全局是无序的
- 遇到
>、<时,会停止匹配
优缺点:
- 优点:
- 加快检索速度,减少 IO 次数
- 唯一索引可以保证该列数据的唯一性
- 缺点:
- 创建和维护索引需要耗费资源
- 索引需要物理存储,需要 耗费空济南
一些概念
- 回表:二级索引中不包含所需要的所有数据,需要通过二级索引获取主键值回到主键索引获取完整数据(通过二级索引过滤的情况下)
- 覆盖索引:二级索引中包含所需要的所有数据,不需要回表
- 索引下推 :在联合索引遍历过程中,对联合索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数
建立索引时,需要考虑区分度,区分度就是某个字段 column 不同值的个数「除以」表的总行数,区分度小的可以放在联合索引的后面,不适合建立单列索引 这样可以在索引页排除大部分不符合的数据,减少 I/O
经常用于 where / group by / order by的字段适合建立索引,可以降低 I/O、加快排序 经常更新或者区分度低的字段不适合建立索引,前者维护成本高,后者增效少
有什么优化索引的方法?
这里说一下几种常见优化索引的方法:
- 前缀索引优化;
- 用字符串的前几个字符建立索引,提高效率
- order by 无法使用
- 无法作为为覆盖索引(缺少全部数据)
- 覆盖索引优化;
- 包括查询需要的所有字段,可以避免回表
- 主键索引最好是自增的;
- 否则插入容易造成页分裂,造成内存碎片,且降低查询效率
- 索引设置为 NOT NULL
- 1、增加额外的 NULL 值列表
- 计算等比较复杂
- 防止索引失效;
- 避免频繁更新的字段
- 索引树会不断的进行调整
- 限制索引数量
- 降低插入和更新的效率
- MySQL 优化器评估执行计划效率更低
- 尽可能建立联合索引
- 避免冗余索引
- 比如 联合索引可以覆盖的索引 或者 不会用到的索引
- 频繁用于查询(需求字段或者过滤字段)、排序、外连接的字段
索引结构选择
好的索引数据结构
- 能在尽可能少的磁盘的 I/O 操作中完成查询工作;
- 要能高效地查询某一个记录,也要能高效地执行范围查找; 差别
- 二分查找树:
- 一个节点最多 2 个子节点
- 所以时间复杂度最好是
O(logN) - 极端情况会出现
O(N) - 平衡二叉查找树(AVL 树)可以避免极端情况
- Hash:
- 非常适合做等值查询,O(1) 时间复杂度
- 但是非常不适合做范围查找,无序
- 跳表:二分查找,比较高,和二分查找树类似
- redis 用跳表因为不需要考虑磁盘 IO,而跳表没有旋转平衡的开销,更快一些
- B 树:
- 一个节点可以有 M 个节点 (M>2),是一个多叉树
- 其非叶子节点也存放数据,所以相对 B+树 来说,层数更高
- 会使用中序遍历进行范围查找,会设计多个节点的磁盘 I/O,整体速度较低
- B+ 树:
- 整体结构和 B 树类似
- 只有叶子节点存放数据,非叶子节点存放索引,层数可以更低,更加的矮胖
- 叶子节点之间有链表连接,可以更高的进行范围查找
- B 和 B+ 树
- 单体查找:B 树可能一次就找到了(非叶子节点也是数据),但是波动很大
- 插入和删除:
- B+ 树在删除时,不需要变动整体结构,一般只需要删除叶子节点即可
- B 树在删除时,因为可能删除非叶子节点,就导致树的复杂变化
- 插入类似,因此,B+ 树的插入/删除效率更高
- 范围查找:
- B+ 树的叶子节点通过链表进行连接,只要找到第一个符合的数据,一直通过链表往后查找即可,不需要回到非叶子节点了。
- B 树需要不断的回到非叶子节点。
- Innodb 使用的 B+ 树,使用双向链表进行连接,左右遍历都方便,更加适合不同场景的范围查找
单表数据
MySQL 为了提高性能,会将表的索引装载到内存中,在 InnoDB buffer size 足够的情况下,其能完成全加载进内存,查询不会有问题。
但是,当单表数据库到达某个量级的上限时,导致内存无法存储其索引,使得之后的 SQL 查询会产生磁盘 IO,从而导致性能下降,所以增加硬件配置(比如把内存当磁盘使),可能会带来立竿见影的性能提升哈。
- 索引结构不会影响单表最大行数,2000W 也只是推荐值,超过了这个值可能会导致 B + 树层级更高,影响查询性能。
索引失效
- 使用左模糊匹配,即
like %xx或者like %xx%这两种- 索引的排序失效
- 当我们在查询条件中对索引列进行变换。
- 1、使用函数,可以使用函数索引避免失效
- 2、进行表达式计算
- 3、进行隐式类型转换,MySQL 在遇到字符串和数字比较的时候
- 会把字符串转为数字,然后再进行比较。如果字符串是索引列,会失效。
- 联合索引要能正确使用需要遵循最左匹配原则,不需要 where 里面按照最左匹配,因为优化器会优化排序
- 在 WHERE 子句中,OR 中有非索引列,即使另一个是索引也失效。
从这个思考题我们知道了,使用左模糊匹配(like "%xx")并不一定会走全表扫描,关键还是看数据表中的字段。
如果数据库表中的字段只有主键 + 二级索引,那么即使使用了左模糊匹配,也不会走全表扫描(type=all),而是走全扫描二级索引树 (type=index)。
再说一个相似,我们都知道联合索引要遵循最左匹配才能走索引,但是如果数据库表中的字段都是索引的话,即使查询过程中,没有遵循最左匹配原则,也是走全扫描二级索引树 (type=index),比如下图:
count()
一个聚合函数:统计符合查询条件(where)的记录中,指定参数不为 NULL 的记录有多少个
- 参数可以是字段名,也可以是其他任意表达式
- 如果是
*或者 1 这种常量,那么直接统计行号,相当于用常量填充每一列,一定不为 NULL*和 1 这种基本一致,但是可能不同的(InnoDB是相同的)
- 如果是字段,那么需要逐行读取该列的值,因为有 NULL 值,不能被统计
- 主键索引 >= 二级索引 >= 普通字段
- 因为主键索引可以选择一个最小的索引树(二级索引),那么每次读取的内容最少,IO 次数少
- 如果是非 null 字段,不会读取行内容,等于少一个 if 判断
- 如果带了 where 条件,会尽量走 where 的字段
- 如果是 MyISAM 引擎,会存一个总行数的值,所以不加 where 的话,只需要 O(1)时间复杂度统计行数
- InnoDB 因为支持事务,每个事务行数不同,所以使用遍历方式统计
- 如果加了 where,MyISAM 也会进行扫描
- 但是如果表很大,count() 还是很耗时
- 如果不是需要很准确的,可以通过
explain select count(*) from t_order - 如果需要准确值,可以通过另外一个表进行记录,但是需要额外维护
- 通过消费 binlog 将数据导入 hive,在 hive 中做查询
- 如果不是需要很准确的,可以通过
问题
高并发场景下,唯一索引偶尔出现插入重复数据问题
原因
- 1、如果唯一索引允许为 NULL,数据库会将多个 NULL 视为不重复
- 字段禁止为 NULL
- 2、唯一索引是联合字段,插入缺失一些字段(应该不算重复)
- 3、先查询后插入,有滞后性,错误未捕获而不回滚可能会导致重复
- 4、隔离级别低于可重复读,两个事务并发插入,可能引发冲突
- INSERT ... ON DUPLICATE KEY UPDATE 原子操作避免 3、4
- 5、分库分表场景下,唯一索引只作用于单表,不同表会有重复数据
- 分布式锁
