3.4 Btree索引的限制
- 若不是从索引的最左列开始查找,则无法使用索引
- 使用索引时不能跳过索引中的列
- Not in和<>操作无法使用索引
- 若查询中有某列的范围查询,则其右边所有列都无法使用索引
3.4.1 即使设置索引,也无法使用
- “%”开头的LIKE语句,模糊匹配
- OR语句,前后没有同时使用索引
- 数据类型出现隐式转化(如varchar不加单引号,可能会自动转int型)
3.4.2 索引选择性与前缀索引
既然索引可加速查询,是否只要是查询语句,就建索引?
NO!因为索引虽然加速查询,但索引也有代价:索引文件本身要消耗存储空间。
索引会加重插入、删除和修改记录时的负担,增加写操作的成本
太多索引会增加查询优化器的分析选择时间
MySQL在运行时也要消耗资源维护索引
但索引绝非银弹,并非越多越好:
不推荐创建索引的场景
- 查询中很少涉及的列
- 例如,在查询中很少使用的列,有索引并不能提高查询的速度,相反增加了系统维护时间和消耗了系统空间
重复值较多的列
“性别”列只有列值“男”和“女”,增加索引并不能显著提高查询的速度
定义为text、image和bit数据类型的列
这些数据类型的数据列的数据量要么很大,要么很小,不利于使用索引
表记录比较少
例如一两千条甚至只有几百条记录的表,没必要建索引,让查询做全表扫描就好了
索引的选择性较低
索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值
Index Selectivity = Cardinality / #T
CREATE INDEX index_ name ON table(col_ name(n));
显然选择性的取值范围为(0, 1],选择性越高的索引价值越大,这是由B+Tree的性质决定的。
例如,上文用到的employees.titles表,如果title字段经常被单独查询,是否需要建索引,我们看一下它的选择性
title的选择性不足0.0001(精确值为0.00001579),所以实在没有什么必要为其单独建索引
前缀索引
用列的前缀代替整列作为索引key,当前缀长度合适时,可实现既使得前缀索引的选择性接近全列索引,又因为索引key变短而减少索引文件的大小和维护开销。
以employees.employees表为例介绍前缀索引的选择和使用。
从图12可以看到employees表只有一个索引<emp_no>
,那么如果我们想按名字搜索人,就只能全表扫描
如果频繁按名字搜索员工,显然效率很低,考虑建索引。
有两种选择,建
- <first_name>
- <first_name, last_name>
看两个索引选择性:
<first_name>显然选择性太低,<first_name, last_name>选择性很好。
但first_name和last_name加起来长度30,有没有兼顾长度和选择性的办法?
可以考虑用first_name和last_name的前几个字符建立索引,例如<first_name, left(last_name, 3)>,看看其选择性
选择性还不错,但离0.9313还是有点距离,那么把last_name前缀加到4
这时选择性已很理想,而该索引长度仅18,比<first_name, last_name>短了一半。
把该前缀索引建上:
ALTER TABLE employees.employees
ADD INDEX first_name_last_name4 (first_name, last_name(4));
再执行一遍按名字查询,比较建索引前的结果
- 性能提升显著,查询加速120多倍。
前缀索引兼顾索引大小和查询速度,但其
缺点
- 不能用于ORDER BY和GROUP BY
- 也不能用于Covering index(即当索引本身包含查询所需全部数据时,不再访问数据文件本身)
索引诗歌
全值匹配我最爱,最左前缀要遵守
带头大哥不能死,中间兄弟不能断
索引列上少计算,范围之后全失效
Like百分写最右,覆盖索引不写*
不等空值还有or,索引失效要少用
字符引号不能丢,SQL高级也不难
3.5 InnoDB的主键选择与插入优化
在使用InnoDB存储引擎时,如果没有特别的需要,请永远使用一个与业务无关的自增字段
作为主键
经常看到有帖子或博客讨论主键选择问题,有人建议使用业务无关的自增主键,有人觉得没有必要,完全可以使用如学号或身份证号这种唯一字段作为主键。不论支持哪种论点,大多数论据都是业务层面的。
如果从数据库索引优化角度看,使用InnoDB引擎而不使用自增主键绝对是一个糟糕的主意
上文讨论过InnoDB的索引实现,InnoDB使用聚集索引,数据记录本身被存于主索引(一颗B+Tree)的叶子节点上。这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放,因此每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,如果页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点)。
如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页。如下图所示:
这样就会形成一个紧凑的索引结构,近似顺序填满
由于每次插入时也不需要移动已有数据,因此效率很高,也不会增加很多开销在维护索引上。
如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置:
此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。
因此,只要可以,请尽量在InnoDB上采用自增字段做主键。