参考
概念
数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成
ACID
atomicity 原子性
事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行
就像atm取款一样,出账和入账不能部分完成。
consistency 一致性
事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束
比如唯一键约束,外键约束
isolation 隔离性
多个事务并发执行时,一个事务的执行不应影响其他事务的执行
通常通过锁,或者mvcc等实现
durability 持久性
已被提交的事务对数据库的修改应该永久保存在数据库中
事务提交的时候,redo log, binlog会刷盘(也可根据配置参数调整),总之,在数据库层面是可以保证事务提交后的持久性的,但外部比如硬件导致的异常不可控
事务的实现
- 隔离性通过锁或者mvcc实现
- 原子性,持久性通过redo log
- 一致性通过undo log
redo log
innodb_flush_log_at_trx_commit
事务提交时候,redo log会把redo log buffer的数据写入系统文件缓存,并同步到磁盘(可以根据参数innodb_flush_log_at_trx_commit控制写入的时机)。
图片来源:mysql实战45讲
innodb_flush_log_at_trx_commit的取值情况:
- 0: 事务提交时不写重做日志(redo log和undo log),只是留在redo log buffer,master thread会每隔1s进行fsync的
- 1: 默认值,每次提交进行fsync
- 2: 提交时候,写入系统文件缓存
除了后台线程刷盘,还有两种情况导致redo log持久化的:
- redo log buffer大小达到innodb_log_buffer_size一半的时候
- 其他事物提交的时候如果设置的要刷盘,由于redo log buffer对于事务是公用的,事务会把别的事务的redo log连带fsync
sync_binlog
图片来源:mysql实战45讲
binlog也有刷盘策略可以控制的:
sync_binlog的取值:
- 0: 每次事务提交,只write写文件缓存,不fsync
- 1: 每次提交都fsync
- N>1: 每次提交都write,积累N个事务后,fsync
双1
sync_binlog=1,innodb_flush_log_at_trx_commit=1
这样设置可以保证每次提交事务,binlog和redo log都刷盘,高可用,但是io压力大
非双1
业务高峰期,备库延迟,批量导入这样的场景可以设置非双1,提高数据库性能,牺牲一点可靠性的代价。mysql实战45讲给的参考是:innodb_flush_logs_at_trx_commit=2、sync_binlog=1000。
两阶段提交
redo log的写入是基于两阶段提交
的:
二阶段提交(英语:Two-phase Commit)是指在计算机网络以及数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法。通常,二阶段提交也被称为是一种协议(Protocol)。在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。因此,二阶段提交的算法思路可以概括为: 参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。
概念中的两阶段提交主要针对分布式事务,这里讲的是内部事务的两阶段提交,大同小异。
LSN
log sequence number,日志序列号。在innodb中,占用8字节,单调递增
含义:
- 重做日志写入的总量 //1
- checkpoint的位置 //2
- 页的版本 //3
1: lsn表示了重做日志的字节的总量,加入原来lsn=1000,重做日志写入1000字节,则lsn=1100
2:可用于checkpoint的记录
redo log组的第一个日志文件的文件头会保存checkpoint位置,checkpoint表示已经刷新到磁盘的lsn号
3:每个页的头部,FIL_PAGE_LSN记录这个页的lsn号,表示该页最后刷新时的lsn号,通过对比redo log中的lsn号,可用于恢复页
group commit
fsync的效率是很慢的,组提交可以一次将多个fsync同时进行,提高效率。
两阶段提交过程:
1: innodb存储引擎进行prepare
2: mysql写入binlog日志
3: innodb写入重做日志文件
- 3.1 将日志写入日志缓冲
- 3.2 调用fsync
3.1步可以并发执行的时候,到3.2就可能一次把多个事务的日志刷盘。但是mysql老得版本不支持,因为老得版本两阶段提交会加锁prepare_commit_mutex,这个锁会导致3.2执行的时候,其他事物不能执行3.1.导致group commit不可用。不过mysql5.6通过BLGC解决了该问题:
BLGC
binlog group comit,mysql server层提交时,按顺序将其放入一个队列,队列中的第一个事务成为leader,其他事务成为follower,leader控制follower,分3个阶段:
- flush阶段,二进制日志写入内存
- sync阶段,二进制日志刷盘,将队列中所有事物刷盘
- commit阶段,leader根据顺序调用存储引擎层的事务的提交。innodb本来就支持组提交。
blgc实现没有用到prepare_commit_mutex锁,所以innodb层group commmit可以发挥出来,并且server层的binlog也变成了group commit,性能很好。
原来server层的binlog是每个线程单独一个binlog cache的,不像redo log是公用的redo log buffer,通过队列将binlog cache串起来组提交。并且leader可以根据顺序控制引擎层按照顺序提交,不需要加锁。
purge
由于mvcc的缘故,删除操作并不直接删除数据,因为这个版本的记录可能被其他事务引用,所以update/delete的时候如果是删除数据,只是把删除标记置为真(不确定这个标记是不是就是del_trx_id),真正删除是purge线程判断然后处理的。
undo log允许在一个页上保存多个undo log记录,之前也总结过undo log是通过链表串起来的,结合这两点,再看上面的图:
从链表第一个节点开始,trx1找到undo page1,清除trx1的undo log,然后继续在这个页中找其他的事务,清除trx3,然后找到trx5,trx5被其他事务引用了,不能删除,回到链表,找到trx2,然后trx6,trx4,整个undo page2就清除完了,该页可以被其他地方使用了。
以上就是purge的大概流程,优先清除一个页上的undo log,避免了过多的随机写操作。
purge还涉及一些配置参数,感觉不是很重要,就不记录了。