5.3.7 使用索引扫描来做排序
Mysql生成有序结果的两种方法: (详见第7章)
- 排序操作.
- 按索引顺序扫描.
// explain结果中的type,如果为index
,则使用第二种方法,按索引顺序扫描.
按索引顺序扫描工作流程:(Innodb二级索引)
- 顺序扫描索引,获取主键指针;
- 回表查询相应主键的数据行.
如果索引是覆盖索引,则可以避免上述第二步,减少一次磁盘IO,并且避免大范围扫描时的大量随机IO.
正确示例:
索引为(a,b,c)
示例1:
1 | select a,b,c from t |
组合where条件和order by条件,而且都是顺序(ASC). 此外如果组合后是索引的最左前缀,也是可以利用索引进行排序的.
错误示例
示例2:
1 | select a,b,c from t |
由于顺序和建立索引的不同,因此不能利用到索引进行排序.
如果需要按不同方向排序, 可以通过存储这列的相反数或者反转值, 满足这种需求.
示例3:
1 | select a,b,c from t |
由于a是范围查询,因此无法利用索引的其他列(b和c).
5.3.8 压缩(前缀压缩)索引
1 | 仅对于MyISAM引擎. |
MyISAM
使用前缀压缩来减少索引的大小,从而让更多的索引可以放入内存中.
压缩对象:
1
21. 字符串(默认设置)
2. 整数(用参数设置)压缩方法:
1
2
3
4
5
6
7
8
9
101. 完全保存索引块的第一个值;
2. 从第二个值开始,保存:(相同前缀字节数,剩余部分)
例如原始数据为:
1. perform
2. performance
压缩后:
1. perform (完整保存)
2. 7,ance (相同前缀字节数,剩余部分)压缩优点:
使用更少空间;压缩缺点:
某些操作变慢: 因为每个值的压缩前缀都依赖前面的值,所以MyISAM查找时无法二分查找. 正序扫描不受影响,倒序扫描性能变慢.
块中查找某一行的平均耗时: 扫描半个索引块.压缩索引使用场景:
IO密集型查询.(减少传输量,可能只需要1/10的磁盘大小)压缩索引不适用的场景:
CPU密集型查询.(因为扫描需要随机查找)
5.3.9 冗余和重复索引
背景:
MySQL允许在列上创建多个索引.重复索引:
相同列,顺序相同,类型相同的索引.
(例: 同一列上建立主键和唯一索引,事实上唯一限制和主键限制都是通过索引实现的,事实上建立了重复索引)冗余索引:
前缀索引与索引冗余.
(例: 索引(A,B)与索引(A)冗余.)
处理:
- 重复索引应该移除;
- 冗余索引应该考虑移除:
(1) 如果原来的索引已经太长到影响性能,可以保留冗余索引
(2) 如果(1)不成立,应该尽量移除冗余索引.
索引太多的缺点:
- 写入速度变慢;(可以通过禁用索引,然后延迟build索引优化)
- 查询时mysql需要选择索引,索引多的话需要比较的索引也变多,影响查询计划的
生成速度. - 存储索引成本增加.
5.3.10 未使用的索引
检查各个索引的使用频率:
- 打开userstates参数;(默认是关闭的)
- mysql服务器正常运行一段时间;(可以考虑使用benchmark)
- 查询Information_schema.Index_statistics,查到每个索引的使用频率.
5.3.11 索引和锁
1 | 以下讨论Innodb引擎 |
索引可以让查询锁定更少的行:
- 减少内存使用;
- 减少锁争用,提高并发度.
早期Mysql:
事务提交后才释放锁;新版本Mysql:
服务器端过滤掉行后,释放没用到的行锁.示例
1
2
3
4
5
6
7
8
9
10Set autoCommit=0;-- 默认是每句都是一个事务
-- 这里改成显式提交。
BEGIN;
Explain
select actor_id from t
where actor_id<5 and actor_id!=1
FOR Update; -- 加上排他锁
Extra列:
Using Where;Using index从Extra列可以看出,虽然使用了索引,但依然有
Using Where
,意思是使用索引从存储引擎获取到一些数据行,传送给服务器后,依然要使用Where条件进行过滤。因此这个查询锁住的并不只是id为2-4的行。
(TODO: 验证锁住的范围,以及索引类型是否为主键的影响)
Innodb不同索引对应的锁:
- 主键索引: 排他锁(写锁);
- 二级索引: 共享锁(读锁)。
5.4 索引案例
5.4.1 支持多种过滤条件
也就是支持Where条件。
作为过滤条件的列需要考察的3个属性,按重要性如下:
- 查询频率;
- 相关查询是否是范围查询,或者列的取值是否可枚举。
- 选择性。
设计索引的原则:
- 查询频率高,则应该加入索引;
- 如果是范围查询,则如果作为联合索引的一列,应该放在后面;(因为最左前缀无法利用范围查询后的列)
如果是可枚举列,则查询语句中可以使用In (‘male’,’female’)绕过列顺序限制,让Mysql始终能够应用相关索引; - 满足前两个条件后,应该把选择性高的列放在联合索引的前面。
案例:
查询频率高的列,组成联合索引:
(sex,country,age)
由于sex的枚举值少,可以放在最前面。
由于age值一般是范围查询,所以应该放在最后面。
- 注意事项:
- 避免使用多个范围查询;
- 通过枚举列(IN条件),让Mysql尽量使用到索引。
5.4.3 优化排序
示例1:
1
2
3select * from profiles
where sex='M'
order by rating limit 10可以考虑建立索引(sex,rating)来优化查询。
示例2:
1
2
3select * from profiles
where sex='M'
order by rating limit 10000,10这个查询的性能难点在于偏移量太大。
Mysql需要花费大量的时间扫描需要丢弃的数据。
解决方案:
- 预先计算+缓存;
- 覆盖索引+延迟关联。
方案2的代码如下:
1 | select * from profiles AS a |
5.5 维护索引和表
相关内容包括:
- 找到并修复损坏的表;
- 维护准确的索引信息;
- 减少碎片。
5.5.1 找到并修复损坏的表
- 相关命令:
1
2
3
4
5
6
7-- 检查:
Check Table xxx
-- 修复方法1:
Repair Table xxx
-- 修复方法2:(重建表)
Alter table innodb_tbl engine=innodb;
-- 注意此处引擎类型应该与原先一致。
5.5.2 维护索引信息
- 相关命令:
1
2-- 重新生成统计信息:
Analyze Table xxx
Innodb引擎则不在磁盘存储索引统计信息,而是通过随机的索引抽样访问获取信息存储在内存。
相关参数:innodb_stats_sample_pages
设置样本页的数量。
5.5.3 减少索引和数据的碎片
1 | 以下针对B-Tree索引。 |
碎片的类型:
- 行碎片;数据行被分散存储到多个地方。
- 行间碎片: 顺序的行被存储到多个地方;
- 剩余空间碎片: 数据页中有大量空余空间。
相关命令:
1 | Optimize table xxx |