这是我的第一篇掘金博客,开启掘金写作之路。
事务的四大特性为Atomic原子性、Consistency一致性、Isolation隔离性和Constancy持久性。
1.原子性是指构成事务的多条操作语句是一个整体,要么全部提交成功,要么全部失败回滚。这意味着只要有一条语句失败整个事务就要回滚。原子性是由undo log实现的。undo log是一种逻辑日志,当一个事务对记录做了变更操作就会产生undo log,也就是说undo log记录了记录变更的逻辑过程。当一个事务要更新一行记录时,会把当前记录当做历史快照保存下来,多个历史快照会用两个隐藏字段trx_id和roll_pointer串起来(关于隐藏字段,这里不用考虑隐式主键id:DB_ROW_ID),形成一个历史版本链。可以用于MVCC和事务回滚。
当事务需要回滚时,利用undo log中记录历史快照信息进行逆操作。对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相反的update,把数据改回去。
2.持久性是指事务一旦成功提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。持久性是由redo log实现的。由于数据页是存放在磁盘上的,每次读取或修改记录要由磁盘和内存进行数据交换,IO效率低,于是引入缓存Buffer Pool。每次读数据先从缓存中取,若是命中则直接读取,没命中的话再从磁盘上读,读到后放进缓存;每次发生数据修改都要先写到缓存中,缓存定期刷新到磁盘。这里就有个问题,若是在此期间数据库崩溃宕机导致缓存中有数据未同步到磁盘,就会使得数据丢失,持久性遭到破坏。于是引入redo log。redo log是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改成什么样。它用来恢复提交后的物理数据页(恢复数据页且只能恢复到最后一次提交的位置)。redo log才用的是WAL(Write-ahead logging预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因Mysql宕机而丢失,从而满足了持久性的要求。
3.隔离性是指事务内部的操作与其他事务是隔离的,并发执行的各个事物之间互不干扰。隔离性是由锁机制、MVCC和undo log实现的。
事务之间的读写操作要求读读不阻塞、读写阻塞、写写阻塞,也就是A事务读取某行数据时其他事务可以读但不能对改行数据修改,A事务写的时候其他事务既不能写也不能读。这是由数据库的锁机制实现的。数据库按照粒度分有表锁和行锁,InooDB两者都支持,并且行锁是只有索引检索时才开启的。
隔离性的四个级别为:读未提交、读已提交、可重复读(Mysql的默认隔离级别)和Serializable。
脏读是指事务读取到了其他事务未提交的修改数据。解决方法是加锁,在事务修改数据并提交以后再释放写锁。
不可重复读指的是一个事务包含的多次对某数据的读取操作,由于其他事务对该数据修改并提交,而导致事务对该数据前后读取不一致的问题。解决方法是MVCC。
幻读与不可重复读类似,区别在于不可重复读是因为其他事务对数据的更改或删除造成多次读取不一致,而幻读是因为事务读取到了其他事务新提交(新增)的数据。解决方法是MVCC+间隙锁(Gap Lock)
MVCC(Multi Version Concurrent Control多版本并发控制),是一种行锁的升级形式。实现方式是为每一行维护两个隐藏字段创建时间和删除时间。这两个字段保存的是插入该记录时的系统版本号。系统版本号类似于乐观锁的版本号,该版本号随着事务的开启而递增,每开启一个事务,系统版本号加一。每个事务开启时同样获得一个系统版本号称为事务版本号。InooDB只允许事务读取创建时间版本号小于等于该事务版本号的记录,这样保证了事务读取到的数据都是在这条事务开启前或这条事务新插入或修改的记录。并且行的删除版本要么未定义要么大于事务版本号,这样保证了该事务读取到的行在事务开始之前未删除。
4.一致性是指事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。实体完整性(如行的主键存在且唯一)列完整性(如字段的类型、大小、长度要符合要求)外键约束用户自定义完整性(如转账前后,两个账户余额的和应该不变)实现。
可以说,一致性是事务追求的最终目标:前面提到的原子性、持久性和隔离性,都是为了保证数据库状态的一致性。此外,除了数据库层面的保障,一致性的实现也需要应用层面进行保障。