MySQL是如何实现事务的?
MySQL有四大特性分别是
原子性、一致性、持久性、隔离性。MySQL实现事务靠
Undo log、Redo log、锁、MVCC这四个核心组件来实现的
- 通过
Undo log来实现原子性,在进行写操作之前,会把原来的数据存入Undo log中,当事务回滚时,通过Undo log进行反向操作,把数据恢复回去。 - 通过
Redo log来实现持久性,在进行写操作时,不会直接把数据写入磁盘数据页,而是先写入Redo log,就算写入磁盘数据页宕机了,也能通过重做Redo log来恢复数据。 - 通过
锁机制来实现写的隔离性,当两个事务同时改一个数据时,必须一个等另一个释放锁,InnoDB支持行锁,间隙锁防止幻读。 - 通过
MVCC来实现读写的隔离性,读的时候不加锁,而是通过Undo log版本链来找到自己读的数据版本,读的时候能写,写的时候也能读。
Redo log的工作原理
- InnoDB在写入数据时,不会直接把数据写入磁盘的数据页,因为这样做随机磁盘IO太多,性能不好,而是先把数据顺序写入Redo log中,再找时间把数据页刷到磁盘上,应为顺序写比随机写快。
- redo log是通过循环写来实现的,redo log中有两个指针,一个记录写到哪里了,一个记录刷盘刷到哪里了,两指针之间就是待刷盘的脏数据。
- 事务提交时,redo log必须落盘,这个行为由 innodb_flush_log_at_trx_commit 控制:
- 设置成1,每次提交都要刷盘
- 设置成0,每一秒刷一次盘,宕机可能造成一秒的数据丢失
- 设置成2,把数据写到系统缓存中,MySQL宕机不丢失,服务器挂了才丢。
Undo log与版本链
- 数据库每条记录都有两个隐藏字段:
- trx_id:记录最后修改这个数据的事务ID
- roll_pointer:指向undo log里的上一个版本
- 多次修改就会形成一个版本链
- MVCC在读数据时,会根据事务的ReadView去沿着版本链去往回找。
- 在读未提交的事务隔离级别下,每次访问都会生成新的ReadView;在读已提交下,事务开始时会生成ReadView,以后每次访问都用这个。
事务提交的两个阶段
InnoDB和Server层各有自己的日志,redo log 和bin log,为了保证两个日志的一致性,所以用到了两个阶段。
- 准备阶段:redo log写盘,然后把状态改为prepare
- 提交阶段:bin log写盘,然后redo log状态改为commit
如果是bin log写入前宕机,redo log的状态为prepare,但是没有对应的bin log,则触发回滚
如果是bin log写入后宕机,redo log的状态为prepare,有对对应的bin log,则会触发事务提交
这一套机制保护了主从复制的数据一致性,主表靠redo log数据恢复,从表靠bin log数据同步。
锁的实现细节
InnoDB的行锁是加在索引上的,如果修改数据时没有走索引,就会锁住整张表
InnoDB有三种行锁:
- 普通的行锁,锁整行数据
- 间隙锁:锁一个区间,不包含数据本身
- 临键锁(行锁+间隙锁):锁整行数据+数据前面的区间
MVCC是什么
MVCC是多版本并发控制,核心思想是让读写互不阻塞:写操作会产生新的版本,读操作会根据事务的ReadView在Undo log的版本链上找到自己版本的数据。
- 表中的每条数据都有两个隐藏字段,一个记录当前事务ID,一个记录undo log
- 每次写操作,都会先把旧的值写入undo log,再把新的值写入磁盘数据页,回滚指针指向旧版本,这样就形成了一个数据链。
- 普通的读都是快照读,不加锁,直接通过版本链找到自己对应的数据直接返回。
MySQL 中的日志类型有哪些?bin log、redo log 和 undo log 的作用和区别是什么?
MySQL 有三种核心日志。bin log 负责主从复制和数据恢复,redo log 保证崩溃后数据不丢,undo log 支撑事务回滚和 MVCC。
-
bin log 是 Server 层的日志,记录的是逻辑操作,也就是原始 SQL 或者行变更前后的值。它的核心场景是主从同步,从库拉取主库的 bin log 重放一遍就能保持数据一致。另外做数据恢复的时候,也是靠 bin log 配合全量备份回放到指定时间点。
-
redo log 是 InnoDB 引擎独有的,记录的是物理变更,具体就是"某个数据页的某个偏移量改成了什么值"。它的作用是 MySQL 挂了重启后,InnoDB 会用 redo log 把没来得及刷盘的脏页恢复出来。redo log 是循环写的,空间固定,写满了就得等 checkpoint 推进才能继续。
-
undo log 也是 InnoDB 的,记录的是数据修改前的旧值。事务回滚的时候,就靠 undo log 把数据改回去。另外 MVCC 的快照读也依赖它,别的事务要读历史版本,顺着 undo log 链往前找就行。