MySQL
2024-12-12 20:05:04 1 举报
AI智能生成
大家面试冲丫丫
作者其他创作
大纲/内容
索引
数据结构
B+树
特点
叶子节点为双向链表
索引不是越多越好,索引越多,数据发生变化时维护成本越高
存储的最小数据单元为页(16kB)
默认主键自增索引
如果自己设置索引,可能造成页分裂
因为会出现插队现象:比如一个节点中是1,2,5;这时候如果生成一个4的索引,就会把5寄出去,造成页分裂
因为会出现插队现象:比如一个节点中是1,2,5;这时候如果生成一个4的索引,就会把5寄出去,造成页分裂
页分裂:
页分裂是指在B+树索引中,当一个节点(无论是叶子节点还是内部节点)满了,无法再容纳新的键值时,将该节点分裂成两个节点的过程。这个过程涉及到重新分配键值和子节点指针,以及可能的父节点更新。
页分裂是指在B+树索引中,当一个节点(无论是叶子节点还是内部节点)满了,无法再容纳新的键值时,将该节点分裂成两个节点的过程。这个过程涉及到重新分配键值和子节点指针,以及可能的父节点更新。
非叶子节点不存放具体数据,只存放索引数据
矮胖
与其他数据结构的对比
B+Tree vs B Tree
B+Tree只在叶子节点存储数据,而BTree的非叶子节点也要存储数据
B+Tree的单个节点的数据量更小,在相同的磁盘I/O次数下,就能查询更多的节点(查询相同节点时,需要的磁盘I/O数更少)
B+Tree叶子节点采用的是双链表连接,适合MySQL中常见的基于范围的顺序查找,而B树无法做到这一点
B+Tree vs 二叉树
对于有N个叶子节点的B+Tree,其搜索复杂度为O(logdN),其中d表示节点允许的最大子节点个数为d,在实际的应用中,d值是很大的,这就保证了,即使数据量很大,B+Tree的高度依然维持在一个较小的范围,也就是说一次数据查询操作只需要较少次数的磁盘I/O操作就能查询到目标数据,而二叉树的每个父节点的儿子节点个数是2个,意味着其搜素复杂度为O(logN),这已经比B+Tree高出了不少,因此二叉树检索到目标数据所经历的磁盘I/O次数要更多;(二叉树的叶子节点只有两个,这样会导致树更高,磁盘I/O数更多)
B+Tree vs Hash
Hash在做等值查询的时候很快,搜索复杂度为O(1),但是Hash不适合做范围查询
为什么采用B+树做索引的数据结构
B+树的非叶子节点不存放实际的记录数据,仅存放索引,因此数据量相同的情况下,相比存储即存储所有又存储记录的B树,B+树的非叶子节点可以存放更多的索引,因此B+树可以比B树更矮胖,查询底层节点的磁盘IO次数会更少
B+树有大量的冗余节点(所有非叶子节点都是冗余索引),这些冗余索引让B+树在插入,删除的效率都更高,比如删除根节点的时候,不会像B树那样会发生复杂的树的变化
B+树叶子节点之间用链表连接起来,有利于范围查询,而B树要实现范围查询,只能通过树的遍历来完成范围查询,这会涉及多个节点的磁盘IO操作,范围效率不如B+树
索引的分类
按数据结构分类
B+tree索引
Hash索引
Full-text索引
按物理存储分类
聚簇索引(主键索引)
叶子节点存放的是实际数据
所有完整的用户记录都存放在主键索引的B+Tree的叶子节点里
所有完整的用户记录都存放在主键索引的B+Tree的叶子节点里
二级索引
允许索引为重复值
叶子节点只存主键值,而不是数据本身
叶子节点只存主键值,而不是数据本身
索引覆盖
查数据时,要查的值就是索引值,不用再根据主键回表
按字段特性分类
主键索引
只有一个,索引列不为空
唯一索引
建立在UNIQUE字段上的索引
一张表允许有多个唯一索引
索引列值必须唯一
但允许有空值
一张表允许有多个唯一索引
索引列值必须唯一
但允许有空值
前缀索引
前缀索引是针对字符类型字段的前几个字符建立的索引,而不是整个字段上建立的索引,前缀索引可以建立在字段类型为char,varchar,binary,varbinary的列上,使用前缀索引的目的是为了减少索引占用的存储空间,提升查询效率
普通索引
建立在普通字段上的索引,既不要求字段为主键,也不要求字段为UNIQUE
按字段个数分类
单列索引
联合索引
索引下推
SELECT * FROM orders WHERE customer_id = 123 AND order_date > '2024-01-01';
在没有索引下推的情况下,数据库可能首先扫描 customer_id = 123 的记录,再对这些记录逐一应用 order_date > '2024-01-01' 的过滤条件。但如果启用了索引下推,数据库会直接在扫描过程中就应用 order_date > '2024-01-01',只检索满足这两个条件的记录,避免了不必要的数据扫描。
在没有索引下推的情况下,数据库可能首先扫描 customer_id = 123 的记录,再对这些记录逐一应用 order_date > '2024-01-01' 的过滤条件。但如果启用了索引下推,数据库会直接在扫描过程中就应用 order_date > '2024-01-01',只检索满足这两个条件的记录,避免了不必要的数据扫描。
索引跳跃
在mysql8.0.13以后,优化器引入了跳跃扫描技术,允许对复合索引的不同部分分别进行范围扫描,举例来说,如果复合索引是由f1和f2组成,而查询条件只设计f2>40,那么mysql会获取f1的不同值,比如f1=1和f1=2;然后根据每个f1的值,结合f2>40的条件,分别做范围扫描.将所有范围扫描的结构合并
索引跳跃ISS其实就是 MySQL 8.0 推出的适合联合索引左边列唯一值较少的情况的一种优化策略,如果左边列唯一值太多,就不会发生索引跳跃
索引跳跃ISS其实就是 MySQL 8.0 推出的适合联合索引左边列唯一值较少的情况的一种优化策略,如果左边列唯一值太多,就不会发生索引跳跃
最左匹配原则
索引(a,b,c)中,b和c是全局无序,局部相对有序的
联合索引失效(大部分),去"翻书"
<---索引失效
联合索引不失效
不失效的原因:有时候走继续走联合索引(查目录)比走全局扫描(翻书)更高效率
不失效的原因:有时候走继续走联合索引(查目录)比走全局扫描(翻书)更高效率
<----索引跳跃
索引跳跃:SQL不符合最左匹配原则的情况下,依然使用联合索引
索引跳跃扫描是 MySQL 8.0 中的一项创新特性,帮助在复合索引的查询中即使不符合最左匹配原则,也能够优化查询性能,特别适用于最左列基数较低的联合索引。
索引跳跃:SQL不符合最左匹配原则的情况下,依然使用联合索引
索引跳跃扫描是 MySQL 8.0 中的一项创新特性,帮助在复合索引的查询中即使不符合最左匹配原则,也能够优化查询性能,特别适用于最左列基数较低的联合索引。
联合索引的最左匹配原则,在遇到范围查询(如>,<)的时候,就会停止匹配,也就是范围查询的字段可以用到联合索引,但是在范围查询字段后面的字段无法用到联合索引,注意,对于>=,<=,BETWEEN,like前缀匹配的范围查询,并不会停止匹配
索引区分度
SQL语句执行流程
要知道执行流程需要知道
表结构
索引结构
步骤
过滤数据
查询字段
查不到
回表
意思是:索引字段少了,拿到主键再去主键索引查一次
<---索引跳跃
查得到
称为索引覆盖
意思是:查询的字段索引都能够覆盖,不需要回表
索引失效
使用不支持索引的操作符
LIKE '%abc', !=, OR
对索引列进行函数操作
例如:WHERE YEAR(date_column) = 2023
隐式类型转换
如果查询条件中的数据类型与索引列的数据类型不匹配,数据库会自动进行隐式类型转换,这可能会导致索引失效。例如:WHERE numeric_column = '123'(将数字与字符串进行比较)
使用OR
如果在or之前的条件列是索引值,而or之后不是
查询结果集过大
当返回结果集非常大,数据库可能选择全表扫描
GROUP BY 或 ORDER BY 无索引
使用DISTINCT
如果查询结果包含多个列,数据库需要根据多个列的值来判断哪些是重复的。如果没有合适的复合索引或索引覆盖了所有查询条件,数据库可能会放弃使用索引进行去重操作,转而使用排序操作来执行 DISTINCT,这会导致性能下降。
建立索引注意事项
索引的缺点:
占用物理空间
创建索引和维护索引耗费时间
降低表的增删改效率,因为每次增删改索引,B+树为了维护索引有序性,都需要进行动态维护
什么时候使用索引
字段有唯一限制的,因为这种字段区分度高
经常用于where查询条件的字段
经常用于group by和order by的字段,这样在查询的时候就不在需要再去做一次排序,因为我们都已经知道了建立索引之后在B+树中的记录都是排序好的
什么时候不适用索引
字段中存在大量重复数据,比如性别字段,如果在数据库表中,男女的记录分布均匀,那么无论搜索哪个值都可能得到一半的数据,在这些情况下,还不如不要索引,因为mysql还有一个查询优化器,查询优化器发现某个值出现在表的数据行中的百分比很高时,它一般会忽略索引,进行全表扫描
经常更新的字段不用建索引,比如不要对电商项目的用户余额建索引,因为索引字段频繁修改,由于要维护B+树的有序性,那么就需要频繁重建索引,这个过程是会影响数据库性能的
表数据太少时,不需要建索引
MySQL8.0之前建索引时可能会锁表
对于大表改结构,建一个新表,在新表上进行结构修改,复制旧表的结构和数据,把旧表的数据迁移过来,这样可以避免锁表
通过创建一个新的表,并在新的表上进行结构修改(如增加列、修改数据类型等),避免了在原表上执行 ALTER 操作,从而 不需要锁定原表
通过创建一个新的表,并在新的表上进行结构修改(如增加列、修改数据类型等),避免了在原表上执行 ALTER 操作,从而 不需要锁定原表
优化索引的方法
前缀索引优化
用某个字段中字符串的前几个字符建立索引,可以减小索引字段的大小,增加一个索引页中存储的索引值,有效提高索引的查询速度
局限性:order by就无法使用前缀索引;无法把前缀索引用作索引覆盖
局限性:order by就无法使用前缀索引;无法把前缀索引用作索引覆盖
覆盖索引
在二级索引中创建索引覆盖的条件,减少I/O
主键索引最好是自增的
使用自增主键,每次插入一条新记录,都是追加操作,不需要移动数据
子主题
使用非自增主键,由于每次插入主键的索引值都是随机的,因此每次插入新的数据时,就可能会插入到现有数据页中间的某个位置,这将不得不移动其他数据来满足新数据的插入,甚至需要从一个页面复制数据到另外一个页面,这种情况称为页分裂,页分裂还有可能会造成大量的内存碎片,导致索引结构不紧凑,从而影响查询效率
子主题
主键字段长度不要太大
因为主键字段长度越小,意味着二级索引的叶子节点越小,这样二级索引占用的空间就越小
explain
执行计划分析
执行计划分析
type
system
表中只有一行数据,MySQL会扫描这一行
const
查询条件是唯一索引(主键),const是与常量进行比较,查询效率更快,而eq_ref通常用于多表联查
select name from product where id = 1
select name from product where id = 1
eq_ref
SELECT * FROM orders o JOIN customers c ON o.customer_id = c_id
(id是主键) 使用主键/唯一索引进行扫描,通常用于多表联查
(id是主键) 使用主键/唯一索引进行扫描,通常用于多表联查
ref
使用非唯一索引(普通索引)查找,查询条件可能返回多行
range
索引范围扫描
index
使用索引全表扫描
all
没有使用索引全表扫描
从上到下性能递减
extra
Using index
查询只需通过索引获取数据
主键索引
非主键索引发生索引覆盖
Using temporary
查询需要创建临时表来存储中间结果
select count(*),city from users group by city order by city
(由于order by 和 group by,且city没有合适的索引,MySQL分组后需要使用临时表对数据进行排序)
select count(*),city from users group by city order by city
(由于order by 和 group by,且city没有合适的索引,MySQL分组后需要使用临时表对数据进行排序)
Using filesort
MySQL需要额外的排序操作来返回结果
select id,name from users order by name(如果name列没有索引,MySQL需要额外排序操作)
select id,name from users order by name(如果name列没有索引,MySQL需要额外排序操作)
possible_keys(字段表示可能用到的索引)
key(字段表示实际用的索引,如果这一项为NULL,说明没有使用索引)
key_len(表示索引的长度)
rows(表示扫描的数据行数)
锁
锁的种类
全局锁
执行:flush tables with read lock后整个数据库就处于只读状态
主要用于全库逻辑备份,这样在备份数据库期间,不会因为数据或表结构的更新,而出现备份文件的数据与预期的不一样
表级锁
表锁
独占表锁(lock tables .... read)
不可读不可写
共享表锁(lock tables ....write)
可读不可写
元数据锁
不需要显示添加,因为我们对数据库表进行操作时,会自动加上元数据锁
在事务提交后释放
在事务提交后释放
对一张表进行CRUD操作的时候,加的是MDL读锁
对一张表做结构变更操作的时候i,加的是MDL写锁
意向锁
没意向锁,加独占表锁就需要遍历表所有记录,查是否有记录存在独占锁,这样效率低
有了意向锁直接查该表是否有意向独占锁,就不用去遍历
目的:快速判断表里是否有记录被加锁
有了意向锁直接查该表是否有意向独占锁,就不用去遍历
目的:快速判断表里是否有记录被加锁
不和行级锁发送冲突;
意向锁之间不发生冲突;
和共享表锁,独占表锁冲突
意向锁之间不发生冲突;
和共享表锁,独占表锁冲突
AUTO-INC锁
插入数据时加,为被AUTO_INCREMENT修饰的字段赋递增值
innode_autoinc_lock_mode = 0
语句执行结束后释放锁
innode_autoinc_lock_mode = 2
轻量级锁,申请主键后就释放,不需要等语句执行后才释放
搭配binlog = statement时,在主从复制场景发生数据不一致
innode_autoinc_lock_mode = 1
行级锁
Record Lock(记录锁)
锁住的是一条记录
S(Share)
S与S兼容
S与X不兼容
X(排它)
X与X与S都不兼容
Gap Lock(间隙锁)
前开后开
前开后开
间隙锁之间兼容,两个事务可以同时包含共同间隙范围的间隙锁,因为间隙锁的目的是防止插入幻影记录而提出的
间隙锁疑难
子主题
Next-Key Lock(临建锁)
前开后闭
前开后闭
间隙锁+记录锁
不同事务获取相同范围间隙锁时互斥
插入意向锁(间隙锁)
一个事务插入一条记录时,需要判断插入位置是否被其他事务加了间隙锁(next-key也包含间隙锁),如果有的话,就会发生阻塞,直到拥有间隙锁的那个事务提交为止(间隙锁释放),在此期间会生成一个插入意向锁,表面事务想在某个区间插入记录,但是现在处于等待状态
不是意向锁,是一种特殊的间隙锁
和间隙锁主要区别是:两个事务不能在同一时间内,一个拥有间隙锁,另一个拥有该间隙区间内的插入意向锁(间隙意向锁为等待状态并不代表获取到了锁)
mysql如何加行级别锁
加锁基本单位:临建锁
临建锁退化
唯一索引等值查询
查到
记录锁
退化原因:仅靠记录锁就能解决幻读问题
没查到
间隙锁
退化原因:仅靠间隙锁就能解决幻读问题
唯一索引范围查询
大于等于的范围查询
如果等值查询的记录存在表中
该记录的next-key锁退化为记录锁
小于或小于等于的范围查询
条件值不在表中
扫描到终止范围查询的记录时,退化成间隙锁
其他扫描到的记录都是临建锁
其他扫描到的记录都是临建锁
条件值在表中
小于
扫描到终止范围查询的记录时,退化成间隙锁
其他扫描到的记录都是临建锁
其他扫描到的记录都是临建锁
小于等于
不会退化
大于的范围查询
不退化
非唯一索引等值查询
查询记录不存在
扫描到第一条不符合条件的二级索引,退化成间隙锁
查询记录存在
对于第一个不符合条件的二级索引记录,退化成间隙锁,在扫描过程中,对扫描到的二级索引记录是临建锁,同时,在符合查询条件的记录的主键索引上加记录锁
非唯一索引范围查询
对二级索引的加锁不会退化
没有加索引的查询
会走全表扫描,每一条记录都会加上临建锁,相当于锁住全表,这时如果其他事务对该表进行增 删 改都会阻塞
加锁对象:索引
sql语句与行级锁
普通的select语句不会对记录加锁(除了串行化隔离级别),它属于快照读,是通过mvcc实现的,如果要在查询时对记录加行级锁可以使用:
select ... lock in share mode; select ... for update这两种查询会加锁的语句称为锁定读
select ... lock in share mode; select ... for update这两种查询会加锁的语句称为锁定读
update和delete操作都会加行级锁,且锁的类型都是独占型(X锁)
update条件与索引
条件用索引,next-key锁会退化成记录锁,也就是只会给一行记录加锁
条件没有使用索引,就会全表扫描,于是就会对所有记录加上next-key锁,相当于把整个表锁住了
死锁
死锁的四个必要条件
互斥
占有且等待
不可强占用
循环等待
死锁例子
事务A查询订单时,给范围(1006,+oo)加上了间隙锁;
事务B查询订单时,也给范围(1006,+oo)加上了间隙锁;
事务A插入时,获取插入意向锁,设置为等待状态(等待状态意味着没有获得锁)插入意向锁与事务B的间隙锁冲突,阻塞
事务B同理
事务B查询订单时,也给范围(1006,+oo)加上了间隙锁;
事务A插入时,获取插入意向锁,设置为等待状态(等待状态意味着没有获得锁)插入意向锁与事务B的间隙锁冲突,阻塞
事务B同理
如何避免死锁
设置事务等待锁的超时时间
当一个事务的等待时间超过该值后,就对这个事务进行回滚,于是锁就释放了,另一个事务就可以执行了,在InnoDB中,参数innodb_lock_wait_timeout用来设置超时时间,默认为50秒
开启自动死锁检测
主动死锁检测在发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行,将参数innodb_deadlock_detect设置为on,表示开启这个逻辑,默认就开启
insert语句如何加行级锁
隐式锁:只有在可能发生冲突时加锁
特点是只在可能发生冲突时才加锁
减少了锁的数量,提高了系统性能
特点是只在可能发生冲突时才加锁
减少了锁的数量,提高了系统性能
冲突1:记录之间有加间隙锁
每插入(插入的时候原本数据库中是没有这个记录的,记住)一条新记录,都需要看一下待插入记录的下一条记录上是否已经被加了间隙锁
如果已加间隙锁,此时会生成一个插入意向锁,然后锁的状态设置为等待状态(PS:mysql加锁时,是先生成锁,然后设置锁的状态,如果锁是等待状态,并不意味着事务成功获得了锁),现象是insert语句被阻塞
如果已加间隙锁,此时会生成一个插入意向锁,然后锁的状态设置为等待状态(PS:mysql加锁时,是先生成锁,然后设置锁的状态,如果锁是等待状态,并不意味着事务成功获得了锁),现象是insert语句被阻塞
冲突2:insert的记录和已有记录存在唯一键冲突
主键索引冲突
报错,并且给重复的索引加上S型的记录锁
唯一二级索引冲突
报错,给重复的二级索引记录加S型的next-key锁
日志
为什么需要undolog
事务还没提交前,mysql会记录更新前的记录到undolog
事务还没提交前,mysql会记录更新前的记录到undolog
实现事务回滚,保障事务的原子性,当事务还没提交前,MySQL会记录更新前的记录到undolog,事务处理过程中,如果出现了错误或者用户执行了ROLLBACK语句,mysql可以利用undo log中的历史数据恢复事务开始之前的状态
实现MVCC关键因素之一,MVCC是通过ReadView+undo log实现的,undo log为每条记录保存多份历史数据,MySQL在执行快照读(普通select语句))的时候,会根据事务Read View里的信息,顺着undo log的版本链找到满足其可见性的记录
Buffer Pool
MySQL启动时,InnoDB会为Buffer Pool申请一片连续的内存空间,然后按照默认的16kb的大小划分出一个个的页,Buffer Pool中的页就叫做缓存页
数据页
索引页
插入缓存页
undo页
自适应哈希索引
锁信息
为什么需要Buffer Pool
当读取数据时,如果数据存在于Buffer Pool中,客户端会直接读取Buffer Pool中的数据,不用再去磁盘中读取
当修改数据时,如果数据存在于Buffer Pool中,那直接修改Buffer Pool中数据所在的页,然后将其页设置为脏页(该页的内存数据和磁盘上的数据已经不一致)为了减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘
Redo Log
redolog什么时候刷盘
redolog是物理日志,记录了某个数据页做了什么修改,比如对什么什么表空间中的什么什么数据页多少多少偏移量的地方做了什么什么更新当一条记录需要更新的时候,InnoDB会先更新内存(同时标记为脏页)然后将本次对这个页的修改记录到redolog
redolog什么时候刷盘
innodb_flush_log_at_trx_commit = 0
每次事务提交时,还是将redo log留在redo log buffer中,该模式下在事务提交时不会主动触发写入磁盘的操作
接着,每隔一秒,会把缓存在redo log buffer中的redo log,通过调用write()写到操作系统的Page Cache,然后调用fsync()持久化到磁盘
所以参数为0的策略,MySQL进程的崩溃会导致上一秒所有事务数据的丢失
接着,每隔一秒,会把缓存在redo log buffer中的redo log,通过调用write()写到操作系统的Page Cache,然后调用fsync()持久化到磁盘
所以参数为0的策略,MySQL进程的崩溃会导致上一秒所有事务数据的丢失
innodb_flush_log_at_trx_commit = 1
每次事务提交时,都将缓存在redo log buffer里的redo log直接持久化到磁盘,这样可以保证MySQL异常重启之后数据不会丢死
innodb_flush_log_at_trx_commit = 2
每次提交事务时,都只是把缓存在redo log buffer里的redo log写到redo log文件,注意写到[redo log文件]并不意味着写入到磁盘,因为操作系统的文件系统中有一个Page Cache,Page Cache是专门用来缓存文件数据的,所有写入[redo log文件]意味着写入操作系统的文件缓存
接着,每隔一秒调用fsync,将缓存在操作系统中的Page Cache里的redo log持久化到磁盘,所以参数为2的策略,较0更为安全,因为MysQL进程的崩溃并不会导致其丢失数据,只有在操作系统崩溃或者系统断电的情况下,上一秒钟所以事务数据才有可能丢失
接着,每隔一秒调用fsync,将缓存在操作系统中的Page Cache里的redo log持久化到磁盘,所以参数为2的策略,较0更为安全,因为MysQL进程的崩溃并不会导致其丢失数据,只有在操作系统崩溃或者系统断电的情况下,上一秒钟所以事务数据才有可能丢失
redolog文件写满了怎么办
默认情况下,InnoDB存储引擎有一个日志文件组(redo log Group),[重做日志文件组]有2个redo log文件组成,这两个redo日志文件名叫 ib_logfile0 和 ib_logfile1;重做日志文件组是以循环写的方式工作,从头开始写,写到末尾又回到开头,相当于一个环形
redo log是为了防止Buffer Pool中的脏页丢失而设计的,那么随着系统运行,Buffer Pool的脏页刷新到了磁盘,那么redo log对应的记录也就没用了,这时候我们擦除这些旧纪录,以腾出空间记录新的更新操作
为什么需要redolog
实现事务的持久性,让MySQL有崩溃恢复的能力,能够保证MySQL在任何时间段崩溃,重启之前已提交的记录都不会丢失
让写操作从[随机写]变成[顺序写],提升了MySQL写入磁盘的性能
为什么需要binlog
binlog文件是记录了所有数据库表结构变更和表数据修改的日志,不会记录查询类的操作,比如select和show操作
binlog与redolog的区别
适用对象不同
binlog是MySQL的Server层实现的日志
redolog是innodb存储引擎实现的日志
文件格式不同
binlog3种格式类型
STATEMENT(默认)
每一条修改数据的SQL都会被记录到binlog中(相当于记录了逻辑操作,所以针对这种格式,binlog可以称为逻辑日志),主从复制中slave端再根据SQL语句重现,但STATEMENT有动态函数的问题,比如你用了uuid或则now这些函数,你从主库上执行的结果并不是你从从库执行的结果,这种随时在变的函数会导致复制的数据不一致
ROW
记录数据最终被修改成什么样(这种格式的日志,就不能称为逻辑日志),不会出现STATEMENT下动态函数的问题,但ROW的缺点是每行数据的变化结果都会被记录,比如执行批量update语句,更新多少行数据就会产生多少条记录,使binlog文件过大,而在STATEMENT格式下只会记录一个update语句而已
MIXED
包含了STATEMENT和ROW模式,它会根据不同的情况自动适用ROW模式和STATEMENT模式
redo log是物理日志,记录的是在某个数据页做了什么修改,比如对XXX表空间中的YYY数据页ZZZ偏移量的地方做了AAA更新
写入方式不同
binlog是追加写,写满一个文件,就创建一个新的文件继续写,不会覆盖以前的日志,保存的是全量的日志
redolog是循环写,日志大小是固定,全部写满就从头开始,保存未被刷入磁盘的脏页日志
用途不同
binlog用于备份恢复,主从复制
redolog用于掉电等故障恢复
主从复制
实现过程
写入binlog:主库写binlog日志,提交事务,并更新本地存储数据
同步binlog:把binlog复制到所有从库上,每个从库把binlog写到relay log的中继日志中
回放binlog:从库创建一个回放binlog的线程,去读relay log的中继日志,然后回放binlog更新存储引擎中的数据,最终实现主从的数据一致性
同步binlog:把binlog复制到所有从库上,每个从库把binlog写到relay log的中继日志中
回放binlog:从库创建一个回放binlog的线程,去读relay log的中继日志,然后回放binlog更新存储引擎中的数据,最终实现主从的数据一致性
在完成主从复制后,你就可以在写数据时只写主库,在读数据时只读从库,这样即使写请求会锁表或者锁记录,也不会影响读请求的执行
MySQL主从复制模型
同步复制
MySQL主库提交事务的线程要等待所有从库的复制成功响应,才能返回客户端结果,这种方式在实际项目中,基本上没法用,原因:一是性能很差,因为要复制到所有节点才会返回响应,二是可用性也很差,主库和所有从库任何一个数据库出问题,都会影响业务
异步复制
MySQL主库提交事务的线程不会等待binlog同步到各从库,就返回客户端结果,这种模式一旦主库宕机,数据就会丢失
半同步复制
事务线程不用等待所有的从库复制成功响应,只要一部分复制成功响应回来就行,比如一主二从的集群,只要数据成功复制到任意一个从库上,主库的事务线程就可以返回给客户端,这种半同步复制的方式,兼顾了异步复制和同步复制的优点,即使出现主库宕机,至少还有一个从库有最新的数据,不存在数据丢失的风险
binlog什么时候刷盘
事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件
在事务提交的时候,执行器把binlog cache里的完整事务写到binlog文件中,并清空binlog cache 如下图
图中的write,指的是把日志写入binlog文件,但是并没有把数据持久化到磁盘,
fsync才是将数据持久化到磁盘的操作,这里会涉及磁盘的IO
MySQL提供一个sync_binlog参数来控制数据库binlog刷到磁盘上的频率
图中的write,指的是把日志写入binlog文件,但是并没有把数据持久化到磁盘,
fsync才是将数据持久化到磁盘的操作,这里会涉及磁盘的IO
MySQL提供一个sync_binlog参数来控制数据库binlog刷到磁盘上的频率
sync_binlog=0(默认)
每次提交事务都只write,不fsync,后续由操作系统决定何时将数据持久化到磁盘
sync_binlog=1
每次提交事务都会write,然后马上执行fsync;
sync_binlog=N
每次提交事务都write,但积累N个事务才fsync
一条update的执行流程
update t_user set name = "xiaolin' where id = 1
1执行器负责具体执行,会调用存储引擎的接口,通过主键索引树搜索id = 1这一行记录
id = 1在buffer pool中,直接返回执行器更新
不在buffer pool,将数据页从磁盘读入buffer pool,返回记录给执行器
3.开启事务,InnoDB层更新记录前,首先要记录相应的undo log,因为这是更新操作,需要把被更新的旧值记下来,也就是要生成一条undo log,undo log会写入Buffer Pool中的Undo页面,不过在内存修改改Undo页面后,需要记录对应的redo log
4.InnoDB层开始更新记录,会先更新内存(同时标记为脏页),然后将记录写到redo log,这个时候更新就算完成了,为了减少磁盘IO,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入磁盘,这就是WAL技术
5.至此,一条更新记录完了
6在一条更新语句执行完成后,然后开始记录该语句对应的binlog,此时记录的binlog会被保存到binlog cache,并没有刷新到硬盘上的binlog文件,在事务提交时才会统一将该事务运行过程中的binlog刷新到磁盘
7.事务提交
prepare阶段
将redolog对应的事务状态设置为prepare,然后将redolog刷新到硬盘
commit阶段
将binlog刷新到磁盘,接着调用引擎的提交事务接口,将redolog状态设置为commit,此时该状态并不需要持久化到磁盘,只需要write到文件系统的page cache就够了,因为只要binlog写到磁盘成功,就算redolog的状态还是prepare也没有关系,一样会被认为事务已经执行成功
8.一条更新语句执行完成
为什么需要两阶段提交
1.假设id = 1这行数据的字段name的值原本是'jay',然后执行update t_user set name = 'xiaolin' where id = 1
如果在将redolog输入到磁盘后,MySQL突然宕机了,而binlog还没有来得及写入磁盘,MySQL重启后,通过redolog能将Buffer pool中id = 1这行数据的name字段恢复到新值xiaolin,但是binlog里面没有记录这条更新语句,在主从架构中,binlog会被复制到从库,由于binlog丢失了这条更新语句,从库的这一行name字段是旧值jay,与主库不一致
如果将binlog刷入到磁盘之后,MySQL突然宕机,而redolog还没有来得及写入磁盘,由于redolog还没写,崩溃恢复以后这个事务无效,所以id=1这行数据的name字段还是旧值jay,而binlog里面记录了这条更新语句,在主从架构中,binlog会被复制到从库,从库执行这条更新语句,那么这一行name字段是新值xiaolin,与主库的值不一致
为了避免1中两份日志之间的逻辑不一致的问题,使用两阶段提交来解决
prepare阶段
将内部XA事务的ID写入redolog,同时将redolog对应的事务状态设置为prepare,然后将redolog持久化到磁(innodb_flush_log_at_trx_commit=1)
commit阶段
把内部XA事务的ID写入binlog,然后将binlog持久化到磁盘(sync_binlog = 1)接着调用引擎的提交事务接口,将redolog状态设置为commit,此时该状态并不需要持久化到磁盘,只需要write到文件系统的page cache中就够了,因为只要binlog写磁盘成功,就是redolog的状态还是prepare也没有关系,一样会被认为事务执行成功
异常重启出现的现象
A时刻和B时刻redolog都处于prepare状态
在MySQL重启后会按顺序扫描redolog文件,碰到处于prepare状态的redolog,就拿着redolog中的内部XA事务的ID去binlog查看是否存在此XID
如果binlog中没有当前内部XA事务的XID,说明redolog完成刷盘,但是binlog还没有刷盘,则回滚事务,对应时刻A崩溃恢复的情况
如果binlog中由当前内部XA事务的XID,说明redolog和binlog都已经完成了刷盘,则提交事务,对应时刻B崩溃恢复的情况
两阶段提交有什么问题
磁盘IO次数高
对于双1配置,每个事务提交都会进行两次fsync(刷盘),一次是redolog刷盘,一次是binlog刷盘
锁竞争激烈
两阶段提交虽然能够保证单事务两个日志的内容一致,但在多事务的情况下,却不能保证两者的提交顺序一致,因此,在两阶段提交的流程基础上,还需要加一个锁来保证提交的原子性,在并发量较大的时候,就会导致对锁的争用,性能不佳
引入组提交
MySQL磁盘IO很高,有什么优化方法
将sync_binlog设置为大于1
表示每次提交事务都write,单积累N个事务后才fsync,但风险是主机掉电时会丢N个事务的binlog日志
Innodb_flush_log_at_trx_commit设置为2
表示每次提交事务时,都只是把缓存在redo log buffer里的redo log写到redolog文件,注意写到redolog文件并不意味着写入到磁盘,因为操作系统的文件系统有一个pagecache,专门用来缓存文件数据的,所以写入redolog文件意味着写入到了操作系统的文件缓存,然后交由操作系统控制持久化到磁盘的时机,这样做的风险是主机掉电时会丢失数据
事务
事务特性有哪些特性
原子性
一个事务的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,而且事务在执行过程中发生错误,会被回滚到事务开始前的状态
通过undolog保证
一致性
指事务操作前和操作后,数据库保持一致性状态
通过持久性+原子性+隔离性保证
隔离性
数据库运行多个并发事务同时对其数据进行读写和修改的能力
通过MVCC(多版本并发控制)或锁机制来保证
持久性
事务处理结束后,对数据的修改是永久的,即便系统故障也不会丢失
持久性通过redolog保证
并发事务会引发哪些问题
脏读
一个事务[读到]了另一个事务[未提交事务修改过的数据]
不可重复读
在一个事务内多次读取同一数据,如果出现前后两次读到的数据不一样的情况,
幻读
在一个事务内多次查询某个符合查询条件的[记录数量],如果出现前后两次查询到的记录数量不一样
事务隔离级别有哪些
READ UNCOMMITTED(读未提交)
指一个事务还没提交时,它做的变更就能被其他事务看到
指一个事务还没提交时,它做的变更就能被其他事务看到
可能造成的问题
不可重复读
通过MVCC解决
MVCC:多版本(一行数据的版本链)并发(场景)控制(读取的结果)
MVCC是一种数据库技术,通过保存数据的历史版本(版本链),使得事务在不阻塞的情况下读取一致的数据快照,同时允许其他事务并发修改数据,从而提高数据库的并发性能和隔离性
MVCC:多版本(一行数据的版本链)并发(场景)控制(读取的结果)
MVCC是一种数据库技术,通过保存数据的历史版本(版本链),使得事务在不阻塞的情况下读取一致的数据快照,同时允许其他事务并发修改数据,从而提高数据库的并发性能和隔离性
版本链:当一个事务对记录进行修改时,数据库会创建一个新的记录版本,并将其添加到版本链的末尾。这个新版本会包含一个指向前一个版本的指针,以及当前事务的trx_id。同时,修改前的数据会被记录在undo日志中,以便在需要时可以恢复到之前的状态。通过这种方式,数据库可以保持数据的多个版本,并且能够根据事务的隔离级别和trx_id来访问正确的数据版本
REDAVIEW(读视图)
min_id
max_id
active_id_list
id指的是trx_id
min_id
max_id
active_id_list
id指的是trx_id
不同隔离级别 READVIEW区别
读已提交:每次读数据都生成一个READVIEW
可重复读:只在第一次读取数据生成READVIEW
读数据规则
比max_id大的(说明未提交)一定不读
比min_id小的(说明已提交)一定读
在活跃事务列表(未提交):不读;不在 读
幻读
脏读
如何实现的
直接读取最新数据就好
READ COMITTED(读已提交)
当一个事务提交之后,它做的变更才能被其他事务看到
当一个事务提交之后,它做的变更才能被其他事务看到
可能造成的问题
不可重复读
不可重复读指的是数据的值
幻读
如何实现的
通过Read View实现
REPEATABLE READ(可重复读)
指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB引擎默认的隔离级别
指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB引擎默认的隔离级别
可能出现的问题
幻读
幻读值的是数据的量
如何实现的
通过Read View实现
SERIALIZABLE(串行化)
会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突,后访问的事务必须等前一个事务执行完成后,才能继续执行
会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突,后访问的事务必须等前一个事务执行完成后,才能继续执行
如何实现的
通过加读写锁
Read View在MVCC里如何工作
MVCC:通过版本链来控制并发事务访问同一个记录时的行为就叫MVCC(多版本并发控制)
MVCC:通过版本链来控制并发事务访问同一个记录时的行为就叫MVCC(多版本并发控制)
Read View的四个字段
m_ids
指的是在创建Read View时,当前数据库中[活跃事务]的事务id列表,注意是一个列表,"活跃事务"指的就是,启动了但还没提交的事务
min_trx_id
指的是在创建Read View时,当前数据库中[活跃事务]中事务id最小的事务.也就是m_ids的最小值
max_trx_id
这个并不是m_ids的最大值,而是创建Read View时当前数据库应该给下一个事物的id值,也就是全局事务中最大的事务id值+1
creator_trx_id
指的是创建该Read View的事务的事务id
对于使用InnoDB存储引擎的数据库表,它的聚簇索引记录中包含下面两个隐藏列
roll_pointer
每次对某条聚簇索引记录进行改动时,都会把旧版本记录到undo日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录
trx_id
当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务id记录在trx_id隐藏列里
在创建Read View后,可以将记录中的trx_id划分为三种情况
<-一个事务去访问记录时
<-一个事务去访问记录时
记录的trx_id值小于Read View中的min_trx_id,表示这个版本记录是在创建Read View前已经提交的事务生成的,所有该版本的记录对当前事务可见
如果记录的trx_id值大于等于Read View中的max_trx_id值,表示这个版本的记录是在创建Read View后才启动的事务生成的,所有该版本记录对当前事务不可见
如果记录的trx_id值在Read View的min_trx_id和max_trx_id之间,需要判断trx_id是否在m_ids列表中
如果记录的trx_id在m_ids列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见
如果记录的trx_id不在m_ids列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见
可重复读是如何工作的
启动事务时生成一个Read View,然后整个事务期间都在用这个Read View
读已提交是如何工作的
每次读取数据时,都会生成一个新的Read View
快照读
查询的不是最新的数据,即使数据修改了,查的也是修改前的
普通的select都是快照读
普通的select都是快照读
当前读
查询的是最新的数据
当进行update/insert/delete/for update时,就需要当前读
select .... for update 也是当前读
当进行update/insert/delete/for update时,就需要当前读
select .... for update 也是当前读
MySQL与幻读
当前读
没有解决当前读的幻读
select where id = 1 1条
insert id = 10
update where id = 10
select id >1 2条
insert id = 10
update where id = 10
select id >1 2条
可以通过next-key(临建锁)解决d
select where id>1 for update 1 1条
insert id = 10(无法插入,因为next-key锁)
update where id = 10(无法更新)
select id > 1 一条
insert id = 10(无法插入,因为next-key锁)
update where id = 10(无法更新)
select id > 1 一条
select name where id = 5 for update 5 name = cf
update name where id = 5(无法更新,因为记录锁(next-key退化为记录锁))
select name where id = 5 for update 5 name = cf
update name where id = 5(无法更新,因为记录锁(next-key退化为记录锁))
select name where id = 5 for update 5 name = cf
快照读
MySQL因为有MVCC中的READVIEW机制从而解决了快照读的幻读
死锁问题
事务A,B发生死锁
总结:间隙锁之间不互斥,但是间隙锁与插入语句互斥
总结:间隙锁之间不互斥,但是间隙锁与插入语句互斥
具体业务场景
name wx_id zfb_id phonenumber
cf kkk ccc 12331213
name wx_id zfb_id phonenumber
cf kkk ccc 12331213
A
1.通过wx登录,select for update name where id = wx_id
2.根据手机号插入用户数据 insert name where phonenumber = 12331213
1.通过wx登录,select for update name where id = wx_id
2.根据手机号插入用户数据 insert name where phonenumber = 12331213
B
1.通过支付宝登录 select for update name where id = zfb_id
2.根据手机号插入用户数据 insert name where phonenumber = 12331213
1.通过支付宝登录 select for update name where id = zfb_id
2.根据手机号插入用户数据 insert name where phonenumber = 12331213
事务A尝试锁定支付宝ID对应的用户数据,以更新或插入手机号数据。
事务B尝试锁定微信ID对应的用户数据,以更新或插入手机号数据。
事务A等待支付宝ID对应的用户数据被事务B释放。
事务B等待微信ID对应的用户数据被事务A释放。
如果用户已经通过微信登录时,之后再通过支付宝登录,就不用再插入数据了,只需要修改
事务B尝试锁定微信ID对应的用户数据,以更新或插入手机号数据。
事务A等待支付宝ID对应的用户数据被事务B释放。
事务B等待微信ID对应的用户数据被事务A释放。
如果用户已经通过微信登录时,之后再通过支付宝登录,就不用再插入数据了,只需要修改
SQL例子
A
1.SELECT for update name where id = 3 for update
3.insert xx where id = 3
1.SELECT for update name where id = 3 for update
3.insert xx where id = 3
B
2.SELECT for update xx where id = 4 for update
3.insert xx where id = 4
2.SELECT for update xx where id = 4 for update
3.insert xx where id = 4
解决死锁方法
降低隔离级别 RR->RC
幻读了怎么办?
幻读只是一个现象,我们可以通过业务代码逻辑去解决
幻读只是一个现象,我们可以通过业务代码逻辑去解决
加分布式锁
A查询插入的时候加锁,这时候B若要进行只能等待
name wx_id zfb_id phonenumber
cf kkk ccc 12331213
A查询插入的时候加锁,这时候B若要进行只能等待
name wx_id zfb_id phonenumber
cf kkk ccc 12331213
A
lock.lock(12331213)
1.通过wx登录,select for update name where id = wx_id()
2.根据手机号插入用户数据 insert name where phonenumber = 12331213(插入)
释放锁
lock.lock(12331213)
1.通过wx登录,select for update name where id = wx_id()
2.根据手机号插入用户数据 insert name where phonenumber = 12331213(插入)
释放锁
B
1.通过zfb登陆 select for update name where id = zfb_id)
2.根据手机号插入用户数据 insert name where phonenumber = 12331213(在A释放锁之前,无法插入,只有A插入成功后释放锁,才可以插入,但此时已经不需要插入了,因为上一条通过支付宝id查到的数据已经有电话号码)
1.通过zfb登陆 select for update name where id = zfb_id)
2.根据手机号插入用户数据 insert name where phonenumber = 12331213(在A释放锁之前,无法插入,只有A插入成功后释放锁,才可以插入,但此时已经不需要插入了,因为上一条通过支付宝id查到的数据已经有电话号码)
MySQL调优
架构
读写分离
主从
分库分表
缓存
java数据结构
硬件
cpu
内存:buffer pool要占内存的75%左右
硬盘
MySQL版本
MySQL连接数:
最大连接数=(CPU核心数*2)+有效磁盘数
最大连接数=(CPU核心数*2)+有效磁盘数
sql
平常注意的点
禁止select *
重复查询相同的数据
可以查询后作为参数传递,不要重复查询
改哪个字段更新哪个字段
错误做法:改哪个字段,先查出对象,然后在对象中去改这个字段,这样其他线程可能也对这个对象进行更改,导致其他线程的更新失效
limit
偏移量大
select * from device_weight_record where limit 100000, 20;
Select * from device_weight_record where id > 100000 LIMIT 20;
Select * from device_weight_record where id > 100000 LIMIT 20;
数据量大
按需查询字段,减少网络IO消耗
避免使用select * 减少MySQL优化器负担
查询的字段尽量保证索引覆盖
使用nosql缓存数据缓解MySQL数据库压力
日期类型
datetime:8个字节
timestamp:4个字节
date:三个字节
不支持时分秒
不支持时分秒
能用tinyint就用tinyint
char比varvhar速度快
对于多插入的情况进行批量插入
使用update时,条件不带索引
因为需要全表扫描,会导致锁表,整个线程停滞
!=会导致索引失效
索引不是越多越好
like %xxx
索引失效
索引失效
使用小表驱动大表
**小表驱动大表**是MySQL调优中的一种策略,指的是在多表连接时,优先从数据量较小的表开始查找,再去查找大表。这能有效减少扫描的数据量,提高查询效率。
用连接查询代替子查询
group by后面的字段加索引
系统级别的调整
redolog刷盘策略
innodb_flush_log_at_trx_commit
0
事务提交时,redo log 仅写入内存,由后台线程每秒批量写入磁盘,性能最优,但若崩溃,可能丢失最近一秒内所有事务数据。
1
每次事务提交时,redo log 都会写入磁盘,数据最安全,但性能最慢。
2
每秒将 redo log 写入磁盘一次,性能较好,但可能丢失最近一秒的数据。
binlog刷盘策略
sync_binlg
慢sql的定位与分析
执行时间超过预设阈值的 SQL 语句
执行时间超过预设阈值的 SQL 语句
查看是否开启
SHOW VARIALBES LIKE '%slow_query_log%'
开启
set global slow_query_log=1
查看阈值时间
SHOW VARIABLES LIKE '%long_query_time%'
设置阈值
set long_query_time=3
对于运行的SQL语句没有使用索引,则MySQL数据库也可以将这条SQL语句记录到慢查询日志文件
阿里云查看慢日志
explain
执行计划分析
执行计划分析
type
system
表中只有一行数据,MySQL会扫描这一行
const
查询条件是唯一索引(主键)
eq_ref
SELECT * FROM orders o JOIN customers c ON o.customer_id = c_id
(id是主键) 使用唯一索引进行连接查询
(id是主键) 使用唯一索引进行连接查询
ref
使用非唯一索引(普通索引)查找,查询条件可能返回多行
range
索引范围扫描
index
使用索引全表扫描
all
没有使用索引全表扫描
从上到下性能递减
extra
Using index
查询只需通过索引获取数据
主键索引
非主键索引发生索引覆盖
Using temporary
查询需要创建临时表来存储中间结果
select count(*),city from users group by city order by city
(由于order by 和 group by,且city没有合适的索引,MySQL分组后需要使用临时表对数据进行排序)
select count(*),city from users group by city order by city
(由于order by 和 group by,且city没有合适的索引,MySQL分组后需要使用临时表对数据进行排序)
Using filesort
MySQL需要额外的排序操作来返回结果
select id,name from users order by name(如果name列没有索引,MySQL需要额外排序操作)
select id,name from users order by name(如果name列没有索引,MySQL需要额外排序操作)
Using index for group-by
select count(*), city from users group by city(有索引city,MySQL使用索引来进行分组)
索引
<-多使用,提高性能
<-多使用,提高性能
最左前缀原则
索引覆盖
索引下推
主键使用自增主键,避免页分裂
长的字段建索引可以使用前缀索引
内存
Buffer Pool
为什么需要Buffer Pool
当读取数据时,如果数据存在于Buffer Pool中,客户端会直接读取Buffer Pool中的数据,不用再去磁盘中读取
当修改数据时,如果数据存在于Buffer Pool中,那直接修改Buffer Pool中数据所在的页,然后将其页设置为脏页(该页的内存数据和磁盘上的数据已经不一致)为了减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘
Buffer Pool有多大
Buffer Pool是MySQL启动的时候,向操作系统申请的一片连续的内存空间,默认配置下Buffer Pool只有128MB,可以通过调整innodb_buffer_pool_size参数来设置Buffer Pool的大小,一般建议设置成可可用物理内存的60%-80%
Buffer Pool缓存什么
MySQL启动时,InnoDB会为Buffer Pool申请一片连续的内存空间,然后按照默认的16kb的大小划分出一个个的页,Buffer Pool中的页就叫做缓存页
查询一条记录时,InnoDB会把整个页的数据加载到Buffer Pool,然后再通过
查询一条记录时,InnoDB会把整个页的数据加载到Buffer Pool,然后再通过
数据页
索引页
插入缓存页
undo页
自适应哈希索引
锁信息
为了更好的管理这些在buffer pool中的缓存页,InnoDB为每个缓存页都创建了一个控制块,控制块信息包括[缓存页的表空间,页号,缓存页地址......];
控制块也是占用内存空间的,它是放在buffer pool的最前面,接着才是缓存页
控制块也是占用内存空间的,它是放在buffer pool的最前面,接着才是缓存页
如何管理Buffer Pool
如何管理空闲页
提高读性能
为了能够快速找到空闲的缓存页,使用链表结构,将空闲缓存页的[控制块]作为链表的节点,这个链表称为Free链表(空闲链表)
有了Free链表后,每当需要从磁盘中加载一个页到buffer pool中时,就从free链表中取一个空闲的缓存页,并且把该缓存页对应的控制块的信息填上,然后把改缓存页对应的控制块从Free链表中移除
为了能够快速找到空闲的缓存页,使用链表结构,将空闲缓存页的[控制块]作为链表的节点,这个链表称为Free链表(空闲链表)
有了Free链表后,每当需要从磁盘中加载一个页到buffer pool中时,就从free链表中取一个空闲的缓存页,并且把该缓存页对应的控制块的信息填上,然后把改缓存页对应的控制块从Free链表中移除
如何管理脏页
buffer pool还能提高写性能
更新数据的时候,不需要每次都要写入磁盘,而是将buffer pool对应的缓存页标记为脏页,然后再由后台线程将脏页写入磁盘
为了能快速知道哪些缓存页是脏的,就设计出了Flush链表,它跟free链表是类似的,链表的节点也是控制块,区别在于Flush链表的元素都是脏页
有了Flush链表后,后台线程就可以遍历Flush链表,将脏页写入到磁盘
更新数据的时候,不需要每次都要写入磁盘,而是将buffer pool对应的缓存页标记为脏页,然后再由后台线程将脏页写入磁盘
为了能快速知道哪些缓存页是脏的,就设计出了Flush链表,它跟free链表是类似的,链表的节点也是控制块,区别在于Flush链表的元素都是脏页
有了Flush链表后,后台线程就可以遍历Flush链表,将脏页写入到磁盘
如何提高缓存命中率
通过LRU算法:链表的头部的节点是最近使用的,而链表末尾的节点是最久没被使用的,那么当空间不够了,就淘汰最久没被使用的节点
问题
预读失效
什么是预读失效
因为程序是有空间局部性的,靠近当前被访问数据的数据,在未来很大概率会被访问,所以mysql在加载数据页的时候,会提前把他相邻的数据页一并加载进来,目的是为了减少磁盘IO,但是可能这些被提前加载进来的数据页,并没有被访问,相当于这个预读白做了,这个就是预读失效
因为程序是有空间局部性的,靠近当前被访问数据的数据,在未来很大概率会被访问,所以mysql在加载数据页的时候,会提前把他相邻的数据页一并加载进来,目的是为了减少磁盘IO,但是可能这些被提前加载进来的数据页,并没有被访问,相当于这个预读白做了,这个就是预读失效
怎么解决预读失效而导致缓存命中率低的问题
为了解决这个问题,mysql改进了LRU算法,将LRU划分为old区和young区,划分这两个区域后,预读的页就只需要加入到old区的头部,当页被真正访问的时候,才将页插入young区的头部
为了解决这个问题,mysql改进了LRU算法,将LRU划分为old区和young区,划分这两个区域后,预读的页就只需要加入到old区的头部,当页被真正访问的时候,才将页插入young区的头部
Buffer Pool污染
什么是buffer pool污染
当一个SQL语句扫描了大量的数据,在buffer pool空间比较有限的情况下,可能会将buffer pool里所有的页都替换出去,导致大量的热数据被淘汰了
当一个SQL语句扫描了大量的数据,在buffer pool空间比较有限的情况下,可能会将buffer pool里所有的页都替换出去,导致大量的热数据被淘汰了
怎么解决出现Buffer Pool污染而导致缓存命中率下降的问题
mysql把进入到young区域条件增加了一个停留在old区域的时间判断,
具体是这样做的,在对某个处在old区域的缓存页进行第一次访问时,就在它对应的控制块记录下这个访问时间,
如果后续的访问时间与第一次访问的时间在某个时间间隔内,那么缓存页就不会被从old区域移动到young区域的头部;
如果后续访问时间与第一次访问时间不在某一个时间间隔内,那么该缓存页移动到young区域的头部
mysql把进入到young区域条件增加了一个停留在old区域的时间判断,
具体是这样做的,在对某个处在old区域的缓存页进行第一次访问时,就在它对应的控制块记录下这个访问时间,
如果后续的访问时间与第一次访问的时间在某个时间间隔内,那么缓存页就不会被从old区域移动到young区域的头部;
如果后续访问时间与第一次访问时间不在某一个时间间隔内,那么该缓存页移动到young区域的头部
脏页什么时候会被刷入磁盘
小疑惑
如果脏页还没来得及刷入磁盘,mysql宕机了,不就丢失数据了吗
这个不用担心,InnoDB的更新操作采用的是write ahead log策略,即先写日志,再写入磁盘,通过redolog日志让mysql拥有了崩溃恢复能力
这个不用担心,InnoDB的更新操作采用的是write ahead log策略,即先写日志,再写入磁盘,通过redolog日志让mysql拥有了崩溃恢复能力
触发脏页刷磁盘的情况
redolog日志满了的情况下,会主动触发脏页刷新到磁盘
Buffer Pool空间不足时,需要将一部分数据页淘汰掉,如果淘汰的是脏页,需要先将脏页同步到磁盘
mysql认为空闲时,后台线程会定期将适量的脏页刷入磁盘
mysql正常关闭之前,会把所有脏页刷新磁盘
0 条评论
下一页