目录
1. 什么是事务?
事务就是一段sql语句的批处理,但是这个批处理是一个atomic(原子性的操作) ,不可分割,要么都执行,要么回滚(rollback)都不执行。这样就避免了某个操作成功某个操作失败,从而导致数据的不一致。mysql数据库支持多种存储引擎,但是只有innoDB存储引擎支持事务。
2. 事务的特性
Mysql,Oracle等关系型数据库事务具备ACID特征。所谓ACID是Atomic(原子性),Consistent(一致性),Isolated(隔离性)和Durable(持续性)。
一致性:其中的一致性不是很好理解,结合我看过的一些资料,在这里叙述一下: 一致性是指事务前后,数据库从一个一致性状态变换到另外一个一致性状态。举例说明,A和B的账户里都有1000元(A和B加起来是2000),A向B转账500,将A所在数据库的accout表中A的记录设置为500,然后将B所在的数据库的account表中B对应的记录改为1500,这个转账过程完成后,A和B加起来还是2000,总的钱的数量既没有增多,也没有减少,多个数据库的多张表(当然也可以是同数据库同表,但是不同记录)在操作完成之后,达到了预期的结果,这就保证了一致性。
个人观点:一致性强调的最终状态,要么是初始状态(有可能事务回滚了),要么是事务成功执行后的最终状态;原子性强调的是操作的完整性,连续的操作不可分割,要么全部成功,要么全部失败
一致性是事务的最终目的,原子性、隔离性、持久性都是为了实现一致性。
3.事务特性之隔离性:
事务的隔离性是有级别的,在某些级别下,在并发事务的情况下,不能保证一个事务的执行不被其它事务干扰。既然会被干扰,那么就会出现一些问题。
对于两个并发执行的事务,如果涉及到操作同一条记录的时候,可能会发生问题。因为并发操作会带来数据的不一致性,包括脏读、不可重复读、幻读等。数据库系统提供了隔离级别来让我们有针对性地选择事务的隔离级别,避免数据不一致的问题。
SQL标准定义了4种隔离级别,那么在不同事务隔离级别下,并发事务会出现哪些问题呢?
如果没有指定隔离级别,数据库就会使用默认的隔离级别。在MySQL中,如果使用InnoDB,默认的隔离级别是Repeatable Read。
1.Read Uncommitted:
Read Uncommitted是隔离级别最低的一种事务级别。在这种隔离级别下,一个事务会读到另一个事务更新后但未提交的数据,如果另一个事务回滚,那么当前事务读到的数据就是脏数据,这就是脏读(Dirty Read)。
可以看下面的脏读示意图:
小结: 脏读是由于并发事务下同一条记录的Update造成的。
2.Read Committed:
这个级别解决了脏读的问题,一个事务只能读取别的事务修改并成功提交的数据。
在Read Committed隔离级别下,一个事务可能会遇到不可重复读(Non Repeatable Read)的问题。不可重复读是指,在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
可以看下面的不可重复读示意图:
小结: 不可重复读也是由于并发事务下同一条记录的Update造成的。
3.Repeatable Read:
这个级别解决了不可重复读的问题,在一个事务执行过程中,不会读取其它事务对同一条数据的修改。
在Repeatable Read隔离级别下,一个事务可能会遇到幻读(Phantom Read)的问题。
幻读是指,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了。
可以看下面的幻读示意图:
小结: 幻读是由于并发事务下的插入或者删除造成的。
4.Serializable:
解决所有并发事务造成的问题,因为事务是一个一个排队c串行执行的。
Serializable是最严格的隔离级别。在Serializable隔离级别下,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。
虽然Serializable隔离级别下的事务具有最高的安全性,但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用Serializable隔离级别。
4.事务的实现原理:
概述:事务的实现依靠的是innodb的redo log, undo log和锁。
- undo日志用于记录事务开始前的状态,用于事务失败时的回滚操作
- redo日志记录事务执行后的状态,用来恢复未写入data file的已成功事务更新的数据,保证事务的持久性。
1.不加事务出现问题:
数据库数据存放的文件称为data file,日志文件称为log file。数据库数据是有缓存的,如果没有缓存,每次都写或者读物理disk,那性能就太低下了,数据库数据的缓存称为data buffer。同理,数据库日志也是有缓存的,日志(redo)缓存称为log buffer。既然数据库数据有缓存,就很难保证缓存数据(脏数据)与磁盘数据的一致性。比如某次数据库操作:
update driver_info set driver_status = 2 where driver_id = 10001;
更新driver_status字段的数据会存放在缓存中,等待存储引擎将driver_status刷新data_file,并返回给业务方更新成功。如果此时数据库宕机,缓存中的数据就丢失了,业务方却以为更新成功了,数据不一致,也没有持久化存储。
2.添加事务解决问题:
上面的问题就可以通过事务的ACID特性来保证。
BEGIN transaction;
update driver_info set driver_status = 2 where driver_id = 10001;
COMMIT;
这样执行后,更新要么成功,要么失败。业务方的返回和数据库data file中的数据保持一致。要保证这样的特性这就不得不说存储引擎innodb的redo和undo日志。
3. 事务的实现原理:
存储引擎也会为redo undo日志开辟内存缓存空间,log buffer。磁盘上的日志文件称为log file,是顺序追加的,性能非常高,注:磁盘的顺序写性能比内存的写性能差不了多少。
undo日志用于记录事务开始前的状态,用于事务失败时的回滚操作;redo日志记录事务执行后的状态,用来恢复未写入data file的已成功事务更新的数据。例如某一事务的事务序号为T1,其对数据X进行修改,设X的原值是5,修改后的值为15,那么Undo日志为<T1, X, 5>,Redo日志为<T1, X, 15>。
梳理下事务执行的各个阶段:
- 写undo日志到log buffer(在事务执行之前)
- 执行事务,并写redo日志到log buffer。
- 如果innodb_flush_log_at_trx_commit=1,则将redo日志写到log file(持久化redo log),并刷新落盘。
- 提交事务。
为什么没有写data file,事务就提交了?
因为事务虽然提交了,但是数据并没有直接写入data file,而是写入到了缓冲区data buffer,data buffer中的数据会在合适的时间由存储引擎写入到data file,如果在写入之前,数据库宕机了,根据落盘的redo日志,完全可以将事务更改的数据恢复。好了,看出日志的重要性了吧。先持久化日志的策略叫做Write Ahead Log,即预写日志。在数据库的世界里,日志貌似比数据还重要,有了日志则保证了一切,保证了事务,保证了数据最重要的一致性。
分析几种异常情况:
- innodb_flush_log_at_trx_commit=2 (innodb_flush_log_at_trx_commit和sync_binlog参数详解)时,将redo日志写入logfile后,为提升事务执行的性能,存储引擎并没有调用文件系统的sync操作,将日志落盘。如果此时宕机了,那么未落盘redo日志事务的数据是无法保证一致性的。
- undo日志同样存在未落盘的情况,可能出现无法回滚的情况。
checkpoint:
checkpoint是为了定期将db buffer的内容刷新到data file。当遇到内存不足、db buffer已满等情况时,需要将db buffer中的内容/部分内容(特别是脏数据)转储到data file中。在转储时,会记录checkpoint发生的”时刻“。在故障回复时候,只需要redo/undo最近的一次checkpoint之后的操作。