MySQL :熟练掌握 MySQL 基础,理解MySQL基本架构、索引、事务、日志、锁、MVCC,具备一定的SQL调优能力。
基础
范式
之后可以加上业务上为什么要这样做
范式含义:构建数据库是遵循的一种规范
-
第一范式:每一列都是不可再分的原子项。
-
第二范式:主要针对联合主键,如果只有一个主键的话,满足第一范式就满足第二范式,每一列必须依赖所有主键,不能依赖于主键的一部分,解决办法:拆表
-
第三范式:每一列必须直接依赖主键,不能间接依赖
SQL语句完整的执行顺序
-
from:确定要查询的表
-
where:由条件过滤的记录
-
group by:对过滤后的记录进行分组
-
having:分组后的过滤(聚合函数再此)
-
select:选出需要的列,执行投影
-
order by:排序
MySQL架构
MySQL可以分为Server和存储引擎两部分
Server里面基本包括连接器,分析器,优化器和执行器
存储引擎负责数据的存储和提取,存储引擎有innoDB、MyISAN、Memory,不同的存储引擎共用一个Server层,而MySQL得默认存储引擎是innoDB
以执行一个简单的查询语句来理解他们之间的关系 select * from T where ID = 10
-
第一步我们需要连接数据库,这时候起作用的就是连接器,这时候我们输入ip地址,端口号,用户名和密码,如果用户名和密码错了,会报一个
Access denied for user
的错误,即拒绝用户访问,用户名和密码通过后,连接器会到权限表中查出你拥有的权限,在之后的权限判断逻辑都依赖此刻读到的权限,在连接上之后如果太久时间未操作,连接器会自动将连接断开,这个时间是由参数wait_timeout控制的,默认是8小时 -
第二部就是分析器,分析器先做词法分析再进行语法分析,比如说,从输入的select这个关键词分析出来是要做查询,table T识别为表名,id识别为列.再之后根据语法规则进行语法分析,判断是否满足MySQL语法,如果语句不对,就会报出一个
you have an error in your SQL
-
在经过分析器后,MySQL就知道要做什么了,接下来就到了优化器,这里举一个具体的例子,比如说有两张表,而有一个条件是where t1.a = 10 and where t2.b = 20,这里有两者方法进行查询,先从t1中取出a=10记录的ID值,在利用ID关联的表二,再判断t2里的b=20,或者反过来也许,这两种执行方法的查询结果是一样的,但执行的效率可能不同,这时候优化器的作用就是决定使用哪一个方案
-
最后就是执行器,在进行执行前,会先判断你对这个表是否有操作权限,没有就会报没有权限的错误,有的话就去调用引擎提供的接口去执行即可
日志
redo log
redo log其实就是按照write-ahead log思想,比如说,当有一条记录需要更新时,InnoDB引擎会先把记录写入redo log里,而之后innoDB引擎会在适当的时候,将这个操作记录更新到磁盘里。如果宕机了之后,MySQL可以通过redo log恢复已经写入但是未刷盘的数据
Page ID = 12345
Offset = 132
Length = 5
New Bytes = 0x44 0x61 0x76 0x69 0x64
binlog
bin log日志记录了所有更改数据的SQL语句,并且以二进制的格式保存在磁盘上,负责主从同步,持久化等
binlog 是追加写。"追加写"是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
undo log
回滚日志,他记录了事务对数据库更改的相反操作,如果执行了一条insert语句,那么undo log回记录一个DELETE操作,他主要是负责事务的回滚和实现MVCC,保证事务的原子性
在事务提交之前,undo log记录的是对数据库更改相反的操纵,所以在事务回滚时,我们只需读取undo log,就可以恢复到事务开始的状态
两阶段提交
目的是为了让两份日志之间的逻辑一致
首先要开启事务(这非常重要),redo log再写完后并没有直接提交,而是先prepare,在写完binlog后再commit提交
这样如果在redo log写完后,再写binlog时系统崩溃了,再恢复时候会根据redo log是否是prepare,那就进行回滚,保证两个日志逻辑一致。
像如果没有两阶段提交的话,就是先redo log后binlog。或者先binlog或redo log之间崩溃,那就会导致两个日志逻辑不一致
为什么修改页后不直接刷盘呢
每一次刷盘的成本都很高。我们可以采取write ahead log,先将数据写入redo log,再写入磁盘,这样可以减少IO次数,加快效率
事务
事务特性ACID
ACID(Atomicity、Consistency、Isolation、Durability)
-
原子性:指一个事务要么都成功,要么都失败,原子性是通过undo log(回滚日志)来保证的
-
一致性
-
隔离性:事务的隔离性是为了保证并发事务时的安全性,是通过MVCC(多版本并发控制)和锁来实现的
-
持久性:即保证数据的修改是持久的,即使数据库崩了也不会丢失,是靠日志来实现的(redo log 和 bin log)
并发事务
-
脏读:在一个事务中读取到了另一个事务还未提交的数据,如果这个事务最终回滚,这种现象称为脏读。
-
不可重复读:在一个事务内多次读取同一数据,但由于另一个事务的提交导致数据发生改变,造成每次读取的结果不同,这种现象称为不可重复读。
-
幻读:在同一事务中进行两次范围查询,但由于另外一个事务在该范围内插入了新的记录,导致第二次查询发现多了额外的数据
隔离级别
-
读未提交是指,一个事务可以读到另一个事务修改但未提交的数据
-
读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
-
可重复读是指,在一个事务内,多次读取同一行数据,结果总是一致,这个事务执行期间,其他事务的更新对它不可见。
-
串行化,顾名思义是后访问的事务必须等前一个事务执行完成,才能继续执行。
如何实现:
读未提交啥也不用
读已提交和可重复读主要靠MVCC机制实现
-
在读已提交情况下,事务开启后,每执行一个语句都会创建一个视图
-
在可重复读情况下,当事务开启时就会创建一个一致性视图,之后事务的查询都共用这个视图
串行化靠加锁来实现,在串行化的隔离级别加,只要是select查询是会对记录加上next-key锁,其他事务就无法操作记录了,从而避免了并发事务问题

