MySQL为数据表建立索引的原则详解


Posted in MySQL onMarch 03, 2022

面试题:

  • 索引是什么?
  • 索引的优点?
  • 索引的缺点?
  • 在建立索引的时候都有哪些需要考虑的因素呢?
  • 为数据表建立索引的原则有哪些?
  • 什么是索引覆盖?
  • 非聚簇索引一定会回表查询吗?

1. 索引是什么?

索引是帮助我们实现快速查询数据库的数据结构。
在很多时候,表中存放的记录非常的多,需要用到好多的数据页来存放这些记录,在很多页中查找记录可以分为2个步骤:

1、定位到记录所在的页

2、从所在页内查找相应的记录

在没有索引的情况下,无论是根据主键列还是其他列的值进行查找,由于我们不能快速的定位到记录所在的页,所以只能从第一页沿着双向链表一直往下找,因为需要遍历所有的数据页,这种方式超级消耗性能。因此人们期望一种能高效完成搜索的方法,因此就出现了索引,索引就是一种数据结构。

2. 索引的优点?

索引可以让服务器快速的定位到表的指定位置 (索引大大减少了服务器需要需要扫描的数据量)。最常见的B-Tree索引,按照顺序存储数据,所以mysql可以来做order bygroup by操作(索引可以帮助服务器避免排序和临时表)。因为数据是有序的,所以B-Tree也就会将相关的列值都存储在一起(索引可以将随机IO变为顺序IO)。因为索引中存储了实际的列值,所以某些查询值使用索引就能完成全部查询。因此,总结下来的索引的优点为:

1、索引大大减少了服务器需要需要扫描的数据量;

2、索引可以帮助服务器避免排序和临时表;

3、索引可以将随机IO变为顺序IO;

3. 索引的缺点?

1、空间上的代价:

建立索引需要占用物理空间

InnoDB存储引擎默认使用的B+树索引,每建立一个索引,都要为它建立一颗B+树,每一颗B+树的每一个节点都是一个数据页,一个数据页默认会占用16KB的存储空间,而一颗很大的B+树由许多数据页组成,这将占用很大的一片存储空间。

2、时间上的代价:

因为每次对表记录进行增删改,需要进行动态维护索引,导致增删改时间变长

每当对表中的数据进行增删改查操作时,都需要修改各个B+树索引,B+树中的每层节点都按照索引列的值从小到大的顺序组成了双向链表,无论是叶子节点中的记录还是非叶子节点中的记录都按照索引列的值从小到大的顺序排成了单向链表。而增删改查操作可能会对节点和记录的排序造成破坏,所以存储引擎需要额外的时间进行页面分裂、页面回收等操作,以维护节点和记录的顺序。

在执行查询语句前,首先要生成一个执行计划,一般情况下,一条查询语句在执行过程中最多使用一个二级索引,因此,在生成执行计划时需要计算使用不同索引执行查询时所需的成本,最后选取成本最小的那个索引执行查询,如果建立了太多的索引,可能会导致成本分析耗时太多,从而影响查询语句的执行性能。

4. 在建立索引的时候,都有哪些需要考虑的因素呢?

在创建和使用索引是应该注意下列事项:

  • 只为用于搜索、排序、分组的列创建索引;
  • 当列中不重复值的个数在总记录条数中的占比很大时,才为列建立索引;
  • 索引列的类型尽量小;
  • 可以只为索引列前缀创建索引,以减少苏呕心占用的存储空间;
  • 尽量使用覆盖索引进行查询,以毕淼回表操作带来的性能损耗;
  • 让索引列以列名的形式单独出现在搜索条件中;
  • 为了尽可能少的让聚簇索引发生页面分裂的情况,建议让主键拥有anto_increment属性;
  • 定位并删除表中的冗余和重复索引;

建立一张数据库表:

create table single_table(
	id int not auto_increment, 
	key1 varchar(100),         
	key2 int,
	key3 varchar(100),
	key_part1 varchar(100),
	key_part2 varchar(100),
	key_part3 varchar(100),
    common_field varchar(100),
	primary key(id),          # 聚簇索引
	key idx_key1(key1),       # 二级索引
	unique key uk_key2(key2), # 二级索引,而且该索引是唯一二级索引
	key idx_key3(key3),       # 二级索引
	key idx_key_part(key_part1,key_part2,key_part3) # 二级索引,也是联合索引
)Engine=InnoDB CHARSET=utf8;

1、只为用于搜索、排序、分组的列创建索引

我们只为出现在where子句中的列、order bygroup by子句中的列、连接子句中的连接列创建索引。仅出现在查询列表中的列就没有必要建立索引了。

比如下面的查询语句:

select common_field,key_part3 from single_table where key1='a';

我们只需要为出现在where子句中的key1建立索引就可以了,而查询列表中的common_field、key_part3这两个列就没有必要建立索引了。

2、索引列的类型尽量小

