引言:
说起数据库事务,绕不开的就是两点,一个是事务的特性,另一个就是事务的隔离机制。每次复习数据库的时候,总会看到数据库特性有什么啊,不就是ACID嘛,A-原子性,C-一致性,I-隔离性,D-持久性。
那么Mysql是基于什么玩意儿去保证它的呢,瞬间懵逼.。。。。。
好了,不尴尬了,开始学习。。。
总结:
1. Mysql之如何实现持久性?
Redo log叫做重做日志,主要用来实现事务的持久性,即D。由两部分组成:
- 内存中的重做日志缓冲(redo log buffer),缺点是易失。
- 重做日志(redo log file)特点是持久的。
1.1 过程:
事务的存储引擎是InnoDB,通过Force Log at Commit机制实现事务的持久性,即当事务提交时,必须先将该事务的所有日志写入到重做到日志文件进行持久化,待事务的提交操作完成之后才算完成。
由于重做日志文件打开没有使用O_DIRECT选项,因此重做日志缓冲先写入到文件系统缓存。为了确保写入磁盘,必须进行一次fsync操作。因此fsync的效率取决于磁盘的性能,决定了事务提交的性能。
1.2 示例图
1.3 重做策略
innodb_flush_log_at_trx_commit用来控制重做日志刷新到磁盘的策略:
0:表示事务提交时不进行重做日志的刷新,该操作只在Master Thread中完成(Master Thread每1s会进行一次fsync操作)。
1:默认策略,每次事务提交时都进行刷新动作。
2:只写入文件系统的缓存内,不进行fsync的操作。
0和2一般不可取,不过还是提供了。
取值影响:默认插入的是50万条数据,时间的花费基本都是fsync上。
1.4采用redo log的好处?
好处就是将redo log进行刷盘比对数据页刷盘效率高.
具体表现如下
redo log体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。
redo log是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。
2 . Mysql之如何实现原子性?
Redo log是规范了重做的操作,因此如果一旦宕机,那么通过重做日志可以轻易恢复刚刚执行的数据变更,但是回滚的操作却不能解决,因此mysql提供了Undo log来记录原始数据,保证了事务能正常回滚,也就保证了原子性 A。
因此Innodb在执行变更操作时不仅仅生成了Redo日志,也同样生成了Undo日志,用来保证操作能够正常回滚。
2.1 存放地址
undo 存放在数据库内部一个特殊段中(segment),这个段称之为 undo段,位于共享表空间内。
2.2 误区
因为undo可以恢复回滚之前的数据,因此有人认为undo是将数据库物理地址恢复到执行语句或事务之前的样子,但是事实并非如此。undo是逻辑日志,所以只是将数据库逻辑的恢复到原来的样子。需要理解这句话,只是逻辑的取消之前的修改并且逻辑的恢复成原来的值。但是要注意的是数据结构和页本身在回滚之后可能大不相同,所以可以看出Innodb所做的工作其实是执行了相反的sql语句,,比如insert的语句回滚时执行delete,delete语句执行insert。
2.3 另一个作用MVCC
undo的另一个作用是MVCC(实现隔离性的一个条件)。当用户读取一行记录时,若该记录已经被其他事务占用时,当前事务可以通过undo读取之前的行版本信息,以此实现非锁定读取。(具体流程另述)
2.4 redo的产生
undo log也会同样产生redo log,因为undo log 同样也需要持久性的保护。
2.5 基本结构
- rseg0保留项
- rseg1-rseg32 用于临时表的undo
- rseg33-rseg128用于普通事务的undo。
- 每个回滚段维护的是一个段头页的地址,在该页中又划分了1024个槽用于记录undo对象,每个事务占用两个slot,一个用于insert,一个用于update/delete。
Innodb1.1 版本,只有一个回滚段,只能支持理论上1024个事务。
1.1版本之后,可以支持理论上128*1024个事务。
1.2版本开始,可以通过参数对Rseg进行设置。
innodb_undo_directory:设置文件位置,可以将回滚段放在共享表之外的地址。
innodb_undo_logs:设置回滚段的个数,默认是128。
innodb_undo_tablespaces:设置构成Rseg文件数量,可以均匀的分散在多个文件中。
2.6 执行过程
当事务提交时,InnoDB会做下面两件事:
- 将undo log放入列表,为purge提供依据。
2.判断undo log是否可以重用,可以的话分配给下个事务使用
因为事务提交之后不能马上删除undo log和它所在的页,这是因为可能其他事务需要通过undo log来得到之前版本的行记录。因此事务提交时将undo log放入一个链表中,是否能删除由purge线程决定。
2.7 格式
分为2个格式:
2.7.1 insert undo log
2.7.2 update undo loginsert操作由于事务的隔离性要求,它只能对本事务可见,对其他事务不可见,所以该undo log可以在事务提交后直接删除,不需要进行purge操作。而delete/update操作不行,需要放入链表中,由purge线程进行最后的删除。
3. purge
对于delete操作,并不是执行真正的删除,例如对于delete * from t where a = 1而是在讲delete flag设置成1,该记录仍然在B+树中。对于update语句,不是直接对记录进行更新,而是标识旧记录为删除,然后产生一条新记录。那么此时产生的旧版本的记录什么时候删除呢,怎么删除?就是通过purge操作。
purge线程开启purge操作,每10s进行一次,通过undo log来进行旧版本数据的删除。上面其实已经说过,一个undo 段页上允许多个对象重用产生undo log,由此InnoDB会维护一个history列表,它根据事务提交的顺序,将undo log进行连接。
- 先提交的事务排在尾端。undo page中存放了很多不同事务的undo log。
- 首先找到需要删除的trx1,清理之后会在trx1所在的undo page中寻找其他可以删除的trx,删除trx3之后发现了trx5,但是trx5被引用了,所以不删除,接着删除trx7。然后找到最尾端的trx2,发现trx2在Undo Page2,所以到Page2中再进行删除。
3 . Mysql之如何实现隔离性?
利用的是锁和MVCC机制。 (下篇详述实现原理。。。。。)
此次拿转账例子来做简单说明,有一个账户表如下,表名t_balance
其中id是主键,user_id为账户名,balance为余额。还是以转账两次为例,如下图所示
至于MVCC,即多版本并发控制(Multi Version Concurrency Control),一个行记录数据有多个版本对快照数据,这些快照数据在undo log中。
如果一个事务读取的行正在做DELELE或者UPDATE操作,读取操作不会等行上的锁释放,而是读取该行的快照版本。
由于MVCC机制在可重复读(Repeateable Read)和读已提交(Read Commited)的MVCC表现形式不同(下篇详述。。。)。
但是有一点说明一下,在事务隔离级别为读已提交(Read Commited)时,一个事务能够读到另一个事务已经提交的数据,是不满足隔离性的。但是当事务隔离级别为可重复读(Repeateable Read)中,是满足隔离性的。
4 . Mysql之如何实现一致性?
这个问题分为两个层面来说。
从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现AID三大特性,才有可能实现一致性。例如,原子性无法保证,显然一致性也无法保证。但是,如果你在事务里故意写出违反约束的代码,一致性还是无法保证的。例如,你在转账的例子中,你的代码里故意不给B账户加钱,那一致性还是无法保证。因此,还必须从应用层角度考虑。
从应用层面,通过代码判断数据库数据是否有效,然后决定回滚还是提交数据!