文章目录
综合
面试官:谈谈你对Mysql数据库读写分离的了解,并且有哪些注意事项?
约束、key、索引
MySQL——约束(constraint)详解
MySQL(五) MySQL中的索引详讲
【原创】MySQL(Innodb)索引的原理
多个单列索引和联合索引的区别详解
MySQL 覆盖索引详解
mysql 外键(foreign key)的详解和实例
MySQL外键约束_ON DELETE CASCADE/ON UPDATE CASCADE
B+树看这一篇就够了(B+树查找、插入、删除全上)
作为a阶的内部节点,其实指的就是某个节点维护了a个key,并且有几个指向子节点的指针,这a个key用来确定子节点指针指向的子节点树储存的key的边界情况。在 MySQL 中,InnoDB 存储引擎的数据底层存储通常是按照主键来排列的,这种存储方式称为聚簇索引。那么为什么不要在中间节点存储指向数据的指针,要理解这个,一个重要的前提是,这些数据结构不是存储在内存的,而是存储在磁盘中,传统的机械硬盘中数据是一个扇区一个扇区存储的,一个扇区存了数据,存指针的数据就少了,而定位下一个数据需要磁头运动的,磁头运动的距离长了,耗时就多了,如果不存数据,将原本用来存指向数据的指针的空间用来多存几个key,这样树就会矮胖了。每次索引的时间都是相同的,数据库的查询更加稳定。
mysql中的b+tree的阶数是多少
因为每个页的大小是固定的,索引存储在一个页里,页大小和节点大小决定了b+tree的阶数,因此每个节点越小,阶数就越多。
为什么 MySQL 使用 B+ 树
b+tree的一个重要优势是,在范围查询的时候,可以顺序io而不是随机io
理解I/O:随机和顺序
比如提取id介于4-9的值,对于B+树,只要找到了根据索引定位到了id=4这一条数据,那么磁盘顺序往下查找即可找到4-9的数据;但对于B树,需要先根据索引定位id=4在磁盘上的位置,然后再根据索引定位id=5在磁盘上的位置,以此类推。
MySQL自增主键id不连续的原因
联合索引在B+树上的存储结构及数据查找方式
数据库数据类型
MySQL常见的数据类型(八)
MySQL之char、varchar和text的设计
1、 char长度固定, 即每条数据占用等长字节空间;适合用在身份证号码、手机号码等定。
2、 varchar可变长度,可以设置最大长度;适合用在长度可变的属性,但数量上限确定的情况下;
3、 text不设置长度, 当不知道属性的最大长度时,适合用text。比如地址;
「MYSQL」MYSQL中的int(11)到底代表什么意思?
时间
Mysql的timestamp(时间戳)详解以及2038问题的解决方案
MySQL 日期类型函数及使用
mysql TIMESTAMP 不能为NULL,但是datetime可以为null
是否为null要参照另一个配置;
数据定义语言DDL
mysql在B数据库下创建一个与A数据库中一样的表
MySQL表的四种分区类型
MySQL HASH分区
MySQL KEY分区
MySQL存储引擎之Myisam和Innodb总结性梳理
【原创】杂谈自增主键用完了怎么办
mysql 5.6 在线 DDL
old_alter_table=0,不启用旧的copy the table 的模式来进行ddl操作;
最后一个例子似乎少了个session,表明的是select没问题,但是update会被block
数据查询语言DQL
LEFT JOIN 和JOIN 多表连接
SQL的各种连接Join详解
oin是先笛卡尔积,然后再筛选,所以如果on条件里出现了or并且两个条件都满足,那么这条就会重复
left join 连表时,on后多条件无效问题
ON 条件(“A LEFT JOIN B ON 条件表达式”中的ON)用来决定如何从 B 表中检索数据行。如果 B 表中没有任何一行数据匹配 ON 的条件,将会额外生成一行所有列为 NULL 的数据
select * from A left join B on A.id=B.id and A.user='zzz'
的含义是,如果B表中存在一条数据,满足A.id=B.id
的同时,对应A表的这个user数据等于zzz,那么就是一个成功的join,否则就会生成一行null作为B表的数据
查询mysql 中某字段为空值的数据
浅析MySQL中exists与in的使用 (写的非常好)
关于SQL中Union和Join的用法
Union不适用与表中存在完全相同的两列的情况,因为这样会删除重复的表;
MySQL 的CASE WHEN 语句使用说明
MYSQL–表分区、查看分区
mysql 行转列 列转行
SETSQL_SAFE_UPDATES=0
mysql中实现类似于oralce中row_number()的方法
MySQL里的found_row()与row_count()的解释及用法
使用MySQL子查询选择第n个最高纪录
MYSQL常用内置函数详解说明
MySQL 字段的说明和备注信息
sql虚构一个常量字段的查询
FROM_UNIXTIME 格式化MYSQL时间戳函数
SQL 随机抽取样本
MySQL之CONCAT()的用法
MySQL group_concat()函数
MySQL Prepared语句
#多变量情况
PREPARE stmt1 FROM 'SELECT productCode, productName
FROM products
WHERE productCode = ? or productCode = ?';
SET @pc1 = 'S10_1678';
SET @pc2 = 'S10_1949';
EXECUTE stmt1 USING @pc1,@pc2;
DEALLOCATE PREPARE stmt1;
MySQL 5.7新特性之Generated Column
Oracle生成不重复字符串 sys_guid()与Mysql生成唯一值
Mysql 默认生成分布式主键UUID
mysql 默认值除了timestamp不支持其他函数,不允许 default uuid()格式,只能通过触发器实现
数据操纵语言DML
mysql在表的某一位置增加一列、删除一列、修改列名
如何让mysql的自动递增的字段重新从1开始呢?
mySql中删除(truncate,delete,drop)的用法
Mysql on duplicate key update用法及优缺点
数据控制语言DCL
MySQL存储过程中declare和set定义变量的区别
MySQL存储过程参数
存储过程在启动时无法访问OUT参数的初始值。
存储过程
MySQL数据库存储过程
调整分隔符DELIMITER //
是以为在终端操作的,存储过程中不可避免要输入;
,而如果不修改分隔符的话,当终端看见;
,终端就会认为到头了。
设置
(转)解决mysqldump: Got error: 1290以及secure-file-priv option简解
Windows下更改MySQL数据库的存储位置
如何重启MySQL,正确启动MySQL
【MySQL】MySQL的配置文件的区别和说明
/etc/mysql/my.cnf->/etc/alternatives/my.cnf -> /etc/mysql/mysql.cnf
tmd我是整不明白这么连接一下有啥用,而/etc/mysql/mysql.cnf引用了两个文件夹下面的配置
其中d结尾的代表的是服务配置,linux都这样,例如mysqld
sql_mode
variables
【MySQL】explicit_defaults_for_timestamp 参数详解
配置文件
mysql配置文件优化
别忘了在使用mysql之前就决定好要不要拆分表空间;
权限管理
MySQL创建用户与授权
Mysql用户管理
Mysql 用户权限管理
mysql.user存放用户账户信息以及全局级别(所有数据库)权限,决定了来自哪些主机的哪些用户可以访问数据库实例,如果有全局权限则意味着对所有数据库都有此权限
而mysql.db表:存放具体某个数据库级别的权限,决定了来自哪些主机的哪些用户可以访问此数据库
例如,select * from mysql.user;
前三行是root用户,拥有全部权限,第三行是匿名用户,第四行是我新建的admin用户,该表中表明该用户全局权限为空
此时来执行select * from mysql.db;
,结果如下
第三行表明,来自localhost的admin用户对数据库admin有着全部权限,而前两行表明,来自任何地方的匿名用户(其实实际意义为任何用户),都可以访问test数据库,以及以名称test_开头的数据库
再来执行show grants for admin@'localhost';
可以看到结果是与db对应的,经过实验表明,grant select on db.* to a_user@
会影响db表并进而影响show grants
的值,当然还有其他的权限表,应该大差不差。
MySQL : Access denied for user ‘’@‘localhost’ to database 'mysql’问题看点。
%’并不包括localhost
mysql全局权限账户%登录不上
如果一个用户在user表里只有admin@%
,那么在本地登录的时候,是无法登录到localhost
的,只能不输密码当做一个匿名用户登录
Mysql 1044错误代码:Access denied for user ‘’@'localhost’的解决方案
上面这边博客实际上是通过删除匿名用户来解决问题,并不推荐。
新建用户,并指定其只能访问某个数据库
首先创建用户CREATE USER 'admin'@'localhost' IDENTIFIED BY 'xxxx';
,此时用户的权限是空的,什么库都访问不了
然后赋予admin这个db的权限,grant all privileges on admin.* to 'admin'@'localhost';
然后刷新权限flush privileges;
如果要赋予某个db的select权限,可以使用grant select on root_own.* to 'admin'@'localhost'
,这会同时更新mysql.db库,新增一行;收回权限revoke select on root_own.* from 'admin'@'localhost';
的时候,也会更新mysql.db库,删除一行。
主从复制
带你了解 MySQL Binlog 不为人知的秘密
MySql 主从复制及配置实现
MySQL 主从复制原理不再难
MySQL主从配置详解
MySQL主从复制-从库如何切换主库
Mysql解决主从慢同步问题(上)
Mysql解决主从慢同步问题(下)
好文章!主从复制默认是单线程的,但可以手动开启多线程从库执行binlog,这需要master开启组提交事务。
事务
ACID
事务ACID理解
隔离性是怎么实现的呢?看起来不会出现下面这种情况:事务写到了一半,然后断电了,发现结果是写了一半时候的状态;
如何理解数据库事务中的一致性的概念?
从一个正确的状态到另一个正确的状态,事务提交前后的状态保持一致
MySQL嵌套事务的讨论
mysql不支持嵌套事务,换句话说,同一个连接,最多只能同时拥有一个事务。
隔离级别
数据库事务隔离级别-- 脏读、幻读、不可重复读(清晰解释)
幻读请看后面的博客
MySQL隔离级别
隔离级别之可重复读级别解读
进行实验之前,要先准备两个会话,因为同一个连接不能同时拥有多个事务
- 上述实验里,a3的结果与a1一样,这说明没有发生“不可重复读”,如果此时的隔离级别是“读提交”,那么a3的结果就是事务B提交后的结果;
- a4的结果表示,事务A读取到了事务B对数据的更新结果;你会问这是可重复读啊,前后结果应该相同才对啊。这是因为a4之前,事务A进行了update操作,进行了所谓当前读的操作。类似地,在事务A里也不可执行
insert into users(id,name,age) values(3,'zbz',18);
操作,因为这也会导致幻读。这才是幻读,就是本事务里看到的明明存在,但是就是执行不了。 - a5尝试对事务B删除的数据进行更新,发现此时数据库此数据已经被删除了,所以事务A发现怎么更新第二条数据都无法实现(影响的row的数目为0),这也是一种幻读,读取到了本不存在的数据。a6同理。
- 所以幻读和不可重复读的区别在于:可重复读,那么只要没提交(且不带锁读),那同一个事务里前后读到的结果就是一模一样的(不可重复读就是反过来);但幻读的意思是说,即使你读到了一模一样的数据,这个数据也可能是不存在的,像幻影一样。
- 幻读和不可重复读的共同点:都是快照读和当前读的结果不一样。重复读更多的是数据是否一致,幻读更多的是数据是否存在。
关于幻读,可重复读的真实用例是什么?
read-do的过程中,do的时候失败了;例如读取数据的时候明明没有id为3的数据,但就是插入不进id为3的新数据(即第一个read没读取到本存在的数据);又例如,读取数据的时候明明有id为3的数据,但对其进行更新就是总失败(即第一个read读到了本不存在的数据)。
第一次select是读取,第二次的 insert 其实也属于隐式的读取,只不过是在 mysql 的机制中读取的,插入数据也是要先读取一下有没有主键冲突才能决定是否执行插入。
串行隔离的时候,读取也会加上锁
【mysql】mysql修改事务隔离级别(session和global)
锁
通过各种简单案例,让你彻底搞懂 MySQL 中的锁机制与 MVCC
drop table `users`;
CREATE TABLE `users`
(
id int auto_increment,
c1 int,
c2 int,
c3 int,
PRIMARY KEY (`id`),
unique index idx_t_c1 (`c1`),
index idx_t_c2 (c2)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8;
TRUNCATE users;
INSERT INTO users (C1, C2, C3)
VALUES (1, 1, 1),(2,1,1),(3,2,2),(4,2,2),
(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);;
SELECT *
FROM users;
SELECT @@tx_isolation;
start transaction;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
SELECT * FROM users WHERE c1 = 1 FOR UPDATE;
SELECT * FROM users WHERE c2 = 1 FOR UPDATE;
commit;
行锁都是挂在索引上的,并且行锁的最细粒度就是行,无论这个行锁是通过哪个index加上的,举例来说,对于如下数据,c2只是普通索引,如果事务1执行了SELECT * FROM users WHERE c2 = 2 FOR UPDATE;
,事务2再执行SELECT * FROM users WHERE id = 1 FOR UPDATE;
也会阻塞掉;
id | c2 |
---|---|
1 | 2 |
2 | 2 |
可以看到,锁都是与索引直接挂钩的;加锁也都是基于索引来为某一行加上锁的。
而间隙锁的间隙是如何确定的?答:表中所有数据的间隙都是一个合规的间隙,例如针对c3
列的间隙包含(-无穷,0)/(0,1)/(1,2)/(2,5)/(5,10)等等。
MySQL锁释放时机(事务)
读懂上面的之后,读下面的一组文章更好,因为下面的文章里有一些概念讲的比较简略
MySQL学习笔记2——事务的隔离级别、幻读
幻读的例子我觉得有点= =偏颇,这只是当前读导致的,看个意思就行,不过其他部分写的恩很好,不算是幻读,幻读是没读到存在的或者读到了不存在的数据;
解决幻读这一部分举的这几个例子实际上执行不下去的(因为session B会block住的),只是说假设能执行下去,会有什么问题。这一块实际上是为了讲解,在对d=5
加锁操作时,即使对d=5的全部行加上了锁,也会造成问题的;
所以mysql引入了间隙锁,在这个栗子里,如果事务A使用了select * from users where d=5 for update ;
,生成了7个间隙锁,间隙锁的作用对于间隙(1,5),即无法操作d取值在(1,5)范围内的行,也无法新增d取值在(1,5)的行,也就是说另一个事务的下面的sql都会被block住;
update users set c3=100 where c3=5;
insert into users (C1, C2, C3) values (12,12,12);
所以最终结果就是将整表锁住;所以说基于非索引字段来加锁,这样是非常不好的;
MySQL学习笔记4——锁
案例绝了;描述一下。
问题:这实际上就是幻读造成的数据不一致;
解决1:文中没说,我想的,其实串行或者加for update就好了,事务1里,A关注B的时候select for update一下然后insert,即使此时事务2需要做B关注A并且读取AB关系的时候没读到A关注B,此时事务2如果再做insert操作,会触发死锁,导致事务失败,这也是一个好现象,维护了一致性;至于事务2为什么会死锁呢?因为select for update会加间隙锁,防止insert。
后面就列举了mysql加锁的一些规则,比如是如何根据索引的值来加上next-key锁的,又是怎样退化成行锁或者间隙锁的。这个建议看下面的知乎回答。不过要知道加锁的基本单位是 next-key lock 以及是直接基于索引来加锁,以及基于索引来优化所加的锁。
MySQL 是怎么加行级锁的?为什么一会是 next-key 锁,一会是间隙锁,一会又是记录锁?
为什么有这么复杂的加锁逻辑?首先要明白两点:
- 加锁的目标:在确保数据一致性的前提下,尽可能少锁一些行,同时能够快速确认锁的范围。
- 加锁的方式:所有的锁都是基于index的,并且是基于已存在的数据的,一个不存在的数据无法加上锁。所以才会出现间隙锁这么个东西,比如数据里有id=1和id=5两条数据,当我select where id=4,因为不存在数据,我需要锁住1~5的区间不能插入任何数据。
- 正确理解间隙锁:一个非唯一键的age_idx上的间隙锁(21,22],表现上来看确实是无法插入任何age=(21,22]的数据,但是在实现上,其实是非聚簇索引树的两个叶子节点(存储主键key)中间无法插入任何数据。如下所示,实际上是(age=21,id=5)和[age=22,id=10]之间不允许插入任何叶子节点,那哪些叶子按理会会插入到这里呢?比如(age=21,id=6)。进一步说,对于这个非唯一索引上的key,我是有可能加入(age=22,id=199)这么个值得,因为间隙锁只包到了(age=22,id=10),也因此,我还需要再加上一个(22,39)的间隙锁才能保证所有age=22的数据不会被插入。而唯一性索引就不会有这个问题,因为诶不可能再出现一个age=22的数。
MySQL学习笔记5——事务一致性视图、MVCC
写的可好了,
解释:“ row trx_id的不同情况”里的情况3,数组指的是本事务(记为事务A)启动时,看到的正在运行的事务;
- 如果某个数据的
row trx_id
在数组之中,代表着在事务A启动的时候,更新这个数据的事务也在运行之中,那么这个数据不可见; - 那什么情况会造成某个数据的
row trx_id
不在数组之中呢?很简单,比如mysql库建表以来的第一个事务(假设id为1)过了十年还没提交,但后来的事务都提交了,那么对于事务A来说这个低水位就是1,但trx_id=2的事务已经完成了,如果那么这个row trx_id=2
不在数组里,说明此时这个row trx_id=2
相关的事务一定已经执行完了,该数据可见;
文章里有一句话:更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。意思是每一条update
语句都会分成两步,第一步先去读取要update的行;然后进行update,所以就可以理解究竟是什么造成了幻读:AB两个事务,A先启动B后启动,B对数据进行了删除后提交了,事务A在select的时候,根据undolog回溯数据历史版本到row txid
满足可以被检索到的条件的版本,所以可以读到,但是update的时候需要的是当前读,当前读的话,就tmd读不到;
2025年4月25日:针对这句话有点新想法,我觉得他说的不对,更新数据并不是“先读后写”,而只是因为这个sql刚好是set k=k+1需要将现有的值读出来,而之所以读出来的k是2,这是因为update操作肯定只能在当前数据上操作,也就是说update的操作本身就是基于最新的数据进行操作的。不过这也确实造成了幻读,因为select默认是快照读,而update只能是当前写,所以就会出现,明明A线程select where age=1能看到数据,如果此时有个B线程做了update set age=6 where age=5,A线程update where age=5;就是update不到
——————————————
总结一下:
- mvcc解决了不可重复读的问题,而无法解决幻读问题,因为幻读本身就是实际情况和我所看到的不一样,之所以不一样,恰恰就是因为mvcc导致的多版本问题和当前读写导致的冲突。
- 导致幻读的根本原因是并发,解决并发靠的是锁,mvcc解决不了并发问题。。
全面深入理解MySQL自增锁
深入剖析 MySQL 自增锁
为什么statement的binlog主从同步不能开启交叉模式,不是因为从库是并发执行主库的statement的,而是因为主库可以并发执行一些sql,因为自增锁本身就只是自旋锁,如果AB两个线程并发执行insert,不一定哪个线程的sql会获得锁,而从库在执行statement的时候不可能知道到底是A先执行还是B先执行,,可能固定写好的就是先A后B,那么就导致主键不一致了。
设计
树结构
干货:在关系型数据库中优雅地存储树形结构
怎样在 MySQL 表中存储树形结构数据? - 知乎
在MySQL中存储树状结构
优化
【好文】MySQL索引原理及慢查询优化
第一条sql 原本做的事情是将BC表join起来,然后与A进行查询;问题就在BC表
join在一起非常大,优化方案就是ABjoin,然后ACjoin,结果去重就好了
MySQL explain 应用详解(吐血整理🤩)
长达 1.7 万字的 explain 关键字指南!
分页
深入浅出 MySQL 优先队列(你一定会踩到的order by limit 问题)
MySQL深度分页的问题及优化方案:千万级数据量如何快速分页
面试官:Mysql千万级大表如何进行深度分页优化
首先要搞明白深度分页为什么慢:在没有触发覆盖索引的时候,limit M N
实际上是取M+N条,然后丢掉前M条;另一方面如果是按照主键查询深度分页并不有问题,就怕不是主键,即使是二级索引,取出M+N条还要回表查询M+N次,要了亲命了。但如果触发了覆盖索引,深度分页按理来说就不是问题了。没测试,只是觉得应该不是问题了。。。
那么解决方案就是通过sql不让mysql这样执行。
下面这个sql中,因为create_time
上有索引,在B+tree的叶子节点有下一个节点的指针,所以只要遍历到第100000个的位置即可,并且create_time触发了覆盖索引,可以直接获取第100万条数据的create_time,然后再在create_time
索引上进行查询,取出100个叶子节点然后回表查询性能压力就小了。但这个sql是有点小问题的,分页可能会出错,因为如果create_time有大量重复值,那么取到的可能并不是第100万~100万零100的数。
SELECT * FROM t_order
WHERE create_time <= (
SELECT create_time FROM t_order ORDER BY create_time desc LIMIT 1000000,1
)
ORDER BY create_time desc LIMIT 100;
解决方案就是不要用create_time,直接用id作为子查询的结果,id不会重复,并且id直接存储在非聚簇索引中,查询也很快。
SELECT torder.* FROM t_order torder
INNER JOIN (
SELECT id FROM t_order ORDER BY create_time DESC LIMIT 1000000,100
) tmp ON torder.id = tmp.id
ORDER BY
create_time DESC
日志
慢查日志
binlog-undolog-redolog
mysql 基于binlog恢复
数据库故障恢复机制的前世今生
Durability of Updates:已经Commit的事务的修改,故障恢复后仍然存在;——事务持久化
Failure Atomic:失败事务的所有修改都不可见。——失败回滚
已经提交的事务,提交后即使data只写入了系统写入的cache中没有刷盘,但只要能重启起来,数据都会是已提交状态;事务因任何原因失败(例如服务器挂掉)数据都需要能够恢复到提交事务之前。
Shadow Paging:一个文件由多个Page组成,每个文件有一个PageTable记录了这个文件所在的page,文件系统存储了一个directory,存储了所有文件的pageTable的地址。ShadowPage相当于一个当前数据的热备份,热备份成功后才算commit。如果故障了,就用热备份来恢复。
Undo-Only Logging:必须先记录undo-log且落盘->修改data且落盘->undo-log记录commit标记且落盘。因为undolog只记录了历史数据,如果第二步第三步反过来,如果unod-logcommit成功后服务挂掉,恢复之后提交成功的日志对应的数据仍然是旧的。同时,改data也一定要落盘,不能仅仅记录在缓存里,因为undo-log只记录了历史数据没有记录最新数据,否则如果不实时落盘,在commit之后data实际落盘之前服务挂掉,那么已提交的事务也仍然会丢失,这里也是一个大缺点,data落盘要随机写入。
Redo-Only Logging:必须先记录redo-log且落盘->redo-log记录commit标记且落盘->修改data。redo-log只记录了新数据,如果第二步第三步反过来,如果修改data之后数据redo-logcommit之前挂了,此时没法恢复到原来的数据了。最后修改data无所谓是否落盘,因为最后一步的时候事务已经提交了,旧数据没用了,新数据记录在redo-log里了。
Redo-Undo Logging:两个日志全都记录,但无论怎样,log都得落盘
ARIES算法保障了数据库的故障恢复,核心就是同时使用redo log和undo log,康神,Redo-Undo Logging的落盘顺序为redo+undo log 落盘 + commit标记落盘。 data刷盘随意是吧?允许在commit标记前是吧?如果data page刷早了还没commit,同时crash了通过undo 进行修正对吧?对
庖丁解InnoDB之REDO LOG
庖丁解InnoDB之Undo LOG
关于Innodb undo log的刷新时机?
Undo + Redo事务的简化过程
假设有A、B两个数据,值分别为1,2,开始一个事务,事务的操作内容为:把1修改为3,2修改为4,那么实际的记录如下(简化):
A.事务开始.
B.记录A=1到undo log.
C.修改A=3.
D.记录A=3到redo log.
E.记录B=2到undo log.
F.修改B=4.
G.记录B=4到redo log.
H.将redo log写入磁盘。
I.事务提交
修改前写入undo的原因是直接修改了数据,在上述书上提供的方案里(page方式),page的刷出是不做控制的,也就意味着修改数据的时候page虽然fix在buffer里面,但是有可能在redo log落盘之前被刷出到磁盘,所以如果undo log没有先落盘会导致无法回滚。顺序是先写undo log,修改数据,再写redo log。回放的时候先回放redo log,再把没提交的事务undo掉。Innodb这种事实上是undo 通过undo段生成undo page,undo page跟数据一起被当做修改写redo log, redo log通过多个mtr落盘。重启时先回放redo,从redo构造undo page再undo掉回滚的事务。mvcc可以通过undo page实现。
作者:陈晓辉
链接:https://www.zhihu.com/question/267595935/answer/328041131
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
MySQL中的Undo Log严格的讲不是Log,而是数据,因此他的管理和落盘都跟数据是一样的:
- Undo的磁盘结构并不是顺序的,而是像数据一样按Page管理Undo写入时,也像数据一样产生对应的Redo Log
- Undo的Page也像数据一样缓存在Buffer Pool中,跟数据Page一起做LRU换入换出,以及刷脏。
- Undo Page的刷脏也像数据一样要等到对应的Redo Log 落盘之后
上面几篇文章都相当硬核,查到这几篇文章都只是源于一个疑惑:redo log和bin log都有buffer,还为此新增了两阶段提交,那undo log呢?
要结合mvcc理解一下,mvcc中,数据库需要记住某个数据的修改之前是什么样的,假如说一条数据目前在buffer pool里,我们可以通过undo log,可以反向执行一步、两步,从而知道他被第一次修改之前的结果、被第二次修改之前的结果。所以逻辑上讲,这个undo log起到的作用本身就是“数据”的作用,并且这个undo log是和这条数据强绑定的。如果一个数据需要从buffer pool里刷出去,那么这条数据对应的undo log也需要被刷回到硬盘上。
Innodb这种事实上是undo 通过undo段生成undo page,undo page跟数据一起被当做修改写redo log, redo log通过多个mtr落盘。重启时先回放redo,从redo构造undo page再undo掉回滚的事务。
因为undo log本身相当于记录了数据的历史版本,所以相当于“写入历史版本数据”这么个操作,而在innodb中,这么个操作被看做update、insert操作一样,需要写入redo log,相当于redo log里不仅记录了真实的数据库操作,还记录了生成undo log的操作。
另外,因为未完成的事务对应的数据可能并不在buffer pool,因此,如果这类事务执行一半挂掉了,可以根据redolog中的undo log进行数据恢复。
MySQL · 最佳实践 · 在线收缩UNDO Tablespace
其他
MySQL执行外部sql脚本文件的命令
MYSQL优化 Analyze Table
Mysql面试
开发如下脚本,然后执行sh dump_db.sh 'select * from tb' > result.txt
#/bin/bash
if [ $# -ne 1 ];then
echo "Usage: bash run_sql_dump.sh sql [ > result.txt]"
exit
fi
sql=$1
mysql -h127.0.0.1 -P3306 -udev -proot my_db --default-character-set=utf8 -e "$sql"
故障修复
MySQL根据离线binlog快速“闪回”
就是新建了一堆库,然后用binlog重跑了一次;