事务是什么?
MySQL中的事务,是指一个逻辑单元执行的一系列操作,它应当被当成是一个不可分离的完整操作,必须要满足四个属性(原子性、持久性、隔离性、一致性)。MySQL支持事务的 数据库引擎是InnoDB。
- 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
- 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
- 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
- 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中
原子性如何实现?
基于InnoDB的回滚日志:undo log。
undo log是逻辑格式的日志,保存了事务发生之前的前一个版本的数据,同时MVCC的非锁定读也依赖于undo log。
在事务开始之前,InnoDB会将当前版本生成undo log,事务回滚则根据undo log的记录回滚,事务提交后undo log不会立即删除,会由一个线程来判断当前是否有其他事务在使用当前的undo log,来判断是否可以清理。
持久性如何实现?
基于InnoDB的重做日志:redo log和binlog。
redo日志记录事务执行后的状态,用来恢复未写入data file的已成功事务更新的数据。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启MySQL服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。
在事务开始之后,redo log会先写入缓冲区,根据InnoDB的配置,选择每秒将缓冲写入日志文件还是每次事务提交时写入日志文件,当对应事务的脏页写入磁盘后,redo log对应的空间就可以被释放了。
InnoDB的落库实际上并不是以数据写入到表文件中为落库完成的标志,当写入日志完成后,即可认为落库成功,这就是write-ahead logging技术,原因是日志的追加写显然要比写入表文件更简单。
同时也需要MySQL的binlog来协助实现一致性。具体的方法为两阶段提交法,能保证数据的一致性。
具体过程为:
- 更新操作先写入redolog,这时候这条log的状态是prepared状态
- 再将逻辑日志写入binlog
- 最后在binlog写好之后,把redolog里的这条日志的状态改为commit
通过这样的方式,能够保证使用binlog备份数据时备份数据与原始数据的一致性。
隔离性如何实现?
隔离性是最复杂的一个点,它主要是为了解决以下几个问题:
脏读:一个事务读到另一个事务未提交的数据
不可重复读:一个事务连续两次读取独到的记录不一致
幻读:一个事务进行范围读取,两次读取到的记录条数不一致
针对以上三个问题,MySQL提出了以下四种隔离级别,可通过参数设置:
- 读未提交(Read Uncommitted):读到未提交的数据,这种情况下InnoDB本身未作任何处理,脏读幻读不可重复读均无法避免,因此效率是最高的。
- 读提交(Read Committed):只能读到提交的数据,实现原理是MVCC,多版本并发控制。
每个表中都有两个隐藏的列,一个是产生该条记录的事务ID,由于事务ID是递增的,因此实际上就是版本号,另一个隐藏列是此条记录对应的上一个版本的地址。当进行普通读取时,使用快照读,InnoDB会维护一个ReadView,记录着当前活跃的,仍未提交的事务,对于当前进行查询的事务,只能读取到版本号小于他且已经提交的数据记录,因此就能避免读到未提交的数据。
此种隔离级别避免了脏读。 - 可重复读(Repeated Read):不会出现连续两次读取的记录不一致的情形。
也是通过MVCC实现,不同的是生成ReadView的策略,对于每一个事务,都会在事务开始时记录当前的ReadView,以后的每次读取都会使用本事务的ReadView,避免了RC隔离级别下的使用全局的ReadView带来的不可重复读问题。
那么RR级别下的当前读是如何解决幻读的呢?这里用到了InnoDB的next-key lock。
InnoDB的行级锁有三种:record lock:只会锁住当前一条记录;gap lock:锁住一个区间,一个gap;next key lock:锁住当前的key和到下一个key的区间。InnoDB使用next key lock,锁住下一个区间,当然就不能够实现往间隙中插入数据,也就不会出现数据条目不一致的问题。 - 串行化:所有事务串行执行,自然不会有隔离的问题,但是效率显而易见极低。
一致性如何实现?
一致性实际上是由开发者来保证的,因为一致性实际上是属于应用层的一个概念,即数据应该是有意义的。比如常见的转账场景,我们必须要保证A转账给B后,无论是否成功,A和B的总和应是不变的,这实际上转换成了事务的原子性的问题。比如我们使用了Redis做缓存,如何保证Redis和MySQL的数据的一致性,这实际上也是依赖于我们的应用层逻辑。一般来讲,我们通过事务的AID+正确的业务逻辑,就能够实现事物的C(一致性)。