sql基础
1.数据库三大范式
-
第一范式:每一列不可分
-
第二范式:非码属性完全依赖候选码
-
第三范式:任何非主属性不依赖于其他非主属性
2.in和exist
in:检查左边的表达式是否存在右边的列表或子查询的结果集中
exist:判断"子查询是否至少返回一行"
- EXISTS 先扫外表,再对每行执行子查询,遇到第一条就短路;
- IN 先一次性把子查询结果集构造出来,再与外表做哈希/循环比对;
存储引擎
1.执行一条SQL请求的过程

连接器:建立连接,管理连接、校验用户身份;
查询缓存:查询语句如果命中查询缓存则直接返回,否则继续往下执行。MySQL 8.0 已删除该模块;
解析 SQL,通过解析器对 SQL 查询语句进行词法分析、语法分析,然后构建语法树,方便后续模块读取表名、字段、语句类型;
执行 SQL:执行 SQL 共有三个阶段:
预处理阶段:检查表或字段是否存在;将 select * 中的 * 符号扩展为表上的所有列。
优化阶段:基于查询成本的考虑, 选择查询成本最小的执行计划;
执行阶段:根据执行计划执行 SQL 查询语句,从存储引擎读取记录,返回给客户端;
2.mysql的引擎有哪些,有什么区别
InnoDB引擎:是MySQL的默认引擎,支持事务,行级锁、外键约束等特性,通过redolog日志实现崩溃恢复,聚簇索引
MyISAM:不支持事务,行级锁、外键约束等特性和崩溃恢复,支持表锁,非聚簇索引
索引
1.索引分类
按「数据结构」分类:B+tree索引、Hash索引、Full-text索引。
按「物理存储」分类:聚簇索引(主键索引)、二级索引(辅助索引)。
按「字段特性」分类:主键索引、唯一索引、普通索引、前缀索引。
按「字段个数」分类:单列索引、联合索引。
2.MySQL聚簇索引和非聚簇索引的区别是什么?
InnoDB存储引擎创建聚簇索引:
如果有主键,默认会使用主键作为聚簇索引的索引键(key);
如果没有主键,就选择第一个不包含 NULL 值的唯一列作为聚簇索引的索引键(key);
在上面两个都没有的情况下,InnoDB 将自动生成一个隐式自增 id 列作为聚簇索引的索引键(key);
聚簇索引和非聚簇索引都是基于B+树实现,聚簇索引的叶子结点里存储了id和完整数据,非聚簇索引的叶子结点存储了id和主键值,因此通过非聚簇索引查找数据时,若没有覆盖索引,会发生回表(通过主键值查找数据)
3.什么字段适合做主键?
唯一非空,具有自增长性。因为具有自增长性的主键数据,在插入时候,会写入到新的页,减少页分类和碎片的产生,以及树的调整。
4.MySQL中的索引是如何实现的?
基于B+树实现,非叶结点存储索引,叶节点存储索引和数据。
在查找时,叶子结点里存储了多条记录,按照主键顺序组成单向链表。为了加快查找速度,将记录分组,同时设置一个页目录,页目录中有多个槽指向每一页的最后一条记录。通过二分查找,找到对应槽,在遍历槽下的所有记录。

5.B+树的特点,和B树的区别?
B+树:所有叶子节点都在同一层,查找性能更稳定;叶节点用双向链表链接(范围查询);非叶节点存储键值(存储更多索引,树更加低),叶节点存储数据;自平衡
B树:非叶节点既存储键值又存储数据;叶节点没有链表连接
6.联合索引的最左匹配原则
按照最左优先的方式进行匹配,不能缺失某个索引。因为创建索引的时候就是按照这个原则,比如索引(a,b,c),先排序a,然后在a相同的情况下,排序b,在b相同的情况下排序c
7.索引失效的情况
- 模糊匹配
- 对索引使用函数
- 不符合最左匹配
- where条件中,or两边一个是索引,一个不是索引
8.索引优化
-
前缀索引
-
覆盖索引
-
主键索引自增
-
防止索引失效
9.性能调优
- explain查看sql的执行计划。分析sql语句的执行过程。可以发现是否使用全表扫描,是否存在索引未被利用
- 创建或优化索引,根据where,orderby创建合适的索引,联合索引符合最左匹配
- 避免索引失效
事务
1.事务特性
A(原子性)undolog
C(一致性)持久性+原子性+隔离性
I(隔离性)MVCC或锁机制
D(持久性)redo log
2.MySQL可能出现的并发相关问题
- 脏读:读到另一个未提交事务修改过的数据
- 不可重复读:两次读数据不一样
- 幻读:查询符合条件的记录数量,两次查询不一样
3.事务的隔离级别
-
读未提交:一个事务还未提交做的更改可以被其他事务看到
-
读提交:一个事物提交后,做的变更才能被其他事务看到(解决了脏读的问题),
-
可重复读:一个事务两次看到的数据是一致的(解决了不可重复读问题)(默认隔离级别),由MVCC实现
-
串行化:对记录加上读写锁
4.MVCC的实现原理
MVCC的实现原理是通过InnoDB表的隐藏字段,UndoLog版本链,ReadView实现。
1.隐藏字段:在创建表的时候,会有两个关键的隐藏字段:DB_TRX_ID,最近修改这条记录的事务ID;DB_ROLL_PTR,回滚指针,指向这条记录的上一个版本
2.UndoLog:在插入,更新,删除的时候产生的便于数据回滚的日志,会生成一条记录版本链表
3.ReadView:

