事务
2019-08-18 15:34:49 41 举报
AI智能生成
mysql_事务&Redo日志&Undo日志&MVCC
作者其他创作
大纲/内容
事务简介
事务的ACID
原子性(atomic)
要么全做,要么全不做
隔离性(Isolation)
两次状态转换应该是互不影响的;
要保证其他的状态转换不会影响本次状态转换。
要保证其他的状态转换不会影响本次状态转换。
一致性(Consistency)
如果数据库中的数据全部符合现实世界的约束,则称符合一致性
例如转账案例中,不仅要保证转账的账户总的余额不变。 要还保证转账后余额不能出现负数
持久性(Durability)
当现实世界的一个状态转换后,这个转换的结果将永久保留
事务的状态机
活动的(active)
事务对应的数据库操作正在执行过程中时,我们就说该事务处在活动的状态。
部分提交的(partially committed)
当事务中的最后一个操作执行完成,但由于操作都在内存中执行(保存在Buffer Pool的free链表中),
所造成的影响并没有刷新到磁盘时,我们就说该事务处在部分提交的状态。
所造成的影响并没有刷新到磁盘时,我们就说该事务处在部分提交的状态。
失败的(failed)
当事务处在活动的或者部分提交的状态时,可能遇到了某些错误(数据库自身的错误、
操作系统错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的状态。
操作系统错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的状态。
中止的(aborted)
如果事务执行了半截而变为失败的状态,比如我们前边唠叨的狗哥向猫爷转账的事务,当狗哥账户的钱被扣除,但是猫爷账户的钱没有增加时遇到了错误,从而当前事务处在了失败的状态,那么就需要把已经修改的狗哥账户余额调整为未转账之前的金额,换句话说,就是要撤销失败事务对当前数据库造成的影响。书面一点的话,我们把这个撤销的过程称之为回滚。当回滚操作执行完毕时,也就是数据库恢复到了执行事务之前的状态,我们就说该事务处在了中止的状态。
提交的(committed)
当一个处在部分提交的状态的事务将修改过的数据都同步到磁盘上之后,我们就可以说该事务处在了提交的状态。
事务语法
开启事务
提交事务
手动回滚事务
关闭自动提交
隐式提交
1. 定义或修改数据库对象的数据定义语言 (DDL)
2. 隐式使用或修改mysql数据库中的表
如ALTER USER、CREATE USER、DROP USER、GRANT、RENAME USER、REVOKE、SET PASSWORD等语句
3. 事务控制或关于锁定的语句
4. 加载数据的语句
LOAD DATA
5. 关于MySQL复制的一些语句
START SLAVE、STOP SLAVE、RESET SLAVE、CHANGE MASTER TO等语句
设置回滚点
删除回滚点
存储引擎对事务的支持
支持
InnoDB
不支持
MyISAM
redo日志(上)
解决的问题
如何在服务器出现故障时保证数据的持久性
(比如很多修改的数据可能还保留在Buffer Pool的脏页面中)
(比如很多修改的数据可能还保留在Buffer Pool的脏页面中)
粗暴的办法
在事务提交完成之前把该事务所修改的所有页面都刷新到磁盘
问题
刷新一个完整的数据页太浪费了
随机IO刷起来比较慢
redo日志基本概念
没有必要在事务提交时将涉及的脏页全部刷新到磁盘。只需要把修改了哪些东西记录一下
(比如在系统表空间的第100号页面的偏移量处的值更新为ABC)然后在事务提交的时候,
把redo日志刷新到磁盘即可。然后在系统崩溃重启时按照上面所记录的步骤重新更新数据页
(比如在系统表空间的第100号页面的偏移量处的值更新为ABC)然后在事务提交的时候,
把redo日志刷新到磁盘即可。然后在系统崩溃重启时按照上面所记录的步骤重新更新数据页
特点
redo日志占用的空间非常小
redo日志是顺序写入磁盘的
redo日志格式
redo日志通用结构
简单的redo日志类型
只在某个页面的某个偏移量处修改了几个字节的值
栗子
如为了维护隐含主键递增,而需维护系统表空间的MAX ROW ID字段
复杂的redo日志格式类型
一条语句可能涉及更新多个页面
栗子:一个Insert操作
可能修改的页面
表中包含多个索引,一条INSERT语句可能更新多棵B+树
针对一颗B+树,可能更新叶子节点,页可能更新内节点,页可能创建新的页面
可能更新数据页的File Header、Page Header、Page Directory信息
表中包含多个索引,一条INSERT语句可能更新多棵B+树
初始方案
方案一:在每个修改的地方都记录一条redo日志
方案二:将整个页面的第一个被修改的字节到最后一个修改的字节之间的所有数据当成是一条物理redo日志的具体数据
新的redo日志类型(逻辑日志)
怎么做?
在系统崩溃重启时,并不能直接根据这些日志的记载,将页面的某个偏移量恢复成某个数据。
而是需要调用一些事先准备好的函数,执行完这些函数后才能将页面恢复成系统崩溃前的样子。
而是需要调用一些事先准备好的函数,执行完这些函数后才能将页面恢复成系统崩溃前的样子。
关键
把能恢复一条数据的必要信息保存下来
常见类型
MLOG_REC_INSERT
插入一条非紧凑行记录(如Reduant)
MLOG_COMP_REC_INSERT
插入一条紧凑行格式的记录
MLOG_COMP_PAGE_CREATE
创建一个存储紧凑行格式记录的页面的redo日志类型
MLOG_COMP_REC_DELETE
删除一条使用紧凑行格式记录的redo日志类型
MLOG_COMP_LIST_START_DELETE
表示从某条给定记录开始删除页面中的一系列使用紧凑行格式记录的redo日志类型
MLOG_COMP_LIST_END_DELETE
删除一系列记录直到某条记录
MLOG_ZIP_PAGE_COMPRESS
压缩数据页的redo日志类型
Mini-Transaction
以组的形式写入redo日志
以组的形式写入redo日志
需要保证原子性的操作必须以组的形式来记录redo日志,在进行系统崩溃重启时,针对某个组的redo日志
要么把全部的日志都恢复掉,要么一条也不恢复
要么把全部的日志都恢复掉,要么一条也不恢复
栗子
更新Max Row ID属性产生的redo日志是不可分割的
向聚簇索引对应B+树的页面中插入一条记录时产生的redo日志是不可分割的
向某个二级索引对应B+树的页面中插入一条记录时产生的redo日志是不可分割的
还有其他的一些页面的访问操作时产生的redo日志是不可分割的
保存日志的页面
redo log block
redo log block
解决磁盘过慢的
redo日志缓冲区
redo日志缓冲区
redo日志写入log buffer(以mtr为单位)
redo日志(下)
redo日志刷盘时机
log buffer空间不足时
事务提交时
为了保证持久性
后台线程不停的刷刷刷
正常关闭服务器时
redo日志文件组
如何调节文件组所在的目录、每个日志文件的大小、指定redo日志文件个数
redo日志文件格式
LSN
目的
为了记录已经写入(到log buffer)的日志量
特性
每一组由mtr生成的redo日志都有一个唯一的LSN值与其对应,LSN值越小,说明redo日志产生的越早
log_buffer中几个重要的指针
标志当前日志写到的最大LSN
buf_free
lsn
标志将log buffer中的日志写入到磁盘
buf_next_to_write
flushed_to_disk_lsn
lsn值与redo日志文件偏移量的对应关系
flush链表中的LSN
只有当flush链表中的脏页刷新到磁盘时,相应的redo日志才能被覆盖
检查点checkpoint
log file中的几个重要指针
checkpoint_lsn
flushed_to_disk_lsn
lsn
崩溃恢复的过程
确定恢复的起点
读取log file中的控制信息checkpoint_lsn
确定恢复的终点
遍历每一个block header,看它是否有没有被写满。 LOG_BLOCK_HDR_DATA_LEN=512
怎么恢复
1. 使用哈希表提高速度
将表空间,页号作为key, redo日志作为value。那么恢复的时候可以一次性将一个页面修复完毕,减少大量的随机IO
2. 跳过已经刷新到磁盘的页面
因为检查点之后可能有脏页被刷新到磁盘了,那这些页面就不需要恢复,需要跳过
如何跳过? 在redo日志中可以定位数据页。而数据页中有属性记录newest_modification。
拿它与checkpoint_lsn相比,如果前者大,说明在check_point之后,该脏页被刷新到了磁盘。
因此这些页面就可以跳过啦。否则的话就不能跳过。要进行恢复
拿它与checkpoint_lsn相比,如果前者大,说明在check_point之后,该脏页被刷新到了磁盘。
因此这些页面就可以跳过啦。否则的话就不能跳过。要进行恢复
undo日志(上)
它是用来干什么的
因为数据库要保证原子性,怎么才能提供回滚功能,让这个事务看起来什么都没做呢?
记录回滚信息
当插入了一条信息,至少需要把这条记录的主键值记录下来,之后回滚的时候就可以根据这个主键值把相应记录删掉
当删除了一条记录,则至少要把这条记录中的内容都记录下来,回滚的时候进行插入
当更新了一条记录,则至少要把旧值记录下来,回滚的时候进行恢复旧值
INSERT操作对应的undo日志
主要是把这条记录的主键信息记上
roll_pointer隐藏列的作用
本质是一个指针,指向记录对应的undo日志
DELETE操作对应的undo日志
UPDATE操作对应的undo日志
不更新主键的情况
产生1条UNDO日志,对应TRX_UNDO_UPD_EXIST_REC
产生1条UNDO日志,对应TRX_UNDO_UPD_EXIST_REC
就地更新
更新记录时,对于被更新的每个列来说,如果更新后的列和更新前的列占用的存储空间都一样大
先删除掉旧记录(用户线程同步做delete mark与purge操作),再插入新记录
更新记录时,如果有任何一个被更新的列更新前和更新后占用的存储空间不一致
更新主键的情况
分两步操作
将旧记录进行delete mark操作,在事务提交后才由专门的线程做purge操作,把它加入到垃圾链表中
(否则别的事务就访问不到该记录了。像INSERT,DELETE操作还能根据ROLL POINTER找到)
(否则别的事务就访问不到该记录了。像INSERT,DELETE操作还能根据ROLL POINTER找到)
根据更新后各列的值创建一条新记录,并将其插入到聚簇索引中
产生2条UNDO日志
分别对应DELETE的UNDO日志(在对记录进行delete mark之前) 和 INSERT的UNDO日志 (在插入新记录时)
分别对应DELETE的UNDO日志(在对记录进行delete mark之前) 和 INSERT的UNDO日志 (在插入新记录时)
undo日志(下)
存放UNDO日志的 FIL_PAGE_UNDO_LOG页面
UNDO页面链表
每个的事务链表要分开
普通表和临时表的链表要分开
(原因:临时表不需要记录REDO日志)
(原因:临时表不需要记录REDO日志)
不同类型的链表要分开
为每个UNDO页面链表生成一个段
UNDO LOG Segment Header
UNDO LOG Segment Header
存储一组(同一事务同一链表)信息的
UNDO LOG HEADER
UNDO LOG HEADER
重用UNDO链表
存储多个链表的first undo page的
回滚段ROLLBACK Segment Header
(表空间,页号)
回滚段ROLLBACK Segment Header
(表空间,页号)
有1024个slot,对应1024个链表
多个回滚段
存放多个ROLLBACK Segment Header信息
存放多个ROLLBACK Segment Header信息
在5号页面存放128个
ROLLBACK Segment Header的地址
提高支持UNDO链表的数量(128组*每组1024个插槽)
ROLLBACK Segment Header的地址
提高支持UNDO链表的数量(128组*每组1024个插槽)
回滚段的分类
第0号、第33~127号回滚段属于一类
第1~32号回滚段属于一类(存放临时表)
为事务分配Undo页面链表详细过程
按着UNDO页面链表的存储结构来就好啦。
还要加上重用链表的相关优化(即分配slot前先查看有没有缓存的链表)
还要加上重用链表的相关优化(即分配slot前先查看有没有缓存的链表)
事务隔离级别
与MVCC
与MVCC
事务并发执行遇见的问题
脏写
如果一个事务修改了另一个未提交事务修改过的数据
脏读
如果一个事务读取到了另一个未提交事务修改过程的数据
不可重复度
如果一个事务能读到了另一个已经提交的事务修改过的数据,并且其他事务每对数据进行一次
修改并提交后,该事务都能查询得出最新的值。
修改并提交后,该事务都能查询得出最新的值。
幻读
如果一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入符合这些条件的记录。
则原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来。
则原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来。
SQL标准中的隔离级别
READ UNCOMMITED
读未提交
READ COMMITED
读已提交
REPETABLE READ
可重复读
SERIALIZABLE
可串行化
MVCC的原理
Multi-Version Concurrency Control (多版本并发控制)
类似于git工作流
通过生成一个ReadView,然后通过ReadView找到符合条件的记录版本(历史版本是由undo日志构建的),其实就像是在生成ReadView的那个时刻做了一次时间静止(就像用相机拍了一个快照),查询语句只能读到在生成ReadView之前已提交事务所做的更改,在生成ReadView之前未提交的事务或者之后才开启的事务所做的更改是看不到的。
可见性问题
READ UNCOMMITED
读取记录的最新的版本即可
READ COMMITED
REPETABLE READ
SERIALIZABLE
进行加锁。读写,写写操作排队访问
控制可见性的
ReadView
ReadView
4个重要的概念
m_ids
表示在生成ReadView时当前系统中活跃的读写事务的事务id列表
m_ids
表示在生成ReadView时当前系统中活跃的读写事务的事务id列表
min_trx_id
表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值
max_trx_id
表示生成ReadView时系统中应该分配给下一事务的id
creator_trx_id
表示生成该ReadView的事务的事务id
表示生成该ReadView的事务的事务id
可见性的推断
如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,
意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,
表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
如果被访问版本的trx_id属性值大于ReadView中的max_trx_id值,
表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。
READ COMMITED
每次读取数据前都生成一个ReadView
所以会有不可重复读现象
REPETABLE READ
在第一次读取数据时生成一个ReadView
所以不会有不可重复读现象
关于purge(清除)
insert undo在事务提交之后可以被释放掉了; 而update undo由于还需要支持MVVC,不能立即删除掉
为了支持MVVC,对于delete mark操作来说,仅仅是在记录上打了一个删除标记,并没有真正将他们删除掉。
随着系统的运行,在确定系统中最早产生的那个ReadView的事务不会再访问某些update undo日志以及被打了删除标记的记录后,有一个后台运行的purge线程会把它们真正的删除掉。
0 条评论
下一页