事务:一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)
事务四大特征(ACID):
原子性(A):原子性就是不可拆分的特性,要么全部成功然后提交(commit),要么全部失败然后回滚(rollback)。MySQL通过Redo/undo Log重做日志实现了原子性,在将执行SQL语句时,会先写入 log buffer,再执行SQL语句,若SQL语句执行出错就会根据undo log buffer中的记录来执行回滚操作,由此拥有原子性。
一致性(C): 一致性指事务将数据库从一种状态转变为下一种一致的状态。比如有一个字段name有唯一索引约束,那么在事务前后都不能有重复的name出现违反唯一索引约束,否则回滚。由原子性,隔离性,持久性共同保证。
隔离性(I):事务A和事务B之间具有隔离性。通过锁来实现。
持久性(D):是事务的保证,事务终结的标志(内存的数据持久到硬盘文件中)。一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。具体实现原理就是在事务commit之前会将,redo log buffer中的数据持久化到硬盘中的redo log file,这样在commit的时候,硬盘中已经有了我们修改或新增的数据,由此做到持久化。
隔离性有隔离级别:
读未提交:read uncommitted
事务中的修改,即使没有提交,其他事务也可以看得到。
读已提交:read committed
大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”,该级别适用于大多数系统。
可重复读:repeatable read
保证了一个事务不会读到另一个并行事务已提交的数据。
串行化:serializable
最严格的级别,事务串行执行,资源消耗最大
脏读:当前事务读到了别的事务回滚前的脏数据
不可重复读:当前事务先进行了一次数据读取,然后再次读取到的数据是别的事务修改成功的数据,导致两次读取到的数据不匹配(update,delete)。通过mvcc解决
幻读:当前事务读第一次取到的数据没有对应记录a,但是插入时却因为其他并行事务插入并提交记录a导致冲突(insert)。通过Next-Key Locks解决
Next-Key Locks:在索引记录上加锁,并且在索引记录之前的间隙加锁(左开右闭)
在可重复读隔离级别下,对于普通select语句,通过mvcc读取快照,对于select...for update/lock in share mode,则通过Next-Key Locks保证不出现幻读。
(当where条件为唯一主键且存在记录时,临键锁优化为行锁,当不存在时,为临键锁;
当where条件为普通索引时为临键锁)
MVCC原理:
通过事务版本号进行mvcc,系统维护一个系统版本号,进行插入操作的时候就把插入记录的系统列事务id设置为当前系统版本号;对于删除操作,则是将记录标记为delete mark,并将事务id改为当前系统版本号;对于更新操作,则是同时保持旧记录和新纪录;对于查询操作则是根据当前系统版本号和查询行的事务id来判断记录是否可读。
决定记录是否可读的规则是由read view决定的,当隔离级别为可重复读时,每个事务开始时都会将当前系统所有的活跃事务拷贝到一个列表中(read view);当隔离级别为读取已提交时,在每个事务的每个语句开始时都会将所有活跃事务拷贝到read view中。
事务提交过程:
1. start transaction;
2. 记录 A=1 到undo log;
3. update A = 2;
4. 记录 A=2 到redo log;
5. 记录 B=3 到undo log;
6. update B = 4;
7. 记录B = 4 到redo log;
8. 将redo log刷新到磁盘
9. commit
在1-8的任意一步系统宕机,事务未提交,该事务就不会对磁盘上的数据做任何影响。如果在8-9之间宕机,恢复之后可以选择回滚,也可以选择继续完成事务提交,因为此时redo log已经持久化。若在9之后系统宕机,内存映射中变更的数据还来不及刷回磁盘,那么系统恢复之后,可以根据redo log把数据刷回磁盘。
recovery
1.启动开始时检测是否发生崩溃
2.定位到最近的一个checkpoint
3.定位在这个checkpoint时flush到磁盘的数据页,检查checksum。如果不正确,说明这个页在上次写入是不完整的,从doublewrite buffer里把正确的页读出来,更新到buffer中的页
4.分析redo log,标识出未提交事务
5.顺序执行redo
6.rollback未提交的事务
两阶段提交:
两阶段提交:当事务提交时,innodb存储引擎会先做一个prepare操作,将事务的xid写入,接着binlog日志的写入。如果在innodb存储引擎提交前,mysql数据库宕机了,那么mysql数据库在重启后会先检查准备的xid事务是否已经提交,若没有,则在存储引擎层在进行一次提交操作。
第一阶段:InnoDB prepare,持有prepare_commit_mutex,并且write/sync redo log; 将回滚段设置为Prepared状态,binlog不作任何操作;
第二阶段:包含两步,1> write/sync Binlog; 2> InnoDB commit (写入COMMIT标记后释放prepare_commit_mutex);
以 binlog 的写入与否作为事务提交成功与否的标志,innodb commit标志并不是事务成功与否的标志。
事务崩溃恢复过程如下:
1> 崩溃恢复时,扫描最后一个Binlog文件,提取其中的xid;
2> InnoDB维持了状态为Prepare的事务链表,将这些事务的xid和Binlog中记录的xid做比较,如果在Binlog中存在,则提交,否则回滚事务。
通过这种方式,可以让InnoDB和Binlog中的事务状态保持一致。
如果在写入innodb commit标志时崩溃,则恢复时,会重新对commit标志进行写入;
在prepare阶段崩溃,则会回滚,在write/sync binlog阶段崩溃,也会回滚。