访问规则如下所示:

1.RC隔离级别:每次select都生成一个快照读,每一次执行快照读时生成ReadView,会遍历版本链,根据访问规则读取数据
2.RR隔离级别:第一个select生成快照读,生成ReadView,复用该ReadView
5.可重复读下的幻读问题
-
快照读 (Snapshot Read):普通的
SELECT,MVCC 基于 Read View + 回滚链 构建一个"过去的一致性快照",别的事务再插入/删除不会影响到这个快照,所以幻读自然被挡住。 -
当前读 (Current Read):
SELECT ... FOR UPDATE/LOCK IN SHARE MODE/UPDATE/DELETE。它们要拿最新版本 并且加锁 ,MVCC 只负责找到最新版,不再提供快照隔离,剩下的靠锁来解决并发问题。
-
SELECT * FROM t WHERE id>5普通快照读 → 只走 MVCC,不管范围锁 ,别的事务插入 id=8 的新行后,第二次快照读依旧看不到 ,所以在这类读里"幻读"不会出现 ;
但如果你在同一事务里再执行一次当前读 ,就会发现"多了一行",于是从整体事务视角看,幻读仍可能发生。
-
SELECT * FROM t WHERE id>5 FOR UPDATE当前读 → 先拿最新版本,再对扫描到的索引范围加 Next-Key Lock(记录锁 + 间隙锁) ,把 (5, +∞) 的间隙封死;
别的事务想插 id=8 必须等待,同一事务内后续再当前读也看不到新行 ,于是幻读被真正解决。
6.串行化是怎么实现的
通过行级锁实现,普通的select查询对记录加S型的临键锁,其他事务就无法对这条记录以及间隙进行修改
锁
1.锁的类别有哪些?
-
全局锁:锁住数据库中的所有表,处于只读状态,用于全库逻辑备份
-
表级锁:每次操作锁住整张表
-
行级锁:每次操作锁住对应行数据
2.表锁的类别有哪些?
- 表锁:包括读锁或写锁,对整个表进行加锁
- 元数据锁:对一张表进行CRUD操作时加读锁,表结构更改时是写锁
- 意向锁:这个是为了避免行锁与表锁冲突(一个线程想要加表锁,需要遍历每一行是否有行锁)。目的是为了快速判断表里是否有记录被加锁
IS(意向共享锁):select ... lock in share mode
IX(意向排他锁):insert update delete select ... for update
3.行级锁的类别有哪些?
行锁:锁定单个行记录,支持RC,RR
间隙锁:锁定索引记录间隙,防止其他事务在这个间隙进行插入,产生幻读,支持RR,可以共存
临键锁(NK lock):行锁+间隙锁,锁住数据及之前的间隙
4.行锁的使用?
行锁是针对索引列加的锁,普通列是表锁
select不加锁;insert,update,delete排他锁;
手动:select ... lock in share mode 共享锁;select ... for update 排他锁
间隙锁和临键锁的应用:
- 索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁 。id=5,实际只有8,会锁住8之前的间隙
- 索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁。id=3 锁住7之前的间隙
- 索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止 id>=19 19包括19之后加锁
日志
1.redolog原理
重做日志,记录的时事务提交时数据页的物理修改,用来实现事务的持久性。(崩溃恢复)
在对缓冲区的数据进行修改后,会产生脏页,把对数据页的修改记录到redolog buffer中,等到事务提交后,在写到redolog磁盘文件中。过一段时间后,当刷新缓冲区的脏页到磁盘时发生错误,就可以借助磁盘中的redolog日志进行恢复。
WAL(先写日志):因为操作数据一般都是随机读写磁盘,而写日志是顺序写,速度更快
2.undolog原理
undolog记录事务执行过程中的日志,实现事务的原子性
在事务为提交之前,将更新前的数据记录到undolog日志中,事务回滚时,利用undolog日志回滚
还可以用作MVCC(版本链)
3.binlog原理
存在Server层中,每完成一条更新操作,会在Server层生成一条binlog,等事务提交之后,会将所有binlog写入日志文件中(主从复制,备份恢复)
binlog有三种格式:
- STATEMENT:每一条修改数据的SQL都会被记录到binlog中,有动态函数问题(函数返回值与当前机器状态,时间等有关)
- ROW:记录行数据最终被修改成什么样,binlog文件过大
- MIXED:混合模式
两阶段提交:

