一. 索引
- Mysql的索引是什么数据结构的?
答:B+树,多路平衡查找树。在数据库中我们将B树(和B+树)作为索引结构,可以加快查询速速,此时B树中的key就表示键,而data表示了这个键对应的条目在硬盘上的逻辑地址。
- B+树的结构是什么样的?
答:多路平衡查找树。
(1)B+树包含2种类型的结点:内部结点(也称索引结点)和叶子结点。内部结点不保存数据,只用于索引,所有数据(或者说记录)都保存在叶子结点中。
(2)m阶B+树表示了内部结点最多有m-1个关键字,同时限制了叶子结点最多存储m-1个记录。最少Math。ceil(m/2)-1个记录
(3)内部结点中的key都按照从小到大的顺序排列,对于内部结点中的一个key,左树中的所有key都小于它,右子树中的key都大于等于它。叶子结点中的记录也按照key的大小排列。
(4)每个叶子结点都存有相邻叶子结点的指针,叶子结点本身依关键字的大小自小而大顺序链接。
- 为什么B+树查找速度更稳定?
答:B+所有关键字数据地址都存在叶子节点上,任何查找都是从根节点到叶子节点的过程,所以每次查找的次数都相同,所以查询速度要比B树更稳定
- 为什么B+树更矮胖?
B+树的中间节点不保存数据,所以磁盘页能容纳更多节点元素,更“矮胖”
- B 树& B+树两者有何异同呢?
B 树的所有节点既存放键(key) 也存放 数据(data),而 B+树只有叶子节点存放 key 和 data,其他内节点只存放 key。
B 树的叶子节点都是独立的;B+树的叶子节点有一条引用链指向与它相邻的叶子节点。
B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。
B+树的优点
1、B+树的层级更少:相较于B树B+每个非叶子节点存储的关键字数更多,树的层级更少所以查询数据更快;
2、B+树查询速度更稳定:B+所有关键字数据地址都存在叶子节点上,所以每次查找的次数都相同所以查询速度要比B树更稳定;
3、B+树天然具备排序功能:B+树所有的叶子节点数据构成了一个有序链表,在查询大小区间的数据时候更方便,数据紧密性很高,缓存的命中率也会比B树高。
4、B+树全节点遍历更快:B+树遍历整棵树只需要遍历所有的叶子节点即可,,而不需要像B树一样需要对每一层进行遍历,这有利于数据库做全表扫描。
B树相对于B+树的优点是,如果经常访问的数据离根节点很近,而B树的非叶子节点本身存有关键字其数据的地址,所以这种数据检索的时候会要比B+树快。
- 为什么不使用Hash索引?
Hash 索引指的就是 Hash 表,最大的优点就是能够在很短的时间内,根据 Hash 函数定位到数据所在的位置。
缺点在于:
①Hash冲突问题
②Hash 索引不支持顺序和范围查询(Hash 索引不支持顺序和范围查询是它最大的缺点: 假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。
- 索引有哪些类型(先写这么多)
1. 主键索引:数据表的主键列使用的就是主键索引
2. 二级索引:二级索引的叶子节点存储的数据是主键
唯一索引:属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引
普通索引:唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL
前缀索引:只适用于字符串类型的数据
全文索引:主要是为了检索大文本数据中的关键字的信息
- 主键索引和普通索引的区别:
- 只有一个主键索引。主键不能为 null,不能重复。主键索引的叶子结点存放了整行记录
- 普通索引是辅助索引。一张表允许创建多个普通索引,并允许数据重复和 NULL。普通索引的叶子结点存放了主键ID,查询的时候可能需要做一次回表查询
- 聚集索引与非聚集索引
- 聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引。
- 非聚集索引即索引结构和数据分开存放的索引。二级索引属于非聚集索引。最大的缺点就是可能会二次查询(回表)
- 补:非聚集索引不一定回表查询。如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”。比如:,用户准备使用 SQL 查询用户名,而用户名字段正好建立了索引。那么这个索引的 key 本身就是 name,查到对应的 name 直接返回就行了,无需回表查询。
- 建索引语句:
- 添加 PRIMARY KEY(主键索引)建其他的索引就只改关键字就行了
ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )
- 添加 UNIQUE(唯一索引)
- 添加 INDEX(普通索引)
- 添加 FULLTEXT(全文索引)
- 添加多列索引
ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )
二. 事务
- 什么是事务?数据库事务?
事务是逻辑上的一组操作,要么都执行,要么都不执行。数据库事务可以保证多条 SQL 语句构成一个逻辑上的整体。构成这个逻辑上的整体的这些数据库操作遵循:要么全部执行成功,要么全部不执行 。
-- 0. 开启事务
START TRANSACTION;
-- 1. 张三账户 -500
UPDATE account SET balance = balance - 500 WHERE NAME = 'zhangsan';
-- 2. 李四账户 +500
UPDATE account SET balance = balance + 500 WHERE NAME = 'lisi';
-- 3. 发现执行没有问题,提交事务
COMMIT;
-- 3. 发现出问题了,回滚事务,数据恢复到事务开始前
ROLLBACK;
- 事务的四大特征:
-- 1. 原子性:事务是不可分割的最小操作单位,要么同时成功,要么同时失败。
-- 2. 持久性:当事务提交后,数据库会持久化的保存数据。即使数据库发生故障也不应该对其有任何影响。
-- 3. 隔离性:多个事务之间。相互独立。
-- 4. 一致性:事务操作前后,数据总量不变
- 事务的实现原理:
以 MySQL 的 InnoDB 引擎为例来简单说一下。
使用 undo log(回滚日志) 来保证事务的原子性
使用 redo log(重做日志) 保证事务的持久性
通过 锁机制、MVCC 等手段来保证事务的隔离性( 默认支持的隔离级别是 REPEATABLE-READ )
保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。
- 并发事务带来哪些问题?
-- 1. 脏读:一个事务,读取到另一个事务中没有提交的数据
-- 2. 不可重复读(虚读):在同一个事务中,两次读取到的数据不一样。
-- 3. 幻读:幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
- 设置不同的事务隔离级别就可以解决以上问题:
-- 1.READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
-- 2.READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
-- 3.REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。(默认隔离机制)
-- 4.SERIALIZABLE(可串行化): 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
三 . 存储引擎
- 查看 MySQL 提供的所有存储引擎:show engines;
- MyISAM 和 InnoDB 的区别 |区别 | MyISAM | InnoDB | |--|--|--| | 行级锁 | 不支持,只支持表级锁 | 支持 | | 事务 | 不支持 | 支持,具有提交和回滚能力 | | 数据库异常崩溃后的安全恢复 | 不支持 | 支持 |
四. 一条语句的执行流程
- MySQL的组成结构:
- Server层:
连接器: 身份认证和权限相关(登录 MySQL 的时候)。
查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
分析器: 没有命中缓存的话,SQL 语句就会经过分析器
词法分析:提取关键字,看 SQL 语句要干嘛
语法分析:检查你的 SQL 语句语法是否正确。
优化器: 按照 MySQL 认为最优的方案去执行。
执行器: 执行前会校验该用户有没有权限,如果没有权限,就会返回错误信息;有权限,则执行语句,然后从存储引擎返回数据。
日志模块(binlog):这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有
- 存储引擎: 主要负责数据的存储和读取,支持 InnoDB、MyISAM、Memory 等多个存储引擎
- 查询语句的执行流程
select * from tb_student A where A.age='18' and A.name=' 张三 ';
- 连接器进行权限校验,没有权限,直接返回错误信息
- 分析器进行词法分析,语法分析
- 优化器确定执行方案
- 权限校验,没有权限就会返回错误信息
- 执行器调用数据库引擎接口,返回引擎的执行结果。
- 更新语句的执行流程(增加,更新,删除)
update tb_student A set A.age='19' where A.name=' 张三 ';
- 连接器进行权限校验,没有权限,直接返回错误信息
- 分析器进行词法分析,语法分析
- 优化器确定执行方案
- 权限校验,没有权限就会返回错误信息
- 执行器先查询这一条数据,对其需要更改的备部分进行更改,得到新的一行数据
- InnoDB把这一条写入内存,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交
- 执行器生成这个操作的 binlog,并把 binlog 写入磁盘
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。
五. 乐观锁悲观锁
- 悲观锁
- 总是假设最坏的情况,所以每次在拿数据的时候都会上锁。这样别人想拿这个数据就会阻塞直到它拿到锁
- 使用场景:多写
- 分类:共享锁:多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改;排他锁:如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁
- 优点:为数据处理的安全提供了保证
- 缺点:额外的开销,还有增加产生死锁的机会。另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。
- 乐观锁
- 总是假设最好的情况,不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。
- 使用场景:多读,可以提高吞吐量
- 实现方式:①版本号机制:在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。②CAS算法:CAS 操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值(V)与预期原值(A)相匹配,那么处理器会自动将该位置值更新为新值(B)。否则,处理器不做任何操作。
- 缺点:①ABA问题:变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?②只能保证一个共享变量的原子操作
六. MVCC
- MVCC多版本并发控制指的是 “维持一个数据的多个版本,使得读写操作没有冲突” 这么一个概念。仅仅是一个理想概念
- 为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。
七. 快照读,当前读
- 当前读:读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
- 当前读是通过 next-key 锁(行记录锁+间隙锁)来是实现的
- 快照读:像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
- 普通读是通过 undo log + MVCC 来实现的
八 三大范式
- 第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值
- 第二范式在第一范式的基础之上更进一层,确保表中的每列都和主键相关
- 第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关
六. 一些其他
- MySQL 中时间到底怎么存储才好?Datetime?Timestamp? 数值保存的时间戳?
- 不要用字符串存储日期!占用空间更大,比较时效率低(逐个字符进行比对)
- 其他的类型对比
2. 为什么主键不用UUID,要用自增?