高性能的索引策略
索引是最佳的解决方案吗
索引并不总是最好用的工具
只有在帮助存储引擎快速查找到记录的好处大于带来的额外工作时,索引才是有效的
① 对于小表,大部分时间全表扫描更高效
② 对于中到大型表,索引是有效的
③ 对于特大型表,建立和使用索引的代价随之增长,最好使用分区技术(直接区分查询需要的一组数据,而非一条一条记录匹配)
不同数据结构的索引
一. B-Tree 索引(对索引列顺序存储,适合查找范围数据)
关于 B-Tree 索引的知识回顾 :若某表有一个多列索引 idx(a,b,c) ,那么如下的 Where 条件是否可以走索引,如何走索引?
select * from instance where a=xx and b>xx and c=xx;
答案 :可以走索引,a 和 b 字段可以走索引,c 无法走索引
该 SQL 语句的 Where 条件已经符合最左前缀匹配原则,此处不叙述相关内容
如果查询有某个列的范围查询,那么此列右面(当前例子中即 b 右面)的字段查询无法走索引
二. Hash 索引(哈希索引,Memory 存储引擎支持)
基于哈希表实现,对所有索引列计算得出一个哈希码(hash code)
MySQL 中只有 Memory 存储引擎支持哈希索引,也是 Memory 存储引擎的默认索引类型
三. R-Tree 索引(空间数据索引,MyISAM 存储引擎支持)
四. FullText 索引(全文索引)
全文索引是一种特殊的索引,查找的是文本中的关键词,而不是比较索引中的值(更类似于搜索引擎,而不是 Where 匹配)
全文索引注意更多的细节,停用词、词干、复数、布尔搜索等
在相同的列上创建 B-Tree 索引和 FullText 索引不会有冲突,全文索引适用于 Match Against 匹配,而非 Where 匹配
B-Tree 索引的优点
减少服务器需要扫描的行的数量(通过索引获得回表查询的指针,或实现索引覆盖)
帮助服务器避免排序和临时表(B-Tree 索引按顺序存储,可以 Oder By 和 Group By 操作)
可以将随机 IO 变成顺序 IO(B-Tree 索引按顺序存储,可以 Oder By 和 Group By 操作)
哈希索引(Memory)
哈希索引基于哈希表实现,MySQL 中只有 Memory 存储引擎支持哈希索引
哈希索引不是按照索引值顺序存储的,无法按照字段的顺序进行排序
哈希索引只支持等值比较查询,包括 = 、IN() 、<=> (注意和 <> 不同,<=> 个人理解可以类比做 IS),不支持任何范围查询
哈希索引维护麻烦,如果在某个选择性很低(哈希冲突很高)的字段上建立哈希索引,当在表中删除一行记录时,存储引擎要遍历哈希索引中哈希值链表的每一行,找到并删除对应的行的引用,冲突越多,代价越大
自适应哈希索引(InnoDB)
InnoDB 存储引擎有一个特性,叫自适应哈希索引
某些索引值被使用的非常频繁时,会在内存中基于 B-Tree 索引之上,再建立一个哈希索引
这让 B-Tree 索引也具有了哈希索引的一些优点,比如快速的哈希查找
这是一个完全自动、内部的行为,用户无法控制或者配置,但可以关闭该功能
索引优化案例 - 长字段索引如何查的更快?
案例 :某表存储了大量长字段的 URL,业务需要根据 URL 进行查找,URL 上建立了索引
# 存储大量的 URL, 而且需要根据 URL 进行搜索和查找
mysql> select id from url where url="http://www.mysql.com";
# 使用 B-Tree 存储 URL ,存储的内容很大,因为 URL 很长,查询速度就很慢
解决方案 :删除原 URL 上建立的索引,新增 一个被索引的 url_crc 列,使用 CRC32 哈希
mysql> select id from url where url="http://www.mysql.com"
-> and url_crc=CRC32("http://www.mysql.com");
# 这样做性能非常高,MySQL 优化器会使用这个选择性很高、体积很小的基于 url_crc 列的索引完成查找
# CRC32 获取的是 32 位整数,当数据量大于 93000 之后,有 1% 的概率产生哈希冲突
# 为避免哈希冲突,将字段值和对应 CRC32() 哈希值一起写到 Where 条件中即可
该情况下是伪哈希索引,索引的结构其实还是 B-Tree ,这个 B-Tree 的叶子节点只包含哈希值和行指针,而不存储字段值,不能避免读取行,但内存中读取行较快,对性能的影响不明显
哈希索引维护,需要创建一个触发器
delimiter $$
-- 插入时对 url_crc 字段进行赋值
create trigger pseudohash_crc_ins
before insert on pseduhash
for each row begin
set NEW.url_crc=crc32(NEW.url);
end$$
-- 更新时对 url_crc 字段进行更新
create trigger pseudohash_crc_upd
before update on pseduhash
for each row begin
set NEW.url_crc=crc32(NEW.url);
end$$
delimiter ;
关于多列索引和多个单列索引
首先,多个单列索引是不推荐的
如下示例中,如果是两个索引,将对两个单列索引进行扫描,将结果进行合并
算法无非有三种 : OR 条件的联合(union)、AND 条件的相交(intersection)、以及前两者的情况都包括的联合和相交
# 有两个单列索引 film_id 和 actor_id
mysql> select film_id,actor_id from ethan.film_actor
-> where film_id = 1 and actor = 1;
# 这种情况下,就应该创建一个多列索引了
# OR 条件的联合需要花费大量的 CPU、内存 在算法的缓存、排序和合并操作上
# 多个单列索引的查询成本(消耗CPU和内存资源),甚至不如全表扫描
多列索引选择合适的索引顺序
选择性较高的,需要放在多列索引的前面
mysql> select count(*),sum(age=25),sum(region="河北省") from itzone_m1;
+----------+-------------+-------------------------+
| count(*) | sum(age=25) | sum(region="河北省") |
+----------+-------------+-------------------------+
| 10000 | 56 | 297 |
+----------+-------------+-------------------------+
1 row in set (0.01 sec)
# 类似这种,如果想要建立 age、region 字段的联合索引,就应该把 age 放在前面,重复值较少,选择性较高
# 多列索引的第一列,若接近于总行数 count(*) ,也代表着这个索引没什么用
InnoDB 与 MyISAM 数据分布
禁止使用 UUID 作为主键
要避免随机的(不连续、值的范围分布很大)聚集索引
使用 UUID 作为聚集索引的主键,使用 UUID 作为聚集索引,数据的插入时随机的,使数据没有任何的聚集特性