索引
- 是什么? 是一个排好序的可以快速查找的数据结构
- 优点:提高数据查询速率,减少数据库磁盘IO,降低CPU的消耗
- 举例:
- 在使用分组和排序子句进行数据查询时,可以显著减少查询中分组和排序的时间 ,降低了CPU的消耗。因为已经是排好序的了,而且相同的元素还是紧挨着的,便于分组。
- 加速表和表之间的连接,提高多表查询速度。
- 类似大学图书馆建书目索引,提高数据检索的效率,降低 数据库的IO成本
- 缺点:
- 创建索引和维护索引要 耗费时间
- 索引需要占 磁盘空间,如果有大量的索引,索引文件就会很大
- 降低表更新数据的速度
- PS:某个时段使用的查询需求比较多,就可以先创建索引,等更新的需求变多时,再删除索引。
索引的数据结构
- 一个表能存储大量的数据,其中数据又分很多页。一页默认可以存储16KB的数据。
- 数据在物理存储上是不连续的,就需要一个指针实现逻辑上连续,也就是一个单链表。页与页之前也不是连续的,也是一个单链表。这样设计能更好的利用碎片化的空间。
- 在一个页中查找数据:
- 以主键作为搜索条件:主键是递增的,在页使用二分法查找数据
- 以其他列作为搜索条件:依次遍历查找
- 在不同页中查找数据:那就要遍历所有的数据了。
- 以上是在不建立索引的情况下,如果一张表中有几十万条数据,那全表查询,一个页一个页的遍历,而且页加载到内存也很耗时。显然查询速度很慢。
简单的主键索引的实现
- 创建一张表,主键递增。
mysql> CREATE TABLE index_demo(
-> c1 INT,
-> c2 INT,
-> c3 CHAR(1),
-> PRIMARY KEY(c1);
- index_demo表的行格式示意图:
record_type :记录头信息的一项属性,表示记录的类型, 0 表示普通记录、 2 表示最小记录、 3 表示最大记录
next_record :记录头信息的一项属性,表示下一条地址相对于本条记录的地址偏移量,我们用箭头来表明下一条记录是谁。
各个列的值 :这里只记录在 index_demo 表中的三个列,分别是 c1 、 c2 和 c3 。
- 但是,由于逐渐递增,5比4大,所以5和4应该换个位置。这个过程叫做页分裂。
- 此时,我想查询ID为20的数据,那如果有多个页,就需要一个页,一个页的遍历,耗时很慢。此时就可以创建一个目录项,目录项中存放的是这个页中最小的key的值和页号,然后通过对目录项进行二分查找就可以快速定位到该数据存放在哪个数据页了。然后在这个页面再使用二分查找,就可以快速查找到数据。
- 以上就是简单的主键索引了
迭代:第一次
- 上面说的主键索引,他的目录项是连续存储的,添加和删除数据的时候,会导致数据页的改变,那么对导致目录项的改变,由于是连续存储,所以很耗时。
- 我们可以把目录项变成不连续的,然后也组合成一个页,叫做目录页。这样目录页就和数据页差不多,可以方便更新。
- 目录页和数据页,每个页有一个页目录,便于在这个页中查找数据。
- 这样的话查找id=20的数据,只需要在目录页中找到这个数据在那一个数据页,然后从这个数据页中找到这个数据。总共进行了两次IO,加载了两个数据页。
迭代:第二次
- 随着数据页的增加,目录页也会越来越多,这个时候查找数据,就需要对目录页进行处理,产生更高级的目录页
最终呢,就变成了一个B+树
B+树
- B+树一般不超过4层,因为层级越高,加载的页越多,IO就越多,4层就4次IO。4层就已经可以存储百亿条数据了。
常见索引的概念
聚簇索引
聚簇索引就是上面描述的主键索引。数据和索引都在叶子结点。
特点:
- 页内的数据是按照主键的大小排列的一个单链表
- 页与页之间是按照主键的大小排列的一个双向链表
- 各层次目录页也是按照主键的大小排列的一个双向链表
- B+树的叶子结点保存着完整的数据记录
- 这种索引不需要我们通过index显示创建,InnoDB引擎会自动帮我们创建
优点:
- 数据访问更快 ,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快
- 聚簇索引对于主键的 排序查找 和 范围查找 速度非常快
- 按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多个数据块中提取数据,所以 节省了大量的io操作 。
缺点:
- 插入速度严重依赖于插入顺序 ,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键
- 更新主键的代价很高 ,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新
非聚簇索引(二级索引)
- 也就是根据非主键的列创建索引。
- 根据这个以c2列大小排序的B+树只能确定我们要查找记录的主键值,所以如果我们想根据c2列的值查找到完整的用户记录的话,仍然需要到 聚簇索引 中再查一遍,这个过程称为 回表 。也就是根据c2列的值查询一条完整的用户记录需要使用到 2 棵B+树!
- 以下图并不准确,其实在目录页中的数据页码的值上面还应该加上这个数据页最小的主键,因为c2列的数据可能是重复的,要保证唯一性,下面还会说到。
联合索引
- 可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,
- 比方说我们想让B+树按照 c2和c3列 的大小进行排序,这个包含两层含义:
- 先把各个记录和页按照c2列进行排序。
- 在记录的c2列相同的情况下,采用c3列进行排序
- 注意一点,以c2和c3列的大小为排序规则建立的B+树称为 联合索引 ,本质上也是一个二级索引。只会创建1颗B+树
- 叶子节点记录着:c2、c3和主键的值
注意事项
根页面一直不动
以上描述,添加数据,都是从叶子结点开始,然后页满了之后,扩充页。真实情况并不是这样,真实的数据添加情况是:先从根节点添加数据,如果根页面的满了,那就复制出一份数据页,然后根页面就成了一个目录页,然后再有数据就在第二层开辟一个页,然后也满了,就在第三层复制一份,第二层就变成了一个目录页,依次类推。
内节点中目录项记录的唯一性
如果二级索引的目录中有相同的值,这个时候就会加上主键的值,来做到唯一
一个页面最少存储2条记录
MyISAM中的索引方案
MyISAM底层索引也是B+树索引,只不过他都是非聚族索引。但是他的非聚族索引和InnoDb的不一样。他的叶子结点存放的是这条数据的地址。也就是索引和数据存放在不同文件中。先通过索引找到数据的地址,然后通过地址去找到数据,也会有一个回表的操作。
MyISAM 与 InnoDB对比
① 在InnoDB存储引擎中,我们只需要根据主键值对 聚簇索引 进行一次查找就能找到对应的记录,而在MyISAM 中却需要进行一次 回表 操作,意味着MyISAM中建立的索引相当于全部都是 二级索引 。
② InnoDB的数据文件本身就是索引文件,而MyISAM索引文件和数据文件是 分离的 ,索引文件仅保存数据记录的地址。
③ InnoDB的非聚簇索引data域存储相应记录 主键的值 ,而MyISAM索引记录的是 地址 。换句话说,InnoDB的所有非聚簇索引都引用主键作为data域。
④ MyISAM的回表操作是十分 快速 的,因为是拿着地址偏移量直接到文件中取数据的,反观InnoDB是通过获取主键之后再去聚簇索引里找记录,虽然说也不慢,但还是比不上直接用地址去访问。
⑤ InnoDB要求表 必须有主键 ( MyISAM可以没有 )。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整型。
索引的代价
- 索引是个好东西,可不能乱建,它在空间和时间上都会有消耗:
- 空间上的代价
- 每建立一个索引都要为它建立一棵B+树,每一棵B+树的每一个节点都是一个数据页,一个页默认会占用 16KB 的存储空间,一棵很大的B+树由许多数据页组成,那就是很大的一片存储空间。
- 时间上的代价
- 每次对表中的数据进行 增、删、改 操作时,都需要去修改各个B+树索引。而且我们讲过,B+树每层节点都是按照索引列的值 从小到大的顺序排序 而组成了 双向链表 。不论是叶子节点中的记录,还是内节点中的记录(也就是不论是用户记录还是目录项记录)都是按照索引列的值从小到大的顺序而形成了一个单向链表。而增、删、改操作可能会对节点和记录的排序造成破坏,所以存储引擎需要额外的时间进行一些 记录移位 , 页面分裂 、 页面回收 等操作来维护好节点和记录的排序。如果我们建了许多索引,每个索引对应的B+树都要进行相关的维护操作,会给性能拖后腿。
B树
- B树在插入和删除节点时,如果导致树不平衡,会自动调节保持自平衡
- 叶子结点和非叶子节点都会存放数据
- 非叶子节点的关键字,不会存放在叶子结点。也就是8对应的数据,不会存在在叶子结点中。
查询优化
SQL优化的步骤:
- 1、看服务器响应时间,如果是服务器响应慢,就对服务器进行调优。
- 2、如果不是服务器的原因,就看慢查询日志,然后把慢的查询sql使用explain进行分析,看是sql等待时间长,还是sql执行时间长。
- 如果是sql等待时间长,比如线程池满了,遇到了锁和阻塞,服务器资源达到了瓶颈(cpu,内存满了等)等,就优化查询,优化并发,优化连接池,优化服务器资源等。
- 如果是sql执行时间长,就进行索引优化、优化join表关联、数据表结构进行优化(满足哪些范式等),
- 如果还是不行,就看表是否达到瓶颈,如果达到瓶颈,例如数据量太大,查询响应时间较长,每次操作IO太大,表结构太复杂等,就考虑分库分表,读写分离。
索引的分类
常用的索引包括:唯一索引,主键索引,普通索引,降序索引(索引的创建默认为升序索引)和隐藏索引。其中普通索引又包括单列索引,联合索引。
索引的创建
限制索引的数目
在实际工作中,我们也需要注意平衡,索引的数目不是越多越好。我们需要限制每张表上的索引数量,建议单张表索引数量不超过6个
。原因:
- 每个索引都需要占用
磁盘空间
,索引越多,需要的磁盘空间就越大。 - 索引会影响
INSERT、DELETE、UPDATE等语句的性能
,因为表中的数据更改的同时,索引也会进行调整和更新,会造成负担。 - 优化器在选择如何优化查询时,会根据统一信息,对每一个可以用到的
索引来进行评估
,以生成出一个最好的执行计划,如果同时有很多个索引都可以用于查询,会增加MySQL优化器生成执行计划时间,降低查询性能。
在创建表的时候添加索引
- 唯一性索引和主键索引的区别就是:唯一性索引可以为空,主键索引不可以为空;唯一索引一个表里可以有多个,但主键索引只能有一个。
CREATE TABLE table_name [col_name data_type]
[UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY] [index_name] (col_name [length]) [ASC | DESC]
UNIQUE
、FULLTEXT
和SPATIAL
为可选参数,分别表示唯一索引、全文索引和空间索引;INDEX
与KEY
为同义词,两者的作用相同,用来指定创建索引;index_name
指定索引的名称,为可选参数,如果不指定,那么MySQL默认col_name为索引名;col_name
为需要创建索引的字段列,该列必须从数据表中定义的多个列中选择;length
为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度;ASC
或DESC
指定升序或者降序的索引值存储。
1. 创建普通索引
CREATE TABLE book(
book_id INT ,
book_name VARCHAR(100),
authors VARCHAR(100),
info VARCHAR(100) ,
comment VARCHAR(100),
year_publication YEAR,
INDEX(year_publication)
);
2. 创建唯一索引
CREATE TABLE test1(
id INT NOT NULL,
name varchar(30) NOT NULL,
UNIQUE INDEX uk_idx_id(id)
);
3. 主键索引
CREATE TABLE student (
id INT(10) UNSIGNED AUTO_INCREMENT,
student_no VARCHAR(200),
student_name VARCHAR(200),
PRIMARY KEY(id)
);
# 删除主键索引
ALTER TABLE student drop PRIMARY KEY ;
4. 创建单列索引
CREATE TABLE test2(
id INT NOT NULL,
name CHAR(50) NULL,
INDEX single_idx_name(name(20))
);
5. 创建组合索引
CREATE TABLE test3(
id INT(11) NOT NULL,
name CHAR(30) NOT NULL,
age INT(11) NOT NULL,
info VARCHAR(255),
INDEX multi_idx(id,name,age)
);
6. 创建全文索引
CREATE TABLE `papers` (
id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(200) DEFAULT NULL,
`content` text, PRIMARY KEY (`id`),
FULLTEXT KEY `title` (`title`,`content`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
SELECT * FROM papers WHERE MATCH(title,content) AGAINST (‘查询字符串’);
7. 创建空间索引
CREATE TABLE test5(
geo GEOMETRY NOT NULL,
SPATIAL INDEX spa_idx_geo(geo)
) ENGINE=MyISAM;
-优化:隐藏索引可以是先设置成隐藏,让索引失效,然后测试查询数据没有问题了,再把索引删除。因为之间删除索引会对数据可能产生影响,我们在优化的时候,可以先设置成隐藏,不行了,再设置成可见索引,这样就不会来回创建和删除索引了。而且这个索引现在不用,以后要用,也可以先设置成隐藏索引,后面用的时候再改回来。
在已有表中添加索引
1. 使用ALTER TABLE语句创建索引
ALTER TABLE table_name
ADD [UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY] [index_name] (col_name[length],...) [ASC | DESC]
2. 使用CREATE INDEX创建索引
CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX index_name
ON table_name (col_name[length],...) [ASC | DESC]
删除索引
1. 使用ALTER TABLE删除索引
ALTER TABLE table_name DROP INDEX index_name;
2. 使用DROP INDEX语句删除索引
DROP INDEX index_name ON table_name;
适合创建索引的情况
- 一个字段创建索引之后,这个字段对应的数据,就按照数据的类型进行了排序和分类。
- 索引不指明,就是升序索引,也就是说创建好索引,也就是排好了序,所以在使用Order by 的时候,如果这个字段设置了索引,那么Orderby 的时候就省去了排序的时间
- 既然是索引按照某种顺序的,那么也是一类的在一起,在groupby分组的时候,也会快很多
1. 字段的数据具有唯一性的限制
2. 频繁作为 WHERE 查询条件的字段
3. 经常 GROUP BY 和 ORDER BY 的列
4. UPDATE、DELETE 的 WHERE 条件列
5.DISTINCT 字段需要创建索引
6. 使用最频繁的列放到联合索引的左侧
7. 在多个字段都要创建索引的情况下,联合索引优于单值索引 - 在组合索引中,如果删除这个索引的某一个字段 ,那么这个组合索引会自动去掉这个字段的索引,保留剩下的索引。例如:索引名称 name_age_sex 如果把age字段删除了,那么name和sex的索引依然存在,此时索引就变成了name_sex。
8. 多表 JOIN 连接操作时,创建索引注意事项
首先,连接表的数量尽量不要超过 3 张
,因为每增加一张表就相当于增加了一次嵌套的循环,数量级增长会非常快,严重影响查询的效率。
其次,对 WHERE 条件创建索引
,因为 WHERE 才是对数据条件的过滤。
最后,对用于连接的字段创建索引
,并且该字段在多张表中的类型必须一致
。
9. 使用列的类型小的创建索引
- 数据类型越小,在查询时进行的比较操作越快
- 数据类型越小,索引占用的存储空间就越少,在一个数据页内就可以
放下更多的记录
,从而减少磁盘I/O
带来的性能损耗,也就意味着可以把更多的数据页缓存在内存中,从而加快读写效率。
10. 使用字符串前缀创建索引
一般对字符串类型数据,长度为 20 的索引,区分度会高达 90% 以上
。
11. 区分度高(散列性高)的列适合作为索引
列的基数
指的是某一列中不重复数据的个数,比方说某个列包含值2,5,8,2,5,8,2,5,8
,虽然有9
条记录,但该列的基数却是3
。也就是说,**在记录行数一定的情况下,列的基数越大,该列中的值越分散;列的基数越小,该列中的值越集中。**这个列的基数指标非常重要,直接影响我们是否能有效的利用索引。最好为列的基数大的列建立索引,为基数太小的列建立索引效果可能不好。
可以使用公式select count(distinct a)/count(*) from t1
计算区分度,越接近1越好,一般超过33%
就算是比较高效的索引了。
不适合创建索引的情况
1. 在where中使用不到的字段,不要设置索引
2. 数据量小的表最好不要使用索引
3. 有大量重复数据的列上不要建立索引
4. 避免对经常更新的表创建过多的索引
- 数据的更新会导致索引的重建和修改,会增大系统的负担
5. 不建议用无序的值作为索引
例如身份证、UUID(在索引比较时需要转为ASCII,并且插入时可能造成页分裂)、MD5、HASH、无序长字符串等。
6. 删除不再使用或者很少使用的索引
7. 不要定义冗余或重复的索引
- 已经在创建了联合索引s_name_id_age,就不要创建单个s_name索引了,他在匹配的时候两个都会匹配到,而且联合索引会优于单个索引,所以s_name索引就冗余了。
索引失效的情况
索引覆盖
查询优化
面试题
0、说几个优化查询的方法
1、Mysql如何实现索引机制?
MySQL中索引机制分三类:B+树索引、Hash索引、全文索引。这三类都存在,一般默认使用的是B+树索引。
2、Mysql的索引分类
3、InnoDB索引与MyISAM索引实现的区别是什么?
- MyISAM的索引方式都是非聚簇的,InnoDB包含聚簇索引和非聚簇索引。
4、事务
- 脏读:事务A读取了事务B更新但未提交的数据,之后事务B回滚了,则事务A读的数据是无效的。
- 不可重复读:事务A读取了某个字段,事务B更新了该字段,并提交了。事务A在读的时候发现前后读取的数据不一样了。
- 幻读:事务A通过查询得到一条数据,事务B此时新增了几条数据,事务A再次查询的时候,发现结果增加了很多条。
事务隔离级别
- 读未提交:所有事务都可以看到其他事务未提交的数据
- 读已提交:一个事务只能看到别的事务已经提交了的结果
- 可重复读:事务A读一条数据,B修改这个数据并提交,A事务并没有结束,此时再次读这条数据,读取到的数据仍然和第一次读的结果一样。
- 串行化:一个事务执行的时候,禁止其他事务操作。
其他
1、sql执行报错。
解决方法: