MySQL
Siona
MySQL
B 树、B+ 树
为什么 MySQL 使用 B+ 树?
MySQL 的索引结构,各自优势?
MySQL 中的索引是通过 B+ 树实现的。B+ 树是一种多叉树,它可以将数据按照一定的顺序组织起来,从而提高查询效率。
✅ B+树、哈希索引、全文索引
▪ 哈希索引 ( 一般用于等值操作,而且人工一般不能干涉 )
▪ 全文索引 ( 没有了解,因为比起使用Mysql的全文索引,用ES实现搜索更好 )
✅ 根据存储引擎,b+树的具体实现有细微区别
🔹 MyISAM
▪ 无聚簇索引
▪ 叶子节点存的是数据的地址
▪ 每次查询都需要去回表
▪ why? 因为MyISAM的数据持久化方法是:数据和索引分别存储
🔹 InnoDB
▪ 分聚簇索引和非聚簇索引
▪ 聚簇索引只有一个,它根据主键实现了b+树
▪ 非聚簇索引有多个,它的叶子节点存的是索引 + 主键
▪ why? 因为InnoDB的数据持久化方法是:数据和索引一个文件
✅ B+树 vs B树
🔹 B+树
▪ 叶子节点存数据
🔹 B树
▪ 所有节点都存数据
🔹 why choose B+
▪ INSERT / DELETE
▪ B树由于所有节点都保存所有数据,每当插入一条数据,哪怕是自增的,都可能造成整个树的自旋重构,当数据量很大的时候,这个时间成本和风险是巨大的
▪ B+树使用叶子节点保存数据,插入一条数据只会在叶子节点上插入,一般不会影响树的结构
▪ SELECT
▪ B+树支持范围查找,同时查询更高效
▪ why ? 因为叶子节点中,页与页之间是双向链表,而簇与簇之间有单向指针连接。
MySQL 聚簇索引、非聚簇索引
MySQL 索引优化?联合索引?
索引是存储引擎快速找到记录的一种数据结构。(相当于图书馆的检索、书本里的目录、超市的导购员)
show index from table; 查看索引详情
联合索引:一个索引包含多个列,多用于避免回表查询
聚集索引:索引和数据存放在一个文件里
非聚集索引:索引和数据分开存放
索引优化:
order by 语句使用 索引最左前列
尽量在索引列上完成排序
覆盖索引
where 高于 having,尽量不要用 having
代码先行、索引后上
联合索引尽量覆盖条件 → age>14 and age<38 这类范围查找的条件放在最后
不要在小基数字段上建立索引
长字符串可以采用前缀索引
where 与 order by 冲突时优先 where
基于慢sql查询做优化
MyISAM、InnoDB 区别
ACID
MySQL事务的ACID四大特性
undo log 和 redo log分别实现了原子性和持久性
log 持久化的三种方式
数据库并发下的五类问题
四种隔离级别
RR 隔离级别下 select 幻读通过 MVCC 机制解决
select ... lock in share mode/select ... for update/insert/update/delete的幻读通过间隙锁来解决
数据库事务是指数据库管理系统(DBMS)中的一个操作序列,这些操作必须作为一个不可分割的单元执行,即要么全部执行成功,要么全部失败回滚。事务通常涉及到对数据库中的数据进行读写操作。
事务的 ACID 特性指四个关键特征:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。
✅ 原子性(Atomicity):一个事务的操作要么成功,要么失败,它是由 undo log 日志去完成的,实现回滚。
✅ 一致性(Consistency):事务执行结束后,数据必须保持一致性状态。在事务执行期间,数据库中的数据可以处于中间状态,但在事务完成时必须保证数据的一致性。
一致性是说无论事务提交还是回滚,不会破坏数据的完整性。比如A给B转100元,如果成功了,A的账户必定会扣100元,而B的账户必定会增加100元;如果失败了,A和B的账户余额不会改变。不会存在其他的情况,这样就是符合一致性的。
✅ 隔离性(Isolation):事务之间的操作是互不影响,互相隔离,是通过mvcc实现的。隔离级别不同,会影响到事务的并发性和数据一致性,比如出现脏读、不可重复读、幻读等问题。
多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
✅ 持久性(Durability):一旦事务提交,其所做的修改必须永久保存到数据库中。即使系统发生故障或宕机,数据也能够保持不变。
总结:只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是手段,C 是目的。
🔹 扩展
InnoDB中:
▪ 通过 undo log 支持事务回滚、当前读(多版本查询)
▪ 通过 redo log 实现持久性
▪ 通过两阶段提交实现一致性
▪ 通过当前读、锁实现隔离性
🔹 应用场景
▪ 银行转账、商品库存
redo log 物理日志,记录的是在某个数据页做了什么修改,而不是SQL语句的形式。它有固定大小,以循环写的形式记录日志,空间用完后会覆盖之前的日志。
undo log 和 redo log 并不是直接写到磁盘上,而是先写入 log Buffer。再等待合适的时机同步到 OS Buffer,由操作系统决定何时刷盘。
InnoDB 如何实现事务
Explain 语句
MySQL 锁
行锁、表锁、全局锁
共享锁、排他锁、意向锁
update、delete、insert、select for update ➕ 排他锁(悲观锁)
select lock in shard mode ➕ 共享锁(读锁)
普通 select 不加锁
只有写锁才分乐观和悲观
意向锁
意向锁是一种 MySQL 数据库中的锁,用于表级锁协调多个行级锁的使用。在表级锁 锁定一个表之前,MySQL 需要先获得一个意向锁,以表明要获取的锁类型(读锁或写锁),避免其他事务锁定整个表或锁定一部分表时引发死锁。
意向锁是一种轻量级锁,它不会影响其他事务的读操作,只有在某个事务要对表进行写操作时才会加上意向锁,而其他事务在读取表时只需要获取读锁,不需要等待意向锁的释放。
意向锁可以提高数据库并发性能,防止死锁的发生。它是表级锁,而不是行级锁。
✅ 意向共享锁(IS): 由语句 select ... lock in share mode 添加。 与表锁共享锁 (read)兼容,与表锁排他锁(write)互斥。
✅ 意向排他锁(IX): 由 insert、update、delete、select...for update 添加 。与表锁共享锁(read)及排他锁(write)都互斥,意向锁之间不会互斥。
一旦事务提交了,意向共享锁、意向排他锁,都会自动释放。
MySQL 产生锁 and 锁冲突
在 MySQL 中,当一个事务正在对一行数据进行修改时,如果另外一个事务也要对该行数据进行修改,就会产生锁冲突。此时,MySQL 会自动给其中一个事务加锁,使其等待另一个事务执行完毕后再继续执行。
产生锁和锁冲突的情况如下:
✅ 行锁冲突:当多个事务同时修改同一行数据时,就会产生行锁冲突。
✅ 表锁冲突:当多个事务同时修改同一张表的不同数据行时,也会产生表锁冲突。
✅ 死锁:当多个事务同时持有资源(如行锁或表锁)并互相等待对方释放资源时,就会产生死锁。
其他情况:如使用 LOCK TABLES 等语句主动加锁时也会产生锁。
为避免锁和锁冲突,开发人员应该尽量避免使用不必要的锁,避免长时间占用资源,并合理设置事务隔离级别等参数,保证事务的正确性和并发性。
死锁
死锁是指两个或多个进程互相等待对方释放资源而陷入无限等待的状态,导致程序无法继续执行下去,称为死锁。
当线程A持有独占锁a,并尝试去获取独占锁b的同时,线程B持有独占锁b,并尝试获取独占锁a的情况下,就会发生AB两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。
✅ 产生死锁的四个必要条件:
▪ 互斥条件:多个线程不能同时使用一个资源
▪ 持有并等待条件:线程A在等待资源2的同时并不会释放自己已经持有的资源1
▪ 不可剥夺条件:在自己使用之前不能被其他线程获取
▪ 环路等待条件:两个线程获取资源的顺序构成了环形链
✅ 预防和避免死锁需要:(从应用层来回答)
▪ 避免使用多个锁:在设计程序时,应尽量减少使用多个锁,或者在使用多个锁时,尽量将锁的持有时间缩短到最小。这可以减少死锁的概率。
▪ 避免嵌套锁:在使用嵌套锁时,需要注意锁的顺序,以避免不同线程获取锁的顺序不同而导致死锁。如果必须使用嵌套锁,可以使用统一的锁顺序来避免死锁。
▪ 使用超时机制:在获取锁时,可以设置一个超时时间,如果在指定时间内没有获取到锁,就放弃锁的获取,避免一直等待锁而导致死锁。
▪ 使用死锁检测和恢复机制:可以使用死锁检测机制来检测死锁的发生,并采取恰当的措施来解决死锁问题,比如终止一个进程或者回滚一个事务。
▪ 避免循环等待:在获取锁的时候,应该避免循环等待,即每个线程只能持有一个锁,而获取其他锁时必须先释放原有锁。
只要破坏其中一个必要条件就可以避免死锁,最常见的并且可行的就是使用资源有序分配法,来破坏环路等待条件
预防和避免死锁需要采取一些措施,包括:
避免资源独占:尽量避免一个进程在获得了某些资源后再次请求其他资源,而应该将所有需要的资源一次性申请到位。
避免资源持有和等待:当一个进程占用了一些资源并等待另一些资源时,其他进程就无法使用这些资源,容易引发死锁。因此,尽可能减少资源持有和等待时间。
避免资源互斥:有些资源在同一时间只能被一个进程占用,比如打印机、磁带机等,需要采用一些技术手段来避免资源互斥的问题。
引入资源剥夺策略:当一个进程请求的资源被其他进程占用时,可以采取剥夺资源的策略,即暂停占用该资源的进程,直到该资源被释放后再恢复该进程的执行。
引入进程抢占策略:当一个进程等待时间过长时,可以采取抢占其资源的策略,即中断正在执行的进程,强制释放其占用的资源。
以上是预防和避免死锁的一些方法,具体选择哪种方法需要根据具体情况进行分析和判断。
🎯 例子1:线程之间如何避免死锁
▪ 线程A和线程B获取资源的顺序要一样
▪ 线程 A 是先尝试获取资源 A,然后尝试获取资源 B 的时候
▪ 线程 B 同样也是先尝试获取资源 A,然后尝试获取资源 B
▪ 线程 A 和 线程 B 总是以相同的顺序申请自己想要的资源
🎯 例子2:MySQL 如何避免死锁
▪ 设置事务等待锁的超时时间
▪ 开启主动死锁检测
🎯 其他:利用工具排查死锁问题
▪ java:jstack
▪ C:pstack + gdb
MySQL 集群如何搭建
MySQL 慢查询如何优化?
MySQL 数据存储引擎
MySQL 执行计划
MySQL 主从同步原理
MySQL 读写分离、主从复制原理,如何解决 MySQL 主从同步的延时问题?
MySQL 中,什么情况下,设置了索引但无法使用?
存储拆分后,如何解决唯一主键?
海量数据下,如何快速查找一条记录?
如何实现分库分表?
数据量大时的优化
MySQL 事务的基本特性、隔离级别
MVCC
MVCC 是指多版本并发控制(Multiversion Concurrency Control),是一种并发控制机制,常用于数据库系统中,用于实现事务的并发控制。它允许在同一时间多个事务对同一个数据集合进行读取操作,同时防止数据不一致和其他并发问题。
InnoDB 是 MySQL 中最常用的存储引擎之一,它的 MVCC 实现是通过在每行记录中添加两个隐藏的列,分别记录行的创建时间和过期时间,以此来判断事务对该行记录的可见性。当一个事务需要读取一行记录时,InnoDB 首先读取这行记录的创建时间和过期时间,并根据这些信息判断该行记录是否可见。如果创建时间早于当前事务的开始时间,且过期时间晚于当前事务的开始时间,那么该行记录对当前事务可见。
在 InnoDB 中,MVCC 主要是通过实现以下几个机制来实现的:
⑴ 事务版本号:每个事务都有一个唯一的版本号,用来标识该事务的创建时间。
⑵ 读取视图:每个事务在开始时都会创建一个读取视图,记录该事务开始时间和其他信息。在事务执行期间,所有读取操作都要检查该视图,以确定读取哪些版本的数据。
⑶ undo 日志:在事务执行期间,如果对数据进行修改,那么会先将原始数据复制一份到 undo 日志中。这样,在回滚操作时就可以使用 undo 日志中的数据来还原原始数据。
⑷ 快照读取:在某些情况下,事务需要读取一个数据的历史版本,而不是当前版本。这时可以使用快照读取来实现,即在读取时根据事务开始时间和 undo 日志来读取历史版本的数据。
MySQL 5 类问题:脏读、幻读、不可重复读...
数据库事务的隔离性:多个事务并发进行时,一个事务的执行不应影响其他事务的执行。
正常情况下,肯定是多个事务同时操作一个数据库,所以事务之间的隔离就显得必不可少。先看一下,如果没有隔离性,会发生哪些情况?
会出现 5 类问题 ❓❓❓
1️⃣ 第一类丢失更新
一个事务在撤销的时候,覆盖了另一个事务已提交的更新数据
假设事务 A 和事务 B 操作同一个账户的资金:
时间 | 事务A | 事务B |
---|---|---|
T1 | 开启事务 | 开启事务 |
T2 | 查询账户余额:500元 | 查询账户余额:500元 |
T3 | 取走100元,剩余400元 | 取走100元,剩余400元 |
T4 | 提交事务,账户余额:400元 | - |
T5 | - | 撤销事务,账户余额:500元 |
事务 B 在撤销事务的时候,覆盖了事务 A 在 T4 的时候已经提交的更新数据。 A 在 T3 的时候已经取走了 100 元,此时的余额应该是 400 元,但是由于事务 B 开始的时候,余额是 500 元,所以回滚后,余额也会变成 500 元。
2️⃣ 脏读
一个事务读到了另一个事务未提交的更新数据
时间 | 事务A | 事务B |
---|---|---|
T1 | 开启事务 | 开启事务 |
T2 | 查询账户余额:500元 | - |
T3 | 取走100元,剩余400元 | - |
T4 | - | 查询余额:400元 |
T5 | 撤销事务,账户余额:500元 | - |
事务 A 在 T3 的时候取走了 400 元,但是未提交。事务 B 在 T4 时查询余额就能看到事务 A 未提交的更新。
3️⃣ 幻读
一个事务读到了另一个事务已提交的新增数据
时间 | 事务A | 事务B |
---|---|---|
T1 | 开启事务 | 开启事务 |
T2 | - | 执行select count统计 |
T3 | 新增一条数据 | - |
T4 | 提交事务 | - |
T5 | - | 执行select count统计 |
事务 B 在同一个事务中执行两次统计操作,得到的结果不一样
4️⃣ 不可重复读
一个事务读到了另一个事务已提交的更新数据
时间 | 事务A | 事务B |
---|---|---|
T1 | 开启事务 | 开启事务 |
T2 | 查询余额:500元 | 查询余额:500元 |
T3 | 取走100元,剩余:400元 | - |
T4 | 提交事务 | - |
T5 | - | 查询余额:400元 |
事务 B 在同一个事务中,两次读取余额,得到的结果却不一样。
5️⃣ 第二类丢失更新
一个事务在提交的时候,覆盖了另一个事务已提交的更新数据
时间 | 事务A | 事务B |
---|---|---|
T1 | 开启事务 | 开启事务 |
T2 | 查询账户余额:500元 | 查询账户余额:500元 |
T3 | 取走100元,剩余400元 | 取走100元,剩余400元 |
T4 | 提交事务,账户余额:400元 | - |
T5 | - | 提交事务,账户余额:400元 |
事务 A 和事务 B 分别取了 100 元,所以余额应该为 300 元。 但是事务 B 在提交的时候,覆盖了事务 A 已提交的更新数据,所以导致结果出错。
MySQL 四种隔离级别
为了解决上述五类问题,MySQL 提供了四种隔离级别:
MySQL 事务有四种隔离级别:
读未提交(Read Uncommitted):事务可以读取未提交的数据,可能会读到脏数据,会导致幻读、不可重复读、脏读等问题;
读已提交(Read Committed):只能读取已经提交的数据,可以避免脏读问题,但是可能会遇到不可重复读、幻读问题;
可重复读(Repeatable Read):保证同一个事务中多次读取同一数据的结果是一致的,避免了脏读和不可重复读问题,但是可能会遇到幻读问题;
序列化(Serializable):最高的隔离级别,可以避免所有并发问题,但是并发性能非常低,开销很大。
MySQL 的默认隔离级别是可重复读(Repeatable Read)。
其中,脏读指一个事务读到了另一个事务未提交的数据,不可重复读指同一个事务多次读取同一数据得到不同结果,幻读指同一个事务前后读取的数据集合不一致。
在实际使用中,应该根据具体情况选择合适的隔离级别,权衡数据的一致性和并发性能。
隔离级别 | 特点 | 可能导致 脏读 | 导致 不可重复读 | 导致 幻读 | 更新丢失 | |
---|---|---|---|---|---|---|
Read uncommitted 读未提交 | 允许读取未提交事务中更新的数据 | ✅ | ✅ | ✅ | ❌ | 最低的隔离级别 |
Read committed 读已提交 | 只能读取已提交的数据 | ❌ | ✅ | ✅ | ❌ | |
Repeatable read 可重复读 | 保证同一个事务中多次读取同一数据的结果是一致的 除非数据是被本身事务自己所修改 | ❌ | ❌ | ✅ | ❌ | MySQL 默认的隔离级别 安全性较高,效率较好 |
Serializable 串行化 | 完全串行化的执行,可能导致大量超时和锁竞争 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰 | ❌ | ❌ | ❌ | ❌ | 最高的隔离级别 损耗数据库性能,不推荐 |
索引的基本原理
✅ 索引:加快 sql 语句的执行速度
✅ 怎么加快❓ → 从 索引覆盖、聚集索引、二级索引 切入
✅ 需要建立索引的情况
通过慢查询判断 sql 慢的时候
慢查询指令 → show variables like '%slow_query_log';
✅ 查询单条 sql 语句是否走索引:explain 关键字
索引的设计原则
索引覆盖
最左前缀原则
MySQL 中 redo log、binlog、undo log
数据库设计三大范式。开发中应该遵守吗?
一条 SQL 语句在 MySQL 中的执行过程
在 MySQL 中,一条 SQL 语句的执行过程通常可以分为以下几个步骤:
✅ 词法分析和语法分析:MySQL 的 SQL 解析器会对输入的 SQL 语句进行词法分析和语法分析,以确定语句的结构和语法是否正确。
✅ 查询优化:MySQL 会对 SQL 语句进行优化,以确定最优的执行计划。在这个过程中,MySQL 会考虑许多因素,例如索引、表连接、统计信息等,以找到执行查询的最有效方式。
✅ 查询执行:在查询优化后,MySQL 开始执行查询,读取和处理数据。在执行过程中,MySQL 会根据查询中所涉及的表和列等信息,从磁盘中读取相应的数据,并进行计算和过滤操作。
✅ 结果返回:最后,MySQL 会将查询结果返回给客户端,完成整个查询过程。
需要注意的是,实际的执行过程可能会因为多种因素而不同,例如数据量、硬件配置等。另外,在并发环境下,多个查询可能会同时进行,需要使用锁和事务等机制来保证数据的一致性和正确性。
连接器:客户端首先进行身份验证,如果没通过直接返回。
查询缓存:如果身份验证不通过则查询缓存。缓存这个原理很容易理解,就是把常用的数据放到更高效的地方便于查询。 查询缓存也有缺点,就是每当数据更改的时候就要重新设置缓存,在MySQL8.0已经将查询缓存去掉。
分析器:分析器先会做“词法分析”。把你输入的内容进行识别,知道字符分别代表什么,有什么含义 然后进行语法分析,如果你的SQL语句不符合MySQL语法就会收到错误提醒。
优化器:优化器作用就是决定使用哪个索引,决定join表的连接顺序。 优化器会选择它自己认为最高效的方案,(也代表它不一定能选择出最优的方案)。
执行器:执行器还是先会判断有没有执行的权限,如果有权限的话才会执行下一步。遍历满足条件的行,并把组成的记录集作为结果集返回给客户端。

MySQL 数据备份。如何恢复半个月前的数据?
MySQL 数据备份是一个非常重要的工作,保证数据的安全性和可靠性。备份数据的方式有很多种,以下是其中一种基本的备份和恢复方式:
✅ 备份 MySQL 数据库 (逻辑备份)
我们可以使用 mysqldump 工具来备份 MySQL 数据库,该工具可以生成 SQL 脚本文件,包含数据库中所有表和数据的语句。在终端中运行以下命令:
mysqldump -u [username] -p [database_name] > [backup_file].sql
其中,[username] 是 MySQL 用户名,[database_name] 是需要备份的数据库名称,[backup_file].sql 是备份的文件名。
该命令会将 SQL 脚本文件导出到当前目录下。
✅ 恢复 MySQL 数据库
如果需要恢复之前备份的数据,可以运行以下命令:
mysql -u [username] -p [database_name] < [backup_file].sql
其中,[username] 是 MySQL 用户名,[database_name] 是需要恢复的数据库名称,[backup_file].sql 是备份的文件名。
该命令会将备份文件中的 SQL 语句执行,从而将数据恢复到指定的数据库中。
如果需要恢复半个月前的数据,可以选择备份文件中的某个时间点之前的数据,并使用以上方法进行恢复。
此外,还有其他的备份方式,如使用 MySQL 自带的 mysqlbinlog 工具进行增量备份,或使用第三方备份软件进行备份。根据实际需求选择合适的备份方式,并将备份文件存放在可靠的位置。
❗️❗️❗️需要注意的是,在恢复数据之前需要先备份当前的数据,以免恢复数据时覆盖了当前的数据,造成不可逆的损失。另外,备份数据时需要保证数据库处于稳定状态,不要在备份过程中进行增删改操作,否则备份的数据可能不完整或不准确。