聚集索引
其实如果我们没有显示的指定主键,InnoDB
会选择一个非空的唯一索引列作为主键,如果我们也没有创建非空的唯一索引,那么 InnoDB
就会选择其自己内置的一个 6
字节长的 ROWID
自增列作为主键。InnoDB
中聚集索引叶子节点直接存储的是整条数据,也就是说索引搜索到叶子节点之后就可以直接返回数据了,无需再去磁盘获取数据。
InnoDB
中聚集索引大致结构如下图所示:
其实如果我们没有显示的指定主键,InnoDB
会选择一个非空的唯一索引列作为主键,如果我们也没有创建非空的唯一索引,那么 InnoDB
就会选择其自己内置的一个 6
字节长的 ROWID
自增列作为主键。InnoDB
中聚集索引叶子节点直接存储的是整条数据,也就是说索引搜索到叶子节点之后就可以直接返回数据了,无需再去磁盘获取数据。
可以看到,当我们存在主键索引时候,_rowid
就等于主键索引,而当不存在主键索引时,默认会采用其内置的算法,这个时候我们就无法直接查询(本实验的截图并非直接使用网页版,而是使用网页环境右边的 SSH直接
功能):
非聚集索引
除了主键索引之外的其他索引都是非聚集索引(Secondary Indexes),既然聚集索引的索引键值和数据行存放在一起,而聚集索引又只有一个,那么非聚集索引又是怎么存储数据的呢?
非聚集索引的叶子节点存储的是当前索引的键值和主键索引的键值。大致结构如下图所示(右边为非聚集索引,左边为聚集索引):
所以非聚集索引查询数据和聚集索引查询数据是不同的,因为非聚集索引的叶子节点只有当前索引的键值和主键的键值,也就是说查询数据的时候获取到非聚集索引的叶子节点只能拿到当前索引值和主键索引值。
回表
回表指的就是非聚集索引从叶子节点拿到数据(主键的键值)之后,还需要再根据主键键值去扫描主键索引的 B+
树,这种操作就叫做回表。
本实验主要讲述了 MyISAM
存储引擎和 InnoDB
存储引擎下索引是如何存储数据的,并讲述了聚集索引和非聚集索引的区别,以及回表与覆盖索引等重要特性,这些概念务必牢牢掌握,才能在面试中游刃有余的回答这些高频面试题。
智能的 MySQL,智能的索引
当一个 SQL
语句过于复杂时,我们经常会说需要去优化一下 SQL
语句,那么这个优化到底是优化什么呢?其实优化最主要的就是优化索引,创建合理的表结构,建立合适的索引,最终的目的都是为了让查询语句能使用上合适的索引,从而提升查询速度。
知识点
在 MySQL
中,通常来说索引可以归纳为三大类:
B
树索引需要重点掌握,不管是面试中,还是工作中,都需要重点掌握 B
树索引。
如何创建 B 树索引
B
树索引中我们又可以细分为普通索引,唯一索引,前缀索引和多列联合索引等等。当然,这些分类并不是一种通用的划分,只是一些常用索引的表现形式。
创建索引:
CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX index_name
[index_type]
ON tbl_name (key_part,...)
[index_option]
[algorithm_option | lock_option] ...
创建普通索引:
create index index_name on table_name(column_name);
创建唯一索引
create unique index index_name on table_name(column_name);
创建前缀索引:
CREATE INDEX index_name ON table_name (column_name(10));
创建多列联合索引
create index index_name on table_name(column_namel,column_name2);
索引合并(INDEX MERGE)
索引合并优化,MySQL
在 5.0
及之后的版本引入了的一种优化方案。这个意思就是我们在一个表中建立了很多单列索引,然后查询的时候同时用到了多列作为条件,MySQL
能够识别并分别使用单列索引进行扫描,然后将结果合并。 这种算法一般用于以下三种情况:
or
条件的并集(union
或者 union all
)。and
条件的交集。 注意:过多的单列索引大部分情况下并不能提高性能。索引合并虽然是 MySQL
的优化方案,但是出现了这种现象,更多是说明索引建的很糟糕。
同样的,我们也来演示一下索引合并的场景:
CREATE INDEX uid_index ON user_job (uid); -- 创建普通索引
EXPLAIN SELECT * FROM `user_job` WHERE `job_name` = 'CTO' AND uid=1;
MySQL 优化器每次都能选出最优索引吗
在一条查询语句中,除了特定场景下触发了索引合并优化措施之外,正常情况下一张表在一次查询中只能用到一个索引,然而我们一张表有时候会不止一个索引,那么 MySQL
是如何知道选择哪一个索引进行查询呢?MYSQL
选出来的索引到底又是不是我们想要的呢?本实验将带领大家一起认识 MySQL
是如何识别出最优索引的。
知识点
其他还有许多场景也无法使用到索引,比如:
replace/substr/concat/sum/count/avg
等),使用表达式或者计算(+、-、*、/
)。to_char()
。!
,<>
,not like
,not in
等反向查询。
什么是事务的 ACID 特性
学习关系型数据库,事务是必须掌握的重点知识,因为事务是关系型数据和非关系型数据库的本质区别。
知识点
事务(Transaction)是由一系列对数据库中的数据进行访问与更新的操作所组成的一个程序执行单元。
在同一个事务中所进行的操作,要么都成功,要么就都失败。理想中的事务必须满足四大特性,这就是大名鼎鼎的 ACID
特性。
A(Atomicity)原子性
原子性指的是数据库事务是不可分割的一部分,只有一个事务中的所有操作都成功,这个事务才算执行成功,一旦有一个操作失败,那么其他成功的操作也必须回滚。 以转账 1000
元场景为例,一个转账过程就是一个事务,这个事务主要包括以下两步:
C(Consistent)一致性
一致性指的是在事务开始之前和事务结束之后,数据库的完整性约束都没有被破坏,事务执行的前后都是合法的数据状态。
I(Isolation)隔离性
隔离性就是说每个事务之间的操作应该相互隔离,互不干扰。比如说一个事务提交之前对另一个事务不可见。
D(Durable)持久性
持久性这个概念就比较容易理解了,就是说事务一旦提交成功了,那么就应该是持久的,即使是数据库重启,服务器宕机等情况发生,数据都不会丢失(当然这个不能包括因为地震等自然灾害导致的存储数据的硬盘发生不可逆的损坏)。
事务的自动提交
常用的事务控制语句
如果需要手动管理事务,通常会使用到以下语句:
COMMIT WORK
。ROLLBACK WORK
。identifier
为自定义的一个唯一标识,只要保证唯一就行。identifier
为自定义的保存点唯一标识,当前语句如果使用一个不存在的保存点时,会直接报错。
COMMIT 和 COMMIT WORK 的区别
这两个语句都能提交一个事务,在某些情况下是等价的,但是它们的执行效果并不完全是相同的。其区别就在于提交事务之后的操作,同样的还有 ROLLBACK
和 ROLLBACK WORK
,而控制这两者之间的区别可以通过变量 completion_type
来控制:
从事务的理论角度来说,我们可以把事务分为以下五大类:
扁平事务
扁平事务是最简单也是最常用的一种事务,这种事务中的所有操作都是原子的,要么全部成功,要么什么都不做,平常我们使用的事务绝大多数都属于扁平事务。
带有保存点的扁平事务
当一个事务过长时,为了避免执行快结束的时候报错导致所有语句都要重新执行,我们可以在指定位置定义好保存点,这样当事务处理到后面报错的时候,我们就可以不需要回滚整个事务,而是回滚到我们自定义好的某一个保存点。
需要注意的是,保存点并不会被持久化,所以在事务提交之前,如果系统发生崩溃,所有的保存点都将消失。
接下来我们来看一个带有保存点扁平事务的例子。
链事务
在提交一个事务之后,释放掉我们不需要的数据,将必要的数据隐式的传给下一个事务。(注意:提交事务操作和开始下一个事务操作是一个原子操作),这就意味着下一个事务能看到上一个事务的结果。
链事务可以看成带有保存点的特殊事务,他们的区别就是带有保存点的事务可以回滚到任意保存点,而链事务中只能回滚到最近的一个保存点(即最新的一个开始事务的点)。
嵌套事务
嵌套事务就是说一个事务之中嵌套另一个事务,事务之间存在父子关系,子事务的提交之后并不生效,需要等到父事务提交之后才会生效。
需要注意的是 MySQL
原生并不支持嵌套事务,但是可以通过保存点模拟嵌套事务,只是说这么模拟的话就没有真正的嵌套事务这么灵活。
分布式事务
分布式事务通常就是在分布式环境下,多个数据库下同时运行不同的扁平事务。多个数据库环境下运行的扁平事务就合成了一个分布式事务。
锁,大家并不陌生,但是并不是所有人都能清楚的说出 MySQL
中到底有哪些锁,不同的锁又有哪些不同的使用场景,本文,将会为大家一一介绍。
知识点
从锁的粒度上来说,我们可以将锁分为全局锁,表锁和行锁。
全局锁,也被简称为 FTWRL
,通过以下语句执行:
FLUSH TABLE WITH READ LOCK;-- 加全局锁
UNLOCK TABLES;-- 解锁
执行这个语句之后整个数据库都只允许读,不允许写。 当然,也可以使用以下语句:
SET GLOBAL READ_ONLY=TRUE;
不过需要注意的是,如果需要加全局锁的话,一般我们推荐使用 FTWRL
,主要是因为如果客户端因为异常断开了,FTWRL
会自动释放锁,而使用 READ_ONLY
语句则会一直使得数据库保持 read-only
状态。
表锁
顾名思议,表锁就是直接锁表,在 MyISAM
引擎中就只有表锁。
表锁也可以分为两种,一种是通过 LOCK
命令加锁,另一种是加 MDL
锁。
LOCK 锁
LOCK TABLE user_job READ;-- 此时本线程只能读user_job表,其他线程也只能读user_job表
LOCK TABLE user_job WRITE;-- 此时本线程只能读写user_job表,其他线程不能读写user_job表
UNLOCK TABLE; -- 解锁
下面我们一起来操作一下全局表,体会一下全局表的加锁,首先给 user_job
加一个读锁,然后分别执行查询语句和更新语句:
LOCK TABLE user_job READ;-- user_job 表加上读锁
select * from user_job limit 1;
update user_job set job_name='CEO';
意向锁
意向锁也是属于表锁的一种,分为两种类型:意向共享锁(Intention Shared Lock)和意向排他锁(Intention Exclusive Lock),这两种锁又分别可以简称为 IS
锁和 IX
锁。
意向锁是 MySQL
自己维护的,用户无法手动加意向,意向锁有两大加锁规则:
S
锁的时候,MySQL
会先给这张表加上 IS
锁。X
锁的时候,MySQL
会先给这张表加上 IX
锁。这样的话上面的问题就迎刃而解了,当需要给一张表上表锁的时候,只需要看这张表是否有对应的意向锁就可以了,无需遍历整张表。
一条 select 语句的执行过程
select
查询语句大家都在使用,但是一条 select
语句到底是怎么执行的,语法是怎么校验的,数据是怎么返回的,本文将为大家一一介绍。
知识点
select 语句的执行流程
在实验一中,我们提到了 MySQL
的逻辑架构图,MySQL
从大方向来说,可以分为 Server
层和存储引擎层。而 Server
层包括连接器、查询缓存、解析器、预处理器、优化器、执行器等,最后 Server
层再通过 API
接口形式调用对应的存储引擎层提供的接口来执行增删改查操作。
如下即为一个简略的 select
语句查询流程图:
根据流程图,可以得出一条 select
查询大致经过以下六个步骤:
sql
语句进行解析,然后对其进行词法分析,语法分析以及预处理。API
来执行查询。
建立连接
第一步建立连接,这一步很容易理解。 MySQL
服务端和客户端的通信方式采用的是半双工协议。
查询缓存
上面查询流程图中缓存我这边使用了虚线框的原因是缓存在 MySQL 8.0
之后的版本已经取消了,这是因为 MySQL
的缓存使用条件非常苛刻,是通过一个大小写敏感的哈希值去匹配的,这样就是说一条查询语句哪怕只是有一个空格不一致,都会导致无法使用缓存。而且一旦表里面有一行数据变动了,那么关于这种表的所有缓存都会失效,所以一般我们都是不建议使用缓存。
而且在 MySQL 8.0
版本之前缓存也是默认关闭的,但是我们可以通过变量 query_cache_type
进行控制。
解析器
这一步主要的工作首先就是检查 sql
语句的语法对不对,在这里,会把我们整个 sql
语句打碎,比如:select name from lanqiao2 where id=1,就会被打散成 select
,name
,from
,lanqiao2
,where
,id
,=
,1
这 8
个字符,并且能识别出关键字和非关键字,然后根据 sql
语句生成一个数据结构,也叫做解析树(select_lex)。
当然,这只是一颗非常简单的解析树,当我们查询语句越复杂,这棵树也会越复杂。
经过了前面的词法和语法解析,那么至少我们这条查询的 sql
语句的语法格式是满足要求了,接下来我们还需要做什么呢?接下来自然是检查表名,列名以及其他一些信息等是不是真实存在的,这就是预处理的一个过程,预处理就是做一个表名和字段名等相关信息合法性的检测。
count(1) 和 count(id) 到底哪个性能好
count(*)
语句大家都经常使用,但是随着数据量的递增,我们也会发现,count(*)
的查询越来越慢,而除了 count(*)
,还有 count(1)
,count(主键 id)
,count(字段)
等都可以获取数据量,那么这么多 count
语句,又到底有什么区别呢?
知识点
如何优化 order by 语句
order by
查询语句使用也是非常频繁,有时候数据量大了会发现排序查询很慢,本文就介绍一下 MySQL
是如何进行排序的,以及如何利用其原理来优化 order by
语句。
知识点
join 语句是如何执行的
join
语句也是日常开发中使用的最频繁的语句之一,join
语句到底是不是会对查询的性能产生很大的影响,而且多表查询的时候又应该如何去选择驱动表?本文就让我们来分析一下 join
语句是如何执行的,了解原理之后才能对其如何进行优化有清晰的思路。
知识点