Mysql

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.可重复读下的幻读问题

  1. 快照读 (Snapshot Read):普通的 SELECT,MVCC 基于 Read View + 回滚链 构建一个"过去的一致性快照",别的事务再插入/删除不会影响到这个快照,所以幻读自然被挡住

  2. 当前读 (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,所以不会特别影响性能

相关推荐
DCTANT2 小时前
【原创】使用更优雅的方式改造MyBatisPlus逻辑删除插件
spring boot·后端·mysql·kotlin·mybatis·mybatisplus
鸠摩智首席音效师2 小时前
MySQL ERROR 1114 (HY000): The table is full
数据库·mysql
数据大魔方2 小时前
【期货量化实战】豆粕期货量化交易策略(Python完整代码)
开发语言·数据库·python·算法·github·程序员创富
Codeking__2 小时前
Redis的value类型介绍——zset
数据库·redis·缓存
muddjsv2 小时前
SQLite3 核心命令全解析 (从入门到精通)
数据库
難釋懷2 小时前
认识NoSQL
数据库·nosql
亿坊电商2 小时前
利于SEO优化的CMS系统都有哪些特点?
前端·数据库
阿阿阿安2 小时前
MySQL(一)数据库风险操作场景总结
数据库·mysql
计算机程序设计小李同学3 小时前
平价药店销售与管理系统
java·mysql·spring·spring cloud·ssm