索引代价

空间上的代价

一个索引都对应一棵B+树,树中每一个节点都是一个数据页,一个页默认会占用16KB的存储空间,所以一个索引也是会占用磁盘空间的。

时间上的代价

索引是对数据的排序,那么当对表中的数据进行增、删、改操作时,都需要去维护修改内容涉及到的B+树索引。所以在进行增、删、改操作时可能需要额外的时间进行一些记录移动,页面分裂、页面回收等操作来维护好排序。

B+树索引实战

以下示例是如下数据:

CREATE TABLE t1(
a int PRIMARY KEY,
b INT,
c INT,
d INT,
e VARCHAR(20)
) ENGINE = INNODB; INSERT into t1 VALUES(4,3,1,1,'d');
INSERT into t1 VALUES(1,1,1,1,'a');
INSERT into t1 VALUES(8,8,8,8,'h');
INSERT into t1 VALUES(2,2,2,2,'b');
INSERT into t1 VALUES(5,2,3,5,'e');
INSERT into t1 VALUES(3,3,2,2,'c');
INSERT into t1 VALUES(7,4,5,5,'g');
INSERT into t1 VALUES(6,6,4,4,'f');

其中字段b,c,d建立联合索引

全值匹配

select * from t1 where b = 1 and c = 1 and d = 1;

查询优化器会分析这些查询条件并且按照可以使用的索引中列的顺序来决定先使用哪个查询条件。

匹配左边的列

select * from t1 where b = 1;
select * from t1 where b = 1 and c = 1;

下面这个sql是用不到索引的

select * from t1 where c = 1;

因为B+树先是按照b列的值排序的,在b列的值相同的情况下才使用c列进行排序,也就是说b列的值不同的记录中c的值可能是无序的。而现在你跳过b列直接根据c的值去查找,这是做不到的。

匹配列前缀

如果只给出后缀或者中间的某个字符串,比如:

select * from t1 where b like '%101%';

这种是用不到索引的,因为字符串中间有'101'的字符串并没有排好序,所以只能全表扫描了。有时候我们有一些匹配某些字符串后缀的需求,比方说某个表有一个url列,该列中存储了许多url:

www.baidu.com
www.google.com
www.qq.com

假设已经对该url列创建了索引,如果我们想查询以com为后缀的网址的话可以这样写查询条件:WHERE url LIKE '%com',但是这样的话无法使用该url列的索引。为了在查询时用到这个索引而不至于全表扫描,我们可以把后缀查询改写成前缀查询,不过我们就得把表中的数据全部逆序存储一下,也就是说我们可以这样保存url列中的数据:

moc.udiab.www
moc.elgoog.www
moc.qq.www

这样再查找以com为后缀的网址时搜索条件便可以这么写:WHERE url LIKE 'moc%',这样就可以用到索引了。

匹配范围值

select * from t1 where b > 1 and b < 20000;

由于B+树中的数据页和记录是先按b列排序的,所以我们上边的查询过程其实是这样的:

  • 找到b值为1的记录。
  • 找到b值为20000的记录。
  • 由于所有记录都是由链表连起来的(记录之间用单链表,数据页之间用双链表),所以他们之间的记录都可以很容易的取出来
  • 找到这些记录的主键值,再到聚簇索引中回表查找完整的记录。

不过在使用联合进行范围查找的时候需要注意,如果对多个列同时进行范围查找的话,只有对索引最左边的那个列进行范围查找的时候才能用到B+树索引,比如:

select * from t1 where b > 1 and c > 1;

上边这个查询可以分成两个部分:

  1. 通过条件b > 1来对b进行范围,查找的结果可能有多条b值不同的记录,

  2. 对这些b值不同的记录继续通过c > 1继续过滤。

这样子对于联合索引来说,只能用到b列的部分,而用不到c列的部分,因为只有b值相同的情况下才能用c列的值进行排序,而这个查询中通过b进行范围查找的记录中可能并不是按照c列进行排序的,所以在搜索条件中继续以c列进行查找时是用不到这个B+树索引的。

精确匹配某一列并范围匹配另一列

对于同一个联合索引来说,虽然对多个列都进行范围查找时只能用到最左边那个索引列,但是如果左边的列是精确查找,则右边的列可以进行范围查找,比方说这样:

select * from t1 where b = 1 and c > 1;

排序

select * from t1 order by b, c, d;

这个查询的结果集需要先按照b值排序,如果记录的b值相同,则需要按照c来排序,如果c的值相同,则需要按照d排序。因为这个B+树索引本身就是按照上述规则排好序的,所以直接从索引中提取数据,然后进行回表操作取出该索引中不包含的列就好了。

分组

select b, c, d, count(*) from t1 group by b, c, d;

这个查询语句相当于做了3次分组操作:

  1. 先把记录按照b值进行分组,所有b值相同的记录划分为一组。

  2. 将每个b值相同的分组里的记录再按照c的值进行分组,将title值相同的记录放到一个分组里。

  3. 再将上一步中产生的分组按照d的值分成更小的分组。

