文章目录
- 隔离性的进一步理解
- MYSQL的日志
- 隔离性具体是如何实现的呢?
-
- 回滚操作的具体过程是如何实现的?(MVCC的介绍,什么是MVCC)
-
- 多次UPDATE操作形成版本链
- 什么是MVCC?
- 听你刚刚的介绍,我每做一次修改(update操作),这次修改前的历史版本就要被拷贝一份放到undo.log中,那我要是修改次数很多,记录的历史版本太多,把undo.log放满了怎么办?
- 一个事务中涉及到了对stu表中张三这条记录的修改操作,请问在这个事务提交之后,undo.log中还保存有张三这条记录的历史版本吗?
- 上面介绍了多次update操作会形成版本链,需要对多版本进行管理和维护,请问delete操作呢?删除一条记录会不会为这条记录形成一个新版本,插入到版本链中?insert操作呢?select操作呢?
- 你隔离性不是也分好几个隔离等级吗?MVCC在哪里体现出了对隔离等级的划分呢?
- 当前读与快照读
- read view介绍
-
- 什么是read view?
- 如何理解事务ID?事务ID是如何产生的?事务ID的大小有什么含义?
- 事务的readview结构体是在什么时候创建形成的?是事务开始的时候吗?
- readview源码
- 有了readview之后,所有的事务就可以根据readview分成三类,请问是哪三类?
- 当前事务想对一条记录进行查找,我怎么知道到底应不应该让这个事务看到这条记录的最新版本呢?
- 不同隔离级别下ReadView的生成规则
- mysql中如何判断某个事务 ID(id)对应的修改是否对当前事务视图可见?
- 如何理解“某个事务中首次出现快照读的位置,决定该事务后续快照读结果的能力 ”这句话?
- RR和RC在MVCC机制层面的本质区别是什么?
- 并发读写的隔离性是如何实现的呢?
- 推荐阅读
隔离性的进一步理解
数据库并发的场景有哪些?这些并发场景下都存在哪些安全问题?
-
读-读并发
不存在任何问题,也不需要并发控制 -
读-写并发
有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读写
(这个场景其实最难的,也是我们重点谈论的,因为这里面存在 很多可以优化的场景) -
写-写并发
有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
这个场景其实没啥可说的,因为肯定要加锁,所以优化空间不大
为什么事务要有ID?ID有什么作用
ID具有唯一性,用于区分不同的事务
ID的大小用来表明事务的先后顺序
即Mysql按照事务开始的先后顺序分配ID,事务越早开始,ID越小
mysql的表格中有3个记录隐藏列字段 ,分别叫做DB_TRX_ID ,DB_ROLL_PTR ,DB_ROW_ID ,这三个字段表示的含义分别是什么?
DB_TRX_ID :字段长度 6 byte 表示最后一次修改表中这条记录信息的事务ID,如果创建之后没有人改过,那就记录创建这条记录的事务ID
DB_ROLL_PTR :字段长度 7 byte 回滚指针,指向这条记录之前的历史版本
DB_ROW_ID:字段长度 6 byte隐含的自增ID(隐藏主键),如果数据表没有主键,那么DB_ROW_ID就是这张表的主键, InnoDB 会自动以 DB_ROW_ID 产生一个聚簇索引 (相当于主键索引)
其实除了上面那三个隐藏列字段之外,还有一个删除flag字段,这个字段的含义是什么?
就是如果你想删除表中的某一行数据,向mysql输入delete操作之后,实际上mysql后台并不会立即把这一行的数据全删了,而是会先将该行的flag字段从1改成0,此后mysql就可以对这块空间进行覆盖写入了
MYSQL的日志
mysql中有三种日志信息:undo log、redo log、bin log,请问这三种日志信息的功能分别是什么?有什么区别?
我们下面就从MYSQL日志系统解决的核心问题入手,来说明三种日志的功能
undo log:解决 “事务原子性” 和 “并发读冲突” 问题
事务原子性问题:如何保证事务执行中途失败(如崩溃、主动回滚)时,能撤销所有已执行的修改,回到事务开始前的状态?
undo log记录 “反向操作” 实现回滚:
事务执行时,每做一次修改(如 INSERT/UPDATE/DELETE),undo log 就记录对应的 “撤销指令”:
- 若执行 INSERT,则记录 DELETE(回滚时删除新增的行);
- 若执行 UPDATE,则记录 “旧值”(回滚时将字段恢复为修改前的值);
- 若执行 DELETE,则记录 INSERT(回滚时重新插入被删除的行)。
当事务需要回滚时,InnoDB 会反向执行 undo log 中的指令,逐步撤销所有修改。
并发读冲突问题:如何让多个事务同时读写数据时,读操作不被写操作阻塞(即 “读不加锁”)?
提供 “历史版本” 支持 MVCC:
- undo log 会保留数据的多个历史版本(按事务顺序生成),每个版本关联对应的事务 ID。
- 当其他事务执行 “快照读”(如普通 SELECT)时,InnoDB 会根据隔离级别,从 undo log 中读取符合条件的历史版本,避免直接读取当前正在修改的数据,实现 “读不加锁” 的并发控制。
在数据库系统中,undo log、redo log、bin log 分别针对三个核心问题设计,各自通过独特的机制解决特定场景的挑战:
redo log:解决“事务持久性”和“写入性能”问题
事务持久性问题
事务持久性问题:如何保证事务提交后,即使数据库崩溃(如断电),修改也不会丢失?
数据库不是存在服务器的磁盘中吗?磁盘是非易失性存储器,不是说非易失性存储器中的数据,即使断电也不会丢吗?你现在咋又说会丢了呢?
这个理解有个关键误区:磁盘的“非易失性”仅保证“已完整写入磁盘的数据不丢失”,但无法解决“数据写入过程中掉电导致的不完整/损坏”,更无法解决“高并发下直接刷数据页的性能灾难”。这正是需要redo log的核心原因——它要解决的不是“已存磁盘数据丢不丢”,而是“如何安全、高效地把内存中的修改落地到磁盘”。
引入redo log之前事务修改数据的完整流程
当mysql服务器收到了客户端的指令,执行update语句修改数据时,数据库的操作不是“直接改磁盘文件”,而是遵循以下步骤(以InnoDB为例):
- 读数据到内存:先从磁盘把目标数据页(默认16KB)加载到内存中的“缓冲池(Buffer Pool)”;
- 内存中修改:在缓冲池里修改数据页的内容(此时内存数据和磁盘数据不一致,称为“脏页”);
- 刷脏页到磁盘:最终需要把缓冲池中的“脏页”写回磁盘,才能让修改永久生效。
为什么“磁盘非易失性”解决不了问题?核心在“写入过程”和“性能”
即使磁盘不会丢已存数据,没有redo log的话,会面临两个致命问题:
问题1:数据页“部分写入”导致损坏,磁盘非易失性救不了
假设一个16KB的数据页正在从内存刷到磁盘:
- 磁盘写入是“按块/按扇区”进行的(比如每个扇区512字节),需要多次IO才能写完16KB的完整数据页;
- 如果刷到第8KB时突然断电,此时磁盘上这个数据页会处于“一半旧数据、一半新数据”的中间状态(即“部分写入”);
- 磁盘的非易失性只能保证“这8KB新数据不会丢”,但无法判断“剩下的8KB为什么没写”——重启后数据库看到这个损坏的数据页,既不能确定它是旧状态还是新状态,也无法修复,最终导致数据损坏。
问题2:直接刷脏页是“随机IO”,高并发下性能崩溃
磁盘的性能瓶颈在于“随机IO”(磁头需要频繁移动定位数据页位置)和“顺序IO”(磁头只需要往后追加写入)的巨大差距:
- 数据页在磁盘上是离散存储的(比如修改用户A的数据在磁盘地址100,修改用户B的数据在地址10000),直接刷脏页就是“随机IO”,每秒只能完成几百次;
- 如果没有redo log,每次事务提交都必须把修改过的脏页“同步刷到磁盘”(否则断电会丢数据)——高并发场景下(比如每秒几千次写入),磁盘IO会瞬间打满,数据库直接卡死。
redo log如何补上这两个漏洞?
redo log日志就是为了解决上述两个问题设计的,本质是用“顺序IO的日志”替代“随机IO的数据页”作为提交的“持久化锚点”:
- 解决“部分写入”问题:记录“修改动作”而非“完整数据”
redo log不记录完整数据页,只记录“某数据页的某位置,从值X改成了值Y”这种“最小修改动作”。
即使数据页刷盘时断电,重启后数据库会:- 先检查磁盘上的数据页状态(无论是否完整);
- 再通过redo log重新执行所有“已提交事务的修改动作”
采用上面的策略,不管数据页之前是旧是新、是否完整,重新应用后都会恢复到正确的最终状态,彻底避免数据损坏。
假如说一个事务要对某16KB大小的数据库进行修改,其中有8处需要修改的数据,4处在前8KB,4处在后8KB。事务提交到服务器之后,服务器开始修改,修改完前面4处,还没修后面4处时,服务器就断电了 ,下次服务器恢复时,他应该怎么办?
假设这16KB数据页的标识为 space_id=1(表空间ID)、page_no=100(页号),8处修改的具体信息如下:
| 序号 | 事务ID | 数据页定位 | 修改位置(偏移量) | 修改内容(旧值→新值) | redo log 记录格式(简化) |
|---|---|---|---|---|---|
| 1 | T100 | space=1, page=100 | 0x0020(32字节处) | 0x000A(10)→ 0x0014(20) | [T100, space=1, page=100, offset=0x0020, len=4, old=0x000A, new=0x0014] |
| 2 | T100 | space=1, page=100 | 0x0100(256字节处) | 0x000C(12)→ 0x001E(30) | [T100, space=1, page=100, offset=0x0100, len=4, old=0x000C, new=0x001E] |
| 3 | T100 | space=1, page=100 | 0x0200(512字节处) | 0x0005(5)→ 0x000F(15) | [T100, space=1, page=100, offset=0x0200, len=4, old=0x0005, new=0x000F] |
| 4 | T100 | space=1, page=100 | 0x1F00(7936字节处,前8KB内) | 0x0030(48)→ 0x0064(100) | [T100, space=1, page=100, offset=0x1F00, len=4, old=0x0030, new=0x0064] |
| 5 | T100 | space=1, page=100 | 0x2000(8192字节处,后8KB起始) | 0x0008(8)→ 0x0010(16) | [T100, space=1, page=100, offset=0x2000, len=4, old=0x0008, new=0x0010] |
| 6 | T100 | space=1, page=100 | 0x3000(12288字节处) | 0x0020(32)→ 0x0040(64) | [T100, space=1, page=100, offset=0x3000, len=4, old=0x0020, new=0x0040] |
| 7 | T100 | space=1, page=100 | 0x3F00(16128字节处) | 0x0050(80)→ 0x00A0(160) | [T100, space=1, page=100, offset=0x3F00, len=4, old=0x0050, new=0x00A0] |
| 8 | T100 | space=1, page=100 | 0x3FF0(16368字节处) | 0x0001(1)→ 0x0002(2) | [T100, space=1, page=100, offset=0x3FF0, len=4, old=0x0001, new=0x0002] |
| 9 | T100 | —— | —— | 事务提交标记 | [T100, COMMIT, xid=12345](事务提交时追加的标记,用于恢复时识别事务已完成) |
当数据库崩溃后重启,会通过这些记录:
- 识别
T100是已提交事务(通过最后一条COMMIT标记); - 按顺序重新应用这8条修改(无论数据页之前是否被部分写入);
- 最终让16KB数据页的8处修改全部生效,与事务提交时的预期完全一致。
我服务器不知道上次断电时这个事务执行到哪了,我就从这个事务的第一条记录开始查,第一条记录要修改的是0x0020这个位置的数据,那我就去数据库中看看这个位置的数据的修改时间是啥时候,如果断电之前修改过,那我就不改了,如果断电之前没改过,我就按照redo log中的修改方式进行修改。后面的记录也是一样,挨个检查是否修改过,断电前没改我就现在改
- 解决“性能崩溃”问题:用“顺序IO日志”替代“随机IO数据页”做提交
redo log文件是固定大小的循环文件,写入时只需从当前位置“追加写入”(顺序IO),性能接近内存(每秒可达百MB级别)。
事务提交时,只需把“修改动作”写入redo log(顺序IO,快),就可以认为事务“已持久化”;而数据页的“脏页刷盘”则交给后台线程异步、批量处理(随机IO,但不阻塞事务提交)。
这样既保证了“事务提交后数据不丢”,又避开了随机IO的性能瓶颈,高并发下也能稳定运行。
总结:磁盘非易失性是“结果保障”,redo log是“过程保障”
- 磁盘的非易失性:负责“一旦数据完整写入磁盘,就永远不丢”——这是“结果层面”的保障;
- redo log:负责“让数据能安全、高效地从内存写到磁盘”——这是“过程层面”的保障(解决写入中掉电损坏、写入性能差的问题)。
没有redo log,磁盘的非易失性就像“有坚固的仓库,却没有安全高效的运输队”——要么运输时货物损坏(部分写入),要么运输太慢导致仓库堵死(性能崩溃)。
redo log 记录“物理修改”实现崩溃恢复:
- 事务执行时,所有对数据页的修改(如某行某字段的值变化)会先记录到
redo log中,内容是“物理地址+修改内容”(例如“在表 t1 的数据页 0x123 中,偏移量 0x45 处的值从 10 改为 20”)。 - 事务提交时,
redo log会被强制刷到mysql服务器磁盘(通过innodb_flush_log_at_trx_commit=1保证)。 - 若数据库崩溃,重启后 InnoDB 会扫描
redo log,重新执行所有“已提交事务”的修改(无论数据页是否刷盘),确保提交的数据不丢失。
写入性能:如何避免每次事务提交都将完整数据页刷到磁盘(成本过高),同时保证数据安全?
这个问题其实上一个问题我们已经讲过了,这里就再提一遍
redo log采用先写日志优化写入性能:
- 磁盘的“顺序写”(如
redo log追加写入)速度远快于“随机写”(如修改数据页时的离散写入)。 redo log采用“循环文件组”设计(固定大小),事务提交时只需顺序写入日志,无需立即刷数据页到磁盘(数据页由后台线程异步刷盘),大幅提升写入性能。
bin log:解决“跨实例数据同步”和“时间点恢复”问题
主从复制问题
如何让从节点数据库同步主节点数据库的所有数据修改,保持主从数据一致?
MySQL 主从复制(Master-Slave Replication)通过 “主库记录变更日志,从库读取并重放日志” 的方式实现数据同步,核心依赖 bin log(二进制日志)和一套协作机制,确保从节点与主节点数据一致。
主从复制分为 3 个关键步骤
1. 主库记录数据变更到 bin log
- 主库开启
bin log功能后,所有对数据的修改操作(INSERT/UPDATE/DELETE等)都会被按顺序记录到bin log中(记录的是逻辑变更,如 SQL 语句或行级修改)。 bin log会为每个事件分配一个 位置编号(Position) 和 日志文件名,用于标记变更的顺序和位置(类似“章节页码”)。
2. 从库获取主库的 bin log
- 从库启动一个 IO 线程,连接主库的专用复制端口(默认 3306),并发送“需要同步的

最低0.47元/天 解锁文章
&spm=1001.2101.3001.5002&articleId=151125014&d=1&t=3&u=970bc7f4a23041368b36217705513fda)
1235

被折叠的 条评论
为什么被折叠?