在定义表结构时,要显式的指定列的类型。以整数类型为例,有tingint、mediumint、int、bigint这几种,他们占用的存储空间的大小依次递增,他们能表示的整数范围当然也是依次递增。如果想要对某个整数类型的列建立索引,在表示的整数范围允许的情况下,尽量让索引列使用较小的类型,比如使用int就不要用bigint,因为数据类型越小,索引占用的存储空间就越少,在一个数据页内就可以存放更多的记录,磁盘IO带来的性能损耗也就越小,读写效率也就越高。

这个建议对表的主键来说更加适用,因为不仅聚簇索引会存储主键值,所有的二级索引的节点都会存储一份记录的主键值,如果主键使用更小的数据类型,也就意味着能节省更多的存储空间。

3、为列前缀建立索引

一个字符串其实是由若干个字符组成的,假如字符串很长,那么存储这个字符串就会占用很多的存储空间。在为这个字符串所在的列建立索引时,就需要在对应的B+树中,把列的完整字符串存储起来,字符串越长,在索引中占用的存储空间越大。

索引列的字符串前缀其实也是排好序的,所以索引的设计人员设计了一个方案,即只将字符串的前几个字符存放到索引中,也就是说二级索引的记录中只保留字符串的前几个字符。

比如可以这样修改idx_key1索引,让索引中只保留字符串的前10个字符:

alert table single_table drop index idx_key1;
alert table single_table add index idx_key1(key1(10));

然后再执行下面的查询语句:

select * from single_table where key1='abcdefghijklmn';

由于在idx_key1的二级索引记录中只保留字符串的前10个字符,所以我们只能定位到前缀为‘abcdefghij'的二级索引记录,在扫描这些二级索引记录时再判断它们是否满足key1='abcdefghijklmn'条件,当列中存储的字符串包含的字符较多时,这种为列前缀建立索引的方式可以明显减少索引大小。

不过,在只对前缀建立索引的情况下,下面这个查询语句就不能使用索引来完成排序需求了:

select * from single_table order by key1 limit 10;

因为二级索引idx_key1中不包含完整的key1列信息,所以在仅使用idx_key1索引执行查询时,无法对key1列前10个字符相同但其余字符不同的记录进行排序,也就是说,只为列前缀建立索引的方式无法支持使用索引进行排序的需求。

4、覆盖索引

为了彻底告别回表操作带来的性能损耗,建议最好在查询列表中只包含索引列,比如这个查询语句:

select key1,id from single_table where key1>'a' and key1<'c';

由于我们只查询key1列和id列的值,所以在使用idx_key1索引来扫描('a','c')区间中的二级索引时,可以直接从获取到的二级索引记录中读出key1列和id列的值,而不需要再通过id值到聚簇索引中执行回表操作了,这样就省去了回表操作带来的性能损耗。

我们把这种索引中已经包含了所有需要读取的列的查询方式称为覆盖索引。如果索引的叶子节点中已经包含要查询的数据,那么还有必要再回表查询呢?如果一个索引包含所有需要查询的字段的值,就称为覆盖索引。

排序操作也优先使用覆盖索引进行查询,比如下面这个查询语句:

select key1 from single_table order by key1;

虽然这个查询语句中没有limit子句,但是由于可以采用覆盖索引,所以查询优化器会直接使用idx_key1索引进行排序,而不需要执行回表操作。

当然,如果业务需要查询索引列以外的列,还是要以保证业务需求为重,如无必要,最好把业务中需要的列放在查询列表中,而不是以简单的*替代。

5、让索引列以列名的形式在搜索条件中单独出现

在下面这两个查询语句中,搜索条件中的语义是一样的:

select * from single_table where key2*2<4;
select * from single_table where key2<4/2;

在第一个查询语句的搜索条件中,key2列并不是以单独列名的形式出现的,而是以key2*2这样的形式表现的,MySQL并不会尝试简化key2*2<4表达式,而是直接认为这个搜索条件不能形成合适的扫描区间来减少需要扫描的记录数量,所以该查询语句只能以全表扫描的方式来执行。

在第二个查询语句的搜索条件中,key2列并是以单独列名的形式出现的,MySQL可以分析出key2<2,这样可以减少需要扫描的记录数量。所以MySQL可能使用uk_key2来执行查询。

所以,如果想让某个查询使用索引来执行,请让索引列以列名的形式单独出现在搜索条件中。

6、新插入记录时主键大小对效率的影响

我们知道,对于一个使用InnoDB存储引擎的表来说,在没有显式创建索引时,表中的数据实际上存储在聚簇索引的叶子节点中,而且B+树的每一层数据页以及页面中的记录都是按照主键值从小到大的顺序排序的,如果新插入记录的主键值是依次递增的话,每插满一个数据页就换到下一个数据页继续插入,如果新插入记录的主键值忽大忽小,就比较麻烦了。

假设某个数据页存储的聚簇索引记录已经满了,它存储的主键值在1~100之间,此时如果再插入一提哦啊主键值为9的记录,因为这个数据页已经满了,新纪录应该插入到哪里呢?

我们需要把当前页面分裂成两个页面,把本页中的一些记录移动到新创建的一些页中,页分裂意味着性能损耗,所以如果想尽量避免这种无谓的性能损耗,最好让插入记录的主键值依次递增。即让主键拥有auto_increment属性,MySQl会自动为新插入的记录生成递增的主键值。

