事务是一组操作的集合,他是一个不可分割的工作单位,事务会把所有操作作为一个整体一起向系统提交或者撤销请求操作,即这些操作要么同时成功,要么同时失败。
在MySQL中,Redo Log和Undo Log是用来支持事务和保证数据一致性的关键日志机制。
Redo Log(重做日志): 作用是记录了所有对数据库的修改操作,包括插入、更新和删除等操作。记录了事务提交时数据页的物理修改,是用来实现事务的持久性
该日志文件有两部分组成:重做日志缓冲(Redo Log Buffer)以及重做日志文件(Redo Log File),前者是在内存中,后者在磁盘中,当事务提交之后会把所有信息都存到该日志文件中,用户刷新脏页到磁盘,发生错误时,进行数据恢复使用。
工作原理:当事务进行数据修改时,MySQL将修改的操作记录到 Redo Log中,而不直接写入磁盘的数据文件中。这样可以减少磁盘IO的操作,提高性能。在事务提交时,Redo Log的内容会被异步刷新到磁盘上的数据文件中,确保数据的持久性。如果系统崩溃,MySQL可以通过Redo Log中的信息重做之前未写入磁盘的修改操作,恢复到事务提交的状态。
Undo Log(回滚日志):用来支持事务的回滚操作,即将事务的修改操作撤销,恢复到之前的状态。在insert、update、delete的时候产生的便于数据回滚的日志。
当insert的时候,产生的 Undo Log日志只在回滚时需要,在事务提交后,可被立即删除。
而update、delete的时候,产生的Undo Log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。
工作原理:当事务进行数据修改时,MySQL将修改的操作记录到Undo Log中,并在事务提交之前保留这些修改的记录。如果事务发生回滚操作,MySQL会根据Undo Log中的信息,将事务的修改操作撤销,将数据还原到事务开始的状态。因此,Undo Log为事务提供了撤销操作的能力,确保数据库的一致性。
不同事务或相同事务对同一条记录进行修改,会导致该记录的Undo Log生成一条记录版本链,链表的头部是最新的旧记录,链表尾部是最早的旧记录
说明1:第一次insert的数据,此时DB_TRX_ID 为1, 因为还没有修改过,所以DB_ROLL_PTR为null
说明2:此时我们模拟几个并发同时开始的事务
说明3:此时我们在事务2中修改id为30的数据
说明4:那么在执行修改操作之前,会先把原始数据写入Undo Log日志中一份,方便数据回滚
说明5:在此时原始记录中age改为3,并且隐藏字段DB_TRX_ID变为2,DB_ROLL_PTR在为Undo Log 日志中的第一条数据的地址
说明6:此时事务2提交事务之后,事务3又修改id为30的数据,name改为A3
说明7:此时在更改原始数据之前,需要把当前的数据在写入到Undo Log日志中一份,继续方便做数据回滚,而这时Undo Log中就有了两条数据
说明8:然后就可以修改原始数据 name 改为 A3,并且DB_TRX_ID也改为3,DB_ROLL_PTR指向Undo Log第二条日志的地址。
说明9:这样就形成了一条完整的Undo Log版本链
读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行枷锁。
如:select...lock in share mode(共享锁)、select...for update、update、insert、delete(排他锁)都是一种当前读。
简单的select(不加锁)就是快照读,快照读读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
全称 Multi-Version Concurrency Control 多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC具体实现,还需要依赖于数据库记录中的三个隐式字段、Undo Log日志、readView。
示例如下:
DB_TRX_ID:最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID
DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本,用于配合Undo Log,指向上一个版本
DB_ROW_ID:隐式主键,如果表结构没有指定主键,将会生成该隐藏字段
使用指令重新创建一个表mvcc_test,这个时候不给该表设置主键
然后在数据库路径下的data文件夹下就可以找到我们刚创建的独立表结构空间mvcc_test.ibd
MySQL提供了一个ibd2sdi 指令可以查看表结构空间
root@ubuntu:/usr/local/mysql/data/mysql_test# ibd2sdi mvcc_test.ibd ["ibd2sdi", { ... # 这里的 ... 是我过滤掉的无关数据 "dd_object_type": "Table", # 这里说明这是一个表结构 "dd_object": { "name": "mvcc_test", # 名称 mvcc_test ... "columns": [ # 所有的列 { "name": "name", # 自己定义的name列 ... }, { "name": "DB_ROW_ID", # 隐式创建的列 ... }, { "name": "DB_TRX_ID", # 隐式创建的列,如果在创建表的时候指定了主键,则该隐藏字段就不会被创建 ... }, { "name": "DB_ROLL_PTR", # 隐式创建的列 ... } ], "schema_ref": "mysql_test", # 该表所属的数据库 ... }
} ] root@ubuntu:/usr/local/mysql/data/mysql_test#
是快照读SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务id,即未提交的事务id
因为read committed 模式下, 在事务中每一次执行快照读时都会生成readView.所以在同一个事务下,读取两次数据,就会产生两个ReadVIew
说明1:以事务5的两次查询为例
说明2:ReadView1中的m_ids:[3,4,5],因为事务2在开启该次查询事务的事就已经提交了,所以m_ids中不包括2,同样的在ReadView2创建的时候,事务3也已经提交,所以ReadView2中的m_ids只有[4,5]
说明3:套用版本链访问数据的规则,ReadView1和ReadView2两次查询数据演示
说明4:ReadView1数据查询演示
说明5:ReadView2数据查询演示
repeatable read:仅在事务中第一次执行快照读时生成readView,后续复用该readView,所以这里只会产生一个ReadVIew
说明6:因为这里的数据查询演示过程个上面的一样,只不过这里只有一个ReadVIew,过程更简单。
三个隐藏字段+Undo Log版本链+ReadView是MVCC的实现原理
MVCC+锁就是事务隔离性的实现原理
Undo Log + Read Log
Read Log