如果没有索引的话,这个分组过程全部需要在内存里实现,而如果有索引的话,正好这个分组顺序又和B+树中的索引列的顺序是一致的,所以可以直接使用B+树索引进行分组。

使用联合索引进行排序或分组的注意事项

对于联合索引有个问题需要注意,ORDER BY的子句后边的列的顺序也必须按照索引列的顺序给出,如果给出order by c, b, d 的顺序,那也是用不了B+树索引的。

同理, order by b?order by b, c 这种匹配索引左边的列的形式可以使用部分的B+树索引。当联合索引左边列的值为常量,也可以使用后边的列进行排序,比如这样:

select * from t1 where b = 1 order by c, d;

这个查询能使用联合索引进行排序是因为b列的值相同的记录是按照c, d排序的。

不可以使用索引进行排序或分组的几种情况

ASC、DESC混用

对于使用联合索引进行排序的场景,我们要求各个排序列的排序顺序是一致的,也就是要么各个列都是ASC规则排序,要么都是DESC规则排序。

select * from t1 order by b ASC, c DESC;

这个查询是用不到索引的。

如何建立索引

考虑索引的选择性

索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数的比值:

选择性 = 基数 / 记录数

选择性的取值范围为(0, 1],选择性越高的索引价值越大。如果选择性等于1,就代表这个列的不重复值和表记录数是一样的,那么对这个列建立索引是非常合适的,如果选择性非常小,那么就代表这个列的重复值是很多的,不适合建立索引。

考虑前缀索引

用列的前缀代替整个列作为索引key,当前缀长度合适时,可以做到既使得前缀索引的选择性接近全列索引,同时因为索引key变短而减少了索引文件的大小和维护开销。

使用mysql官网提供的示例数据库:https://dev.mysql.com/doc/employee/en/employees-installation.html
github地址:https://github.com/datacharmer/test_db.git

employees表只有一个索引,那么如果我们想按名字搜索一个人,就只能全表扫描了:

EXPLAIN SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido';

那么可以对或建立索引,看下两个索引的选择性:

SELECT count(DISTINCT(first_name))/count(*) AS Selectivity FROM employees.employees; -- 0.0042
SELECT count(DISTINCT(concat(first_name, last_name)))/count(*) AS Selectivity FROM employees.employees; -- 0.9313

显然选择性太低,选择性很好,但是first_name和last_name加起来长度为30,有没有兼顾长度和选择性的办法?

可以考虑用first_name和last_name的前几个字符建立索引,例如,看看其选择性:

SELECT count(DISTINCT(concat(first_name, left(last_name, 3))))/count(*) AS Selectivity FROM employees.employees; -- 0.7879

选择性还不错,但离0.9313还是有点距离,那么把last_name前缀加到4:

SELECT count(DISTINCT(concat(first_name, left(last_name, 4))))/count(*) AS Selectivity FROM employees.employees; -- 0.9007

这时选择性已经很理想了,而这个索引的长度只有18,比短了接近一半,建立前缀索引的方式为:

ALTER TABLE employees.employees ADD INDEX `first_name_last_name4` (first_name,last_name(4));

前缀索引兼顾索引大小和查询速度,但是其缺点是不能用于ORDER BY和GROUP BY操作,也不能用于覆盖索引。

总结

  • 索引列的类型尽量小
  • 利用索引字符串值的前缀
  • 主键自增
  • 定位并删除表中的重复和冗余索引
  • 尽量使用覆盖索引进行查询,避免回表带来的性能损耗。
  • 最左前缀匹配原则。这是非常重要、非常重要、非常重要(重要的事情说三遍)的原则,MySQL会一直向右匹配直到遇到范围查询 (>,<,BETWEEN,LIKE)就停止匹配
  • 尽量选择区分度高的列作为索引,区分度的公式是 COUNT(DISTINCT col)/COUNT(*)。表示字段不重复的比率,比率越大我们扫描的记录数就越少。
  • 索引列不能参与计算,尽量保持列“干净”。比如, FROM_UNIXTIME(create_time)='2016-06-06' 就不能使用索引,原因很简单,B+树中存储的都是数据表中的字段值,但是进行检索时,需要把所有元素都应用函数才能比较,显然这样的代价太大。所以语句要写成 : create_time=UNIX_TIMESTAMP('2016-06-06')。
  • 尽可能的扩展索引,不要新建立索引。比如表中已经有了a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。