-
若隔离级别是"读未提交", 则 V1 的值就是 2。这时候事务 B 虽然还没有提交,但是结果已经被 A 看到了。因此,V2、V3 也都是 2。
-
若隔离级别是"读提交",则 V1 是 1,V2 的值是 2。事务 B 的更新在提交后才能被 A 看到。所以, V3 的值也是 2。
-
若隔离级别是"可重复读",则 V1、V2 是 1,V3 是 2。之所以 V2 还是 1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。
-
若隔离级别是"串行化",则在事务 B 执行"将 1 改成 2"的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2。
怎么在RR级别下避免幻读
在可重复读的情况下,引起幻读的原因是快照读和当前读交替使用,只要我们只使用快照读,或只使用当前读(加个for update)就可以避免了
❗ 幻读产生的根本原因是: 使用了快照读读取了"旧视图",然后又用当前读操作了"新数据",二者不一致导致幻读。
视图
在访问数据库时,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。
-
在"可重复读"隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。
-
在"读提交"隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。
-
这里需要注意的是,"读未提交"隔离级别下直接返回记录上的最新值,没有视图概念
-
而"串行化"隔离级别下直接用加锁的方式来避免并行访问。
索引
索引就相当于一个字典的目录,可以帮助我们快速查找到我们所需要的值
-
主键索引和非主键索引
-
覆盖索引,指一个索引包含(或者说覆盖)所有需要查询的字段的值。比如说查询主键和该索引的键
-
联合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并。
索引结构
B树:多路平衡搜索树
-
一个节点有多个分支
-
叶子节点和非叶子节点都要存储值
B+树其实是B树的升级版
-
所有数据仅存储在叶子节点,非叶子节存储指针
-
叶子节点间采用双向链表连接
优点:
-
IO更少,因为非叶子节点不用存储数据,相同的数据量的情况下层数更低
-
范围查询高效,因为叶子节点间使用双向链表连接
-
查询性能更加稳定,所有查询都需要遍历到叶子节点,查询路径长度一致
索引优缺点
优点:
-
可以大大加快数据的查询速度,这也是创建索引的主要原因。
-
通过创建唯一索引,可以保证数据库表中每一行数据的唯一性。
-
在使用分组和排序子句进行数据查询时,也可以显著减少查询中分组和排序的时间。
缺点:
-
创建索引和维护索引要耗费时间,并且随着数据量的增加所耗费的时间也会增加。
-
除了数据表占数据空间之外,索引需要占磁盘空间,如果有大量的索引,索引文件可能比数据文件更快达到最大文件尺寸。
-
当对表中的数据进行增加、删除和修改的时候,索引也要动态地维护,这样就降低了数据的维护速度。
B+树的叶子节点和非叶子节点存储的是什么
非叶子节点存储的是指针。叶子节点的话,主键索引存储的是这一行的值,非主键索引存储的是主键索引。也就是回表
什么情况下要建索引
-
经常用于查询的字段
-
经常用于连接的字段(JOIN)建立索引,可以加快连接的速度
-
经常需要排序的字段建立索引,因为索引已经排好序,可以加快排序查询速度
索引失效
模型函 or not(懊恼) 最左(2min)
索引失效的场景是指在数据库查询中,虽然我们为某些字段建立了索引,但由于某些原因,查询时索引未能被使用,导致查询性能下降。
-
查询条件中模糊匹配,并且是以通配符开头的,这时候数据库根本无法利用索引(意味着需要扫描整个表来匹配数据),它都无法利用索引了,这不自然就失效了啊(索引的工作原理和数据结构决定了它无法高效处理以通配符开头的模式匹配)
-
查询条件中使用了类型隐式转换,比如说如果索引列是字符串类型(索引列是数据库中用于加速数据检索的一列或多列。通过对这些列创建索引,数据库可以更快地定位数据,而无需扫描整个表),而查询条件中数数字类型,那么数据库会尝试将字符串转化为数字类型进行比较,这种隐式转换可以会导致索引失效。|||||数据库系统通常有明确的数据类型优先级规则。在大多数情况下,数字类型(如
INT
、FLOAT
)的优先级高于字符串类型(如VARCHAR
、TEXT
)。当两种类型进行比较时,数据库会尝试将低优先级的类型(字符串)转换为高优先级的类型(数字),而不是相反。) -
第一种时索引条件中包含了函数表达式,因为数据库索引存储的时原始值,而不是经过函数处理后的值,这可能会导致索引失效
-
还有是当查询条件中使用了or但是有部分条件未命中索引,或者说它没创建索引的话,索引可能失效。比如说,如果查询条件里有where a = 'value1' or b = 'value2',但是这个b啊,它就没有索引,数据库可能就会选择全表扫描了,这样导致索引失效
-
查询条件中使用了not 或 != 操作符,索引可能失效,因为not这样的查询条件会导致数据库放弃使用索引,扫描大量数据来排除不符合条件的记录
-
使用了复合索引但是未遵循最左匹配原则,比如说有一个复合索引(A,B,C),如果查询条件只包含了B或C的话,而没有包含A,那么这个复合索引将无法使用,导致索引失效
最左匹配原则
在使用联合索引时,MySQL 会根据联合索引中的字段顺序,从左到右依次到查询条件中去匹配
所以,我们在使用联合索引时,可以将区分度高的字段放在最左边,这也可以过滤更多数据。
锁
简单讲讲
锁分为全局锁,表锁和行锁3大类
首先全局锁一般来说是在备份的时候使用,阻塞整个库的写操作,但是读操作时不影响的
Flush tables with read lock (FTWRL)
表锁一般用在处理批量操作和逻辑备份的情况下,但是实际上用表锁的情况不太多,因为他严重影响了并发性能
lock tables … read/write
行锁有3种,分别是记录锁,间隙锁,next-key锁
记录锁是锁住一行数据
间隙锁是锁住两个记录之间的间隙,比如说在插入数据的时候
next-key锁是锁住记录本身和前后的间隙。并且是可重复读下的默认加锁方式