4索引
B+作为索引数据结构优点:树矮胖IO次数少,范围查找效率高
常见索引模型
- hash
- 有序数组
- 其他
索引类型
- 聚集索引、非聚集索引
- 普通索引、唯一索引
- 全文索引
- 空间索引 MyISAM
默认索引的最大长度为767字节
MySQL5.7及以上版本中,innodb_large_prefix,可增加到3072字节(前提条件使用DYNAMIC或COMPRESSED 行格式)
超过最大限制可能创建索引失败,索引性能差
使用 Barracuda 文件格式。
- hash
区间搜索效率低,适合等值查询场景 - 有序数组
数组插入移动数据成本高,适合静态数据存储,等只查询和范围查询性能优秀 - 搜索树
二叉树存储树高较高,受限于IO效率,100个节点树高大概20.
InnoDB 1200叉,存储1200^3=17亿,17亿树高3
B+ 相对二叉树减少单次查询IO访问次数 - 其他
跳表、LSM树
InnoDB索引模型
- 主键索引(聚簇索引 clustered index)
叶子存储整行数据 - 非主键索引(二级索引 secondary index)
叶子存储聚集索引值(若是int只需要4字节,若是UID则需要64字节),需要再次查询聚集索引 即回表;主键的长度越小,普通索引的叶节点越小,占用空间越小
索引维护
数据页满再插入需申请新的数据页,挪动部分数据过去,称为页分裂(性能会受到影响)
页分裂空间利用率下降大概50%
删除数据导致利用率很低会进行合并
选择递增主键可减少页分裂。
覆盖索引
索引包含查询结果的所有字段(避免回表)即可成为覆盖索引,单字段也可称为覆盖索引。
前缀索引(最左原则)
索引字段ABC
查询条件AB,命中
查询条件AC ,命中A
查询条件B,不命中
查询条件BC,不命中
索引下推
MySQL 5.6 引入的索引下推优化(index condition pushdown), 在索引遍历过
程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。
其他
读取数据时按页读入内存,每页大小16K
索引数据页内部通过有序数组二分法定位具体行
问题
如果你要重建索引 k,两个 SQL:
alter table T drop index k;
alter table T add index(k);
重建主键索引,这么写:
alter table T drop primary key;
alter table T add primary key(id);
对于上面这两个重建索引的作法,说出你的理解。如果有不合适的,为什么,更好的方法是什么?
重建索引k的做法合理,可以省空间。但重建主键不合理。不论是删除主键还是创建主键,都会将表重建。连着执行这两个语句,第一个就白做了。可使用alter table T engine=InnoDB代替
执行计划
重点关注 type、rows、key 和 Extra字段
type
MySQL 如何查找数据
system:表只有一行数据(系统表)。
const:通过主键或唯一索引查找,结果只有一行。
eq_ref:连接查询时,使用主键或唯一索引。
ref:使用非唯一索引查找。
range:使用索引范围查找。
index:全索引扫描。
ALL:全表扫描(性能最差)。
Extra
链接查询什么都没有写表示用NLJ?
- using filesort 使用了文件排序(性能较差)。
结果列数据大小超过分配的内存大小sort_buffer_size - using index condition 索引下推
- using index 索引覆盖 (直接从索引中获取数据,无需回表,减少随机IO)。
性能高
索引维护有成本,所有字段都建索引并不一定高效 - Using where:使用了 WHERE 条件过滤数据。
- Using temporary:使用了临时表(常见于排序或分组)。
- Using join buffer (Block Nested Loop):BNL,使用了连接缓冲区,需优化
rows
预估行数
filtered
条件过滤百分比(条件有效性)
越高越好,
filtered = (符合条件的行数 / 描行数row ) * 100
使用索引时扫描行数和符合条件行数接近,filtered会越大。filtered太小时排查索引是否命中。
普通索引和唯一索引,应该怎么选择?
如身份证号码建索引如何选择呢?
- 查询性能
微乎其微 - 更新性能
普通索引数据更新使用changebuffer存储操作记录,减少读取磁盘到内存的IO性能消耗
唯一索引数据更新要判断是否冲突,必须将磁盘数据读入内存,当数据不在内存时有IO消耗
写多读少系统(账单、日志),可充分发挥changebuffer的作用
读多的系统,写入后立即需要读取,需将磁盘数据读入内存,再应用缓存的操作,不会提升性能,还增加了维护changebuffer的代价。
change buffer
延迟合并减少IO,机械磁盘效果大
跟新数据页时,数据已存在内存中则直接更新内存
数据不在内存中,将缓存操作记录在change buffer(避免IO读),当对应的IO页加载到内存时,应用change buffer中的相关操作,写redo log ,此时merge结束,后续再刷入磁盘
适用于写多读少场景
change buffer占用buffer pool内存,不能无限增大
innodb_change_buffer_max_size设置
区别redo log 的WAL 是为了减少写入IO,changebuffer减少读取IO
可能引发的问题:对唯一索引数据大批量插入时缓存不命中导致缓存命中率低,系统阻塞。
MySQL为什么有时候会选错索引?
索引选择判断条件(优化器):扫描行数、排序、回表、临时表
扫描行数统计:采样估算统计”基数”(不准确会导致使用错误索引,analyze table t重新统计修复)
不同值越多,索引区分度越好,不同值的个数称为“基数”
InnoDB默认选择N个数据页,统计页面上的不同值,得到一个平均值,乘以索引的页面数,得到索引的基数。
动态更新,变更行数超过1/M时自动触发重新统计
两种存储索引统计的方式,可以通过设置参数 innodb_stats_persistent的值选择:
- 设置为 on 的时候,表示统计信息会持久化存储。这时,默认的 N 是 20,M 是 10。
- 设置为 off 的时候,表示统计信息只存储在内存中。这时,默认的 N 是 8,M 是 16。
mysql索引选择异常处理(如何让mysql选择正确索引)
- force index
- 考虑修改语句,引导 MySQL 使用我们期望的索引。
- 新建一个更合适的索引,来提供给优化器做选择
- 删掉误用的索引
怎么给字符串加索引?
- 前缀索引
- 倒排
- 添加hash字段
前缀索引
使用前缀索引,定义好长度,即节省空间,又不增加查询成本。
索引长度长,占用空间大,数据页存放的值少,效率相对低。
如何定义好长度呢?
通过下面语句查询不同前缀,可查询到的数据量,设置可接受损失比例,如5%,在L4-L7中选择合适长度即可
1 | select |
前缀索引的影响
- 扫描行数
- 索引覆盖
需要回表,即使前缀索引包含整个字符串也会回表,因为InnoDB不知道。
倒序存储与hash
- 存储额外空间
hash需额外字段存储
倒序存储,不需要额外空间 - CPU消耗
hash调用crc32函数
倒序调用reverse,消耗小点 - 查询效率
hash性能更稳定,冲突概率小,扫描行数接近1
倒序,使用前缀,增加扫描行数
小结
- 完整索引,比较占用空间;
- 前缀索引,节省空间,但增加查询扫描次数,且不能使用覆盖索引;
- 倒序存储,再创建前缀索引,用于绕过字符串本身前缀的区分度不够的问题;
- 创建hash字段索引,查询性能稳定,有额外的存储和计算消耗,跟第三种方式一样,都不支持范围扫描。
问题
如果你在维护一个学校的学生信息数据库,学生登录名的统一格式是”学号@gmail.com”, 而学号的规则是:十五位的数字,其中前三位是所在城市编号、第四到第六位是学校编号、第七位到第十位是入学年份、最后五位是顺序编号。
系统登录的时候都需要学生输入登录名和密码,验证正确后才能继续使用系统。就只考虑
登录验证这个行为的话,你会怎么设计这个登录名的索引呢?
为什么sql语句逻辑相同性能差异很大?
对索引字段做函数操作,优化器就放弃走树搜索功能(函数操作可能会破坏索引值的有序性)
隐式类型转换也是隐含了函数操作所以可能导致放弃索引
类型装换方式:转换为范围较大的类型
若索引字段类型范围小就会导致索引字段类型转换,优化器会放弃索引。若条件字段类型范围小则会继续使用索引。
索引不命中
- force index
- 重建索引