7、冗余和重复索引

针对single_table表,可以单独针对key_part1列建立一个idx_key_part1索引:

alert table single_table and index idx_key_part1(key_part1);

而此时我们已经有了一个针对key_part1、key_part2、key_part3列建立的联合索引idx_key_paridx_key_part索引的二级索引记录本身就是按照key_part1列的值排序的,此时再单独为key_part1列建立一个索引其实是没必要的,我们可以把整个新建的idx_key_part1索引看作一个冗余索引,该冗余索引是没有必要的。

有时,我们可能会对同一个列建立多个索引,比如下面两个添加索引的语句:

alert table single_table add unique key uk_id(id);
alert table single_table add index idx_id(id);

我们针对id列又建立了一个唯一二级索引uk_id,还建立了一个普通二级索引idx_id,可是id列本身就是single_table表的主键,InnoDB自动为该列建立了聚簇索引,此时uk_ididx_id就是重复的,这种重复索引应该避免。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注三水点靠木的更多内容!   

MySQL 相关文章推荐
mysql死锁和分库分表问题详解
Apr 16 MySQL
MySQL 亿级数据导入导出及迁移笔记
Jun 18 MySQL
MYSQL 运算符总结
Nov 11 MySQL
MySQL 开窗函数
Feb 15 MySQL
MySQL七大JOIN的具体使用
Feb 28 MySQL
mysql数据插入覆盖和时间戳的问题及解决
Mar 25 MySQL
MySQL 执行数据库更新update操作的时候数据库卡死了
May 02 MySQL
MySQL 计算连续登录天数
May 11 MySQL
MySQL 语句执行顺序举例解析
Jun 05 MySQL
MySQL事务的ACID特性以及并发问题方案
Jul 15 MySQL
MySQL的表级锁,行级锁,排它锁和共享锁
Jul 15 MySQL
分享很少见很有用的SQL功能CORRESPONDING
Aug 05 MySQL
MySQL数据库完全卸载的方法
千万级用户系统SQL调优实战分享
Mar 03 #MySQL
解析MySQL索引的作用
Arthas排查Kubernetes中应用频繁挂掉重启异常
Feb 28 #MySQL
一文搞懂MySQL索引页结构
MySQL七大JOIN的具体使用
一文弄懂MySQL索引创建原则
You might like
php 函数使用方法与函数定义方法
2010/05/09 PHP
PHP中的array数组类型分析说明
2010/07/27 PHP
全局记录程序片段的运行时间 正确找到程序逻辑耗时多的断点
2011/01/06 PHP
PHP safe_mode开启对于PHP系统函数有什么影响
2020/11/10 PHP
JavaScript判断两种格式的输入日期的正确性的代码
2007/03/25 Javascript
如何快速的呈现我们的网页的技巧整理
2007/07/01 Javascript
在线编辑器中换行与内容自动提取
2009/04/24 Javascript
JQuery从头学起第三讲
2010/07/06 Javascript
jQuery的deferred对象使用详解
2011/08/20 Javascript
jQuery.extend 函数详解
2012/02/03 Javascript
js对象关系图 方便dom操作
2012/03/18 Javascript
ext中store.load跟store.reload的区别示例介绍
2014/06/17 Javascript
JavaScript里实用的原生API汇总
2015/05/14 Javascript
jQuery复制表单元素附源码分享效果演示
2015/09/30 Javascript
Node.js操作mysql数据库增删改查
2016/03/30 Javascript
jQuery添加和删除输入文本框标签代码
2016/05/20 Javascript
微信js-sdk预览图片接口及从拍照或手机相册中选图接口用法示例
2016/10/13 Javascript
vue.js的提示组件
2017/03/02 Javascript
JS闭包可被利用的常见场景小结
2017/04/09 Javascript
jQuery回调方法使用示例
2017/06/26 jQuery
vue实现带过渡效果的下拉菜单功能
2020/02/19 Javascript
JS面向对象编程实现的拖拽功能案例详解
2020/03/03 Javascript
Django objects.all()、objects.get()与objects.filter()之间的区别介绍
2017/06/12 Python
Django model序列化为json的方法示例
2018/10/16 Python
Python爬虫爬取糗事百科段子实例分享
2020/07/31 Python
波兰在线杂货店:Polski Koszyk
2019/11/02 全球购物
创立科技Java面试题
2015/11/29 面试题
高级技校毕业生自荐信
2013/11/18 职场文书
小摄影师教学反思
2014/04/27 职场文书
公安民警正风肃纪剖析材料
2014/10/10 职场文书
业务员岗位职责
2015/02/03 职场文书
2015年煤矿工作总结
2015/04/28 职场文书
交通安全温馨提示语
2015/07/14 职场文书
Python学习之os包使用教程详解
2022/03/21 Python
SpringBoot中获取profile的方法详解
2022/04/08 Java/Android
如何Tomcat中使用ipv6地址
2022/05/06 Servers