Mysql之B+树索引实战的更多相关文章

  1. SQL优化 MySQL版 - B树索引详讲

    SQL优化 MySQL版  - -B树索引详讲 作者:Stanley 罗昊 [转载请注明出处和署名,谢谢!] 为什么要进行SQL优化呢?很显然,当我们去写sql语句时: 1会发现性能低 2.执行时间太 ...

  2. MySQL的B+树索引和hash索引的区别

    简述一下索引: 索引是数据库表中一列或多列的值进行排序的一种数据结构:索引分为聚集索引和非聚集索引,聚集索引查询类似书的目录,快速定位查找的数据,非聚集索引查询一般需要再次回表查询一次,如果不使用索引 ...

  3. 搞懂MySQL InnoDB B+树索引

    一.InnoDB索引 InnoDB支持以下几种索引: B+树索引 全文索引 哈希索引 本文将着重介绍B+树索引.其他两个全文索引和哈希索引只是做简单介绍一笔带过. 哈希索引是自适应的,也就是说这个不能 ...

  4. MySQL的B树索引与索引优化

    MySQL的MyISAM.InnoDB引擎默认均使用B+树索引(查询时都显示为"BTREE"),本文讨论两个问题: 为什么MySQL等主流数据库选择B+树的索引结构? 如何基于索引 ...

  5. MySQL之B+树索引(转自掘金小册 MySQL是怎样运行的,版权归作者所有!)

    每个索引都对应一棵B+树,B+树分为好多层,最下边一层是叶子节点,其余的是内节点.所有用户记录都存储在B+树的叶子节点,所有目录项记录都存储在内节点. InnoDB存储引擎会自动为主键(如果没有它会自 ...

  6. MySQL中B+树索引的使用

    1)         不同应用中B+树索引的使用 对于OLTP应用,由于数据量获取可能是其中一小部分,建立B+树索引是有异议时的 对OLAP应用,情况比较复杂,因为索引的添加应该是宏观的而不是微观的. ...

  7. mysql性能优化之索引优化

    作为免费又高效的数据库,mysql基本是首选.良好的安全连接,自带查询解析.sql语句优化,使用读写锁(细化到行).事物隔离和多版本并发控制提高并发,完备的事务日志记录,强大的存储引擎提供高效查询(表 ...

  8. Mysql:索引实战

    MySQL主要提供2种方式的索引:B-Tree索引,Hash索引 B树索引具有范围查找和前缀查找的能力,对于有N节点的B树,检索一条记录的复杂度为O(LogN).相当于二分查找. 哈希索引只能做等于查 ...

  9. MySQL:InnoDB存储引擎的B+树索引算法

    很早之前,就从学校的图书馆借了MySQL技术内幕,InnoDB存储引擎这本书,但一直草草阅读,做的笔记也有些凌乱,趁着现在大四了,课程稍微少了一点,整理一下笔记,按照专题写一些,加深一下印象,不枉读了 ...

随机推荐

  1. MySQL快速入门(二)

    目录 MySQL快速入门(二) 约束条件 自增 自增的特性 主键 外键 级联更新/删除 表与表之间的关系 外键约束 操作表方法 查询关键字 练习数据 select··from where 筛选 gro ...

  2. 使用讯飞tts+ffmpeg自动生成视频

    参考 FFmpeg 讯飞离线语音合成 起因 某日,看到一个营销号的视频说做视频日进斗金,大意是用软件识别文章小说,搭配一些图片转换成自己的视频.看完当时脑海里冒出一个念头,我也可以,于是有了这番尝试. ...

  3. 2021年都要过去啦,你还在用Excel做数据可视化效果吗?

    2021年都要过去啦,你还在用Excel做数据可视化效果吗?古语有云,"工欲善其事,必先利其器",没有专业的工具,前期准备的再好也是白搭.现在运用数据可视化工具于经营活动中的企业是 ...

  4. 2021年BI软件系统推荐,知名商业智能厂商品牌

    国内外一直有一些厉害的商业智能厂商,在国外,例如国外微软的PowerBI.在国外是商业智能的行业领导者,在国外的市场占有率上远远领先其它产品,然而在中国市场却落后于国内商业智能厂商思迈特软件的Smar ...

  5. 【C# IO 操作 】Span<T>类

    Span 原理探究 ref结构 ref struct是仅在堆栈上的值类型: 表现一个顺序结构的布局:(译注:可以理解为连续内存) 只能在堆栈上使用.即用作方法参数和局部变量: 不能是类或正常结构的静态 ...

  6. WPF中RichTextBox中添加文字的两种方法

    RichTextBox控件不同于TextBox控件,后者可以直接通过其Text属性绑定或者在后台动态添加文字. (一)使用数据绑定 <RichTextBox FontSize="12& ...

  7. netty系列之:EventExecutor,EventExecutorGroup和netty中的实现

    目录 简介 EventExecutorGroup EventExecutor EventExecutorGroup在netty中的基本实现 EventExecutor在netty中的基本实现 总结 简 ...

  8. Oracle账户被锁:the account is locked

    转至:https://blog.csdn.net/weixin_37615080/article/details/80400239?utm_medium=distribute.pc_relevant_ ...

  9. MySQL之InnoDB存储引擎 - 读书笔记

    1. MySQL 的存储引擎 MySQL 数据库的一大特色是有插件式存储引擎概念.日常使用频率最高的两种存储引擎: InnoDB 存储引擎支持事务,其特点是行锁设计.支持外键.非锁定读(默认读取操作不 ...

  10. 【战略】以色列公司的数据驱动tip

    Eric Rapps:我们的团队获得了大量的收入,并且持续保持着增长.如果获得我们的投资,实际上是你是获得了和相关领域技术积累.专家沟通的支持.但更重要的是,你可以近距离地接触我们的运营资源,您可以直 ...