ACID
- 原子性:要么都成功,要么都失败
- 一致性:事务前后状态一致,比如某张表的id是唯一约束,不能重复。则无论事务成功失败,都不能出现重复的id。
- 隔离性:A事务不应该访问到B事务没有提交的数据
- 持久性:事务一旦提交,结果是永久的。
事务分类
1. 扁平事务
最简单也最频繁的事务。就是begin开始,中间一个或几个编辑操作,然后一起提交。整体要么都成功要么都失败。
缺点:只能全部回滚,不能部分回滚。在某些大事务中,代价太大
2. 带保存点的扁平事务
对上诉问题的优化处理,可以设置保存点,回滚时选择回滚到哪个保存点。
缺点:如果是大事务,所有持有锁是不会释放的。select * from a; set autocommit =0; start transaction ; insert into a(id) values (12); savepoint x; insert into a(id) values (13); savepoint m; insert into a(id) values (14); rollback to x; select * from a;
3. 链事务
保存点模式的一个变种。由多个小事务组成,上一个事务的结果对下一个事务可见,只能回滚当前节点的事务。进行下一个事务时,会释放上一个事务的持有锁。
4. 嵌套事务
5. 分布式事务
事务的实现
- 事务的隔离性由锁机制实现
- 原子性、持久性由数据库的redo log实现。
- 一致性由数据库的undo log实现。
undo并不是redo的逆操作。
- undo log和redo log都可视为是一种恢复操作。redo恢复的是提交事务修改的页操作,而undo回滚行记录到某个特定版本。
- redo是物理日志,记录的是页的物理修改操作日志。undo 是逻辑日志,根据每行进行记录。
redo log
- redo log由两部分组成,一是内存中的redo log buffer,其是易失的;二是redo log file,是持久的;
- Innodb是事务的存储引擎,事务的提交必须是数据写入文件之后的commit操作才算完成。这就提到了之前介绍的提前写日志机制和double write(详见Mysql之Innodb引擎)
- redo log是顺序写入的,在数据库运行时不需要对redo log的文件进行读取操作。
- redo log允许用户设置非持久性的情况,这样可显著提高数据库性能,但是如果宕机,会丢失最近一段时间的数据。
innodb_flush_log_at_trx_commit 默认1,表示事务提交时,必须执行一次fsync。0表示提交时不进行写入redo log的操作。2表示提交时写入redo log file,但不执行fsync。此时数据并没有真正写入文件,而是在操作系统的缓存中,此时如果数据库重启,数据不会丢失,但如果系统重启,则可能丢失。
注:mysql还有一种二进制日志binlog其作用是PIONT-IN-TIME恢复和主从复制环境的建立。从本质上,两者有很大的不同。
redo log | binlog | |
---|---|---|
出处 | 是Innodb引擎 | mysql 数据库 |
内容形式 | 物理格式日志,记录的是对每个页的修改 | 逻辑日志,记录的是对应的sql |
写入时间 | 事务进行中不断写入 | 只在提交时写入一次 |
redo log 结构
log group
- log group指重做日志组,有多个重做日志组成。
- 是逻辑概念。
1 redo log file 前2kb
- 对于log group的第一个redo log file 前2kb不存储log block(第二个也要预留2kb空间),而是保存4个512b的块,其存放内容如图所示。
- 由于这部分数据的存在,redo log file的写入并不是完全顺序的。
- 这部分数据对于Innodb存储引擎的恢复操作非常重要和关键。
- log file header后面的checkpoint的值是交替写入的。这样避免了因介质失败而导致无法找到可用checkpoint的情况。
log block
redo log是由log block组成,每个log block为512字节。由于重做日志块的大小和磁盘扇区大小一样都为512字节,因此重做日志的写入可以保证原子性,不需要double write;
log block的结构
由三部分组成,大小如图所示:
- log block header
LOG_BLOCK_HDR_NO: 把log buffer看作一个数组,该值表示在数组中的位置。递增并且循环使用
LOG_BLOCK_HDR_DATA_LEN:表示log block所占用的大小。
LOG_BLOCK_FIRST_REC_GROUP:表示log block中第一个日志所在的偏移量
LOG_BLOCK_CHECKPOINT_NO:该log block最后被写入时检查点第4字节的值
undo log
- 用作事务回滚和MVCC功能。
- undo log是逻辑日志。回滚时只是将数据逻辑的恢复到原来的样子。因为在多用户并发系统中,可能会用多个事务对同一个页的不同记录进行修改,因此不能将一个页回滚到事务开始的样子。(Innodb存储引擎回滚时,它实际做的是一个与原来相反的操作。对于insert,则会delete;对于delete,则会insert;update会执行相反的一个update)
- undo log也会产生redo log,因为undo log也需要持久性的保护(事务在undo log segment分配页并写入undo log的过程同样是需要写入redo log)。
- undo log是需要进行随机读写的,为了提高磁盘利用率。
undo log 存储管理
Innodb存储引擎对undo采用段的方式进行管理。首先Innodb存储引擎有rollback segment。每个rollback segment有1024个 undo segment,在每个undo segment中申请undo段。共享表空间偏移量5的页记录了所有rollback segment header所在的页。
show variables like 'innodb_undo_%'
字段 | 值 | 说明 |
---|---|---|
innodb_undo_directory | . | 用于设置rollback segment文件所在路径,“.”表示当前Innodb存储引擎的目录 |
innodb_undo_logs | 128 | 用于设置rollback segment的个数,表示可支持128 * 1024 个事务 |
innodb_undo_tablespaces | 3 | 用于设置rollback segment文件的数量,这样rollback segment可以较为平均的分布在多个文件中 |
事务提交时,Innodb存储引擎需要做两件事
- 将undo log放入列表,以供之后的purge操作;
- 判断undo log所在的页是否可以重用,可以则分配给下一个事务。
事务提交时,并不能立即删除undo log,因为可能还有其他事务需要根据undo log来获取之前版本的记录。故将undo log放入一个链表,是否可以删除由purge线程判断。
show engine innodb status; -- 结果 -- -- history list length 12 :代表redo log的数量 --
- undo log的格式
- 查看undo log信息:可以通过两张表来查询:INNODB_TRX_ROLLBACK_SEGMENT、INNODB_TRX_UNDO。
purge
- purge用于完成最终的delete和update操作。这是因为支持MVCC,所有记录不能在事务提交时立即进行处理。若改行没有被任何事务引用,那么才执行真正的操作。
- history list根据事务提交顺序,将undo log进行连接,先提交的事务总是在尾端。
- 在purge过程中,首先innodb存储引擎会根据history list找到第一个需要被清除的记录,清楚之后会在该undo log所在的页查找是否有其他可以被清除的记录。如有则清除,没有则再去history list中查找。这种设计避免了大量的随机IO,从而提高效率。
group commit
若不是只读事务,则每次事务提交时都需要进行一次fsync操作,确保redo log刷到磁盘。但是fsync性能有限,为了提高效率,一次fsync会确保多个事务日志刷新到磁盘。
Innodb存储引擎事务提交时会进行两个操作
1)修改内存中事务对应的信息,将日志写入redo log buffer.
2)调用fsync将确保日志从redo log buffer写入到redo log。
对于步骤1)来说,步骤2)时一个缓慢的过程,在进行步骤2)时,其他事务可以进行步骤1)的操作,当正在提交的事务完成提交操作后,再次进行步骤2)时,可以将多个事务的redo log一次fsync刷到磁盘。
BLGC(binary log group commit)
为了弥补Innodb1.2之前,在开启二进制日志后,group commit失效问题。
BLGC事务提交过程
在 MYSQL数据库上层进行提交时首先按顺序将其放入一个队列中,队列中的第一个事务称为 leader,其他事务称为 follower,leader控制着 follower的行为。BLGC的步骤分为以下三个阶段:
- Flush阶段,将每个事务的二进制日志写入内存中。
- Sync阶段,将内存中的二进制日志刷新到磁盘,若队列中有多个事务,那么仅一次 fsync操作就完成了二进制日志的写人,这就是BLGC。
- Commit阶段,leader根据顺序调用存储引擎层事务的提交,INNODB存储引擎本就支持 group commit,因此修复了原先由于锁 prepare_ commit mutex导致group commit失效的问题。
当有一组事务在进行 Commit阶段时,其他新事务可以进行 Flush阶段,从而不会使group commit失效。当然 group commit 的效果由队列中事务的数量决定,若每次队列中仅有一个事务,那么可能效果和之前差不多,甚至会更差。但当提交的事务越多时,group commit的效果越明显,数椐库性能的提升也就越大。