在prepare阶段,写入redo log,并设置为prepare状态
在commit阶段,写入bin log,将redo log设置为commit状态

在A时刻,从binlog中没有当前内部XA事务的XID,说明redolog完成刷盘,binlog没有,就回滚事务
在B时刻,从binlog中有当前内部XA事务的XID,说明redolog完成刷盘,binlog也有,就提交事务
保证了日志一致性
4.update语句的执行过程
1.执行器负责具体执行,会调用存储引擎的接口,通过主键索引树搜索获取 id = 1 这一行记录:
如果 id=1 这一行所在的数据页本来就在 buffer pool 中,就直接返回给执行器更新;
如果记录不在 buffer pool,将数据页从磁盘读入到 buffer pool,返回记录给执行器。
2.执行器得到聚簇索引记录后,会看一下更新前的记录和更新后的记录是否一样: 如果一样的话就不进行后续更新流程; 如果不一样的话就把更新前的记录和更新后的记录都当作参数传给 InnoDB 层,让 InnoDB 真正的执行更新记录的操作;
3.开启事务, InnoDB 层更新记录前,首先要记录相应的 undo log,因为这是更新操作,需要把被更新的列的旧值记下来,也就是要生成一条 undo log,undo log 会写入 Buffer Pool 中的 Undo 页面,不过在内存修改该 Undo 页面后,需要记录对应的 redo log。
4.InnoDB 层开始更新记录,会先更新内存(同时标记为脏页),然后将记录写到 redo log 里面,这个时候更新就算完成了。为了减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘。这就是 WAL 技术,MySQL 的写操作并不是立刻写到磁盘上,而是先写 redo 日志,然后在合适的时间再将修改的行数据写到磁盘上。
5.在一条更新语句执行完成后,然后开始记录该语句对应的 binlog,此时记录的 binlog 会被保存到 binlog cache,并没有刷新到硬盘上的 binlog 文件,在事务提交时才会统一将该事务运行过程中的所有 binlog 刷新到硬盘。
6.事务提交(为了方便说明,这里不说组提交的过程,只说两阶段提交):
prepare 阶段:将 redo log 对应的事务状态设置为 prepare,然后将 redo log 刷新到硬盘;
commit 阶段:将 binlog 刷新到磁盘,接着调用引擎的提交事务接口,将 redo log 状态设置为 commit(将事务设置为 commit 状态后,刷入到磁盘 redo log 文件); 至此,一条更新语句执行完成。
5.两次写的原理?
- InnoDB 的页大小默认 16 KB,而大多数操作系统只保证 4 KB 原子页。
- 如果一次 16 KB 刷盘写到一半机器掉电,会出现"页头新、页尾旧"的半写页。
- redo log 记录的是"页内某偏移量改成什么值",它假设页本身格式是完整的。遇到半写页就无能为力。
双写缓冲(Doublewrite Buffer): 一块连续的磁盘空间(2MB)
位置: 在系统表空间(ibdata1)中
固定大小:128个页(128 × 16KB = 2MB)
作用: 在写数据文件前,先写双写缓冲(完整性保证)(原子写的操作),本身是顺序IO,所以不会特别影响性能
DB 的页大小默认 16 KB,而大多数操作系统只保证 4 KB 原子页。
- 如果一次 16 KB 刷盘写到一半机器掉电,会出现"页头新、页尾旧"的半写页。
- redo log 记录的是"页内某偏移量改成什么值",它假设页本身格式是完整的。遇到半写页就无能为力。
双写缓冲(Doublewrite Buffer): 一块连续的磁盘空间(2MB)
位置: 在系统表空间(ibdata1)中
固定大小:128个页(128 × 16KB = 2MB)
作用: 在写数据文件前,先写双写缓冲(完整性保证)(原子写的操作),本身是顺序IO,所以不会特别影响性能