在使用Mysql的时候,有两种常用的存储引擎:MyISAM和InnoDB

InnoDB的性能不如MyISAM高,因为InnoDB提供事务支持以及外部键等高级数据库功能,很好奇InnoDB是如何保证事务特性,所以最近研究了一下。

事务有什么用?

银行转账,A转给B账户100元,需要保证A账户减少100元的同时B账户增加100元,如果A账户减少100元之后,系统crash,B账户并未增加100元,这样是没有数据保证一致性的。所以,如果两个动作是同一个事务,那么此时A操作需要回滚,保证数据一致性。

如何回滚?

首先,来说说事务的四个属性:ACID

1.原子性(Automic):一个动作要么做完,要么不做。

2.一致性(Consistency):保证数据处于一致性的状态,我理解就是保证数据有意义的。

3.隔离性(Isolation):多个事务并行的结果,应该和多个事务串行的结果一致。

4.持久性(Duration):一个事务一旦成功提交,对数据改变是永久性的。

其实这个四个属性中,最重要的是一致性,也就是说其他的三个属性都是为了保证一致性而存在。

原子性如何保证?

这个就需要说到Mysql的log,Mysql有许多种类的log,例如:二进制日志(binlog),错误日志,慢查询日志等等。这里需要引入的是undo log。这是Mysql的Write-Ahead-Logging机制。

例如:数据库中A=1,现在需要update A=3

那么整个步骤如下:

1.事务开始

2.记录A=1到undo log

3.修改A=3

4.将undo log写入磁盘

5.将A=3数据写入磁盘

6.事务提交

这个就是undo log工作流程,也就是在数据库断电或者crash的时候,在进行恢复的时候,把undo log里面的数据写回到数据库,这样就让数据回滚了。这样实现了事务的原子性,同时保证了数据的一致性。

但是,这样每个操作都会进行磁盘IO的写入,频繁的磁盘IO对性能是很大的降低。

引入redo log实现持久性,这个时候就在考虑如果只需要将日志写入磁盘,将数据缓存在内存中,一定时间后再进行更新。

例如:数据库中A=1,B=2,需要update A=3,B=4

1.事务开始

2.记录A=1到undo log

3.修改A=3

4.记录A=3到redo log

5.记录B=2到undo log

6.修改B=4

7.记录B=4到redo log

8.将redo log顺序写入磁盘

9.事务提交

整个过程中,数据修改都是在内存中,极大提升磁盘IO速度,而且将redo log提前写入磁盘。

如果整个事务执行的过程系统崩溃或者断电了,在系统重启的时候,恢复机制会将redo log中已提交的事务重做,保证事务的持久性;而undo log中未提交的事务进行回滚,保证事务的原子性。

Mysql通过预写式日志,保证了原子性和持久性。那么在多个事务并行的情况下,是否还能保证数据的一致性?如果A事务能够访问B事务正在提交的数据,然后B事务又做出了回滚,这样是不是就让数据乱套了。所以事务并行的情况下,这样是不够的。

隔离性:引入锁机制来保证。那么是什么情况下不锁,什么情况下锁呢?还有读写分离需要实现吗?这就需要去说说四种级别的隔离性。

1.未提交读:A事务可以读取B事务正在修改的数据,但是会出现B事务如果回滚,这样数据前后不一致,会造成脏读的现象。

2.已提交读:A事务只能读取B事务已提交的数据。B事务修改数据,A事务进行读取,数据未改,当B事务提交数据后,A事务读取数据不一致。这是幻读现象,因为同一个事务,我读取两次相同的数据返回的是不同的,这样并未保证一致性。

3.可重复读:利用MVCC并发版本控制,B事务修改数据,A事务进行读取,数据未改,当B事务提交后,A事务读取数据,这个时候会返回A数据之前读的版本。这是在这条数据上加上了GAP锁,针对A事务。这样保证前后两次读取同样的数据一致。

4.序列化:简单粗暴,读锁和写锁都是排它锁,不管读操作还是写操作都是会对数据上锁。这样粗暴造成性能下降很多。

大多数数据库默认使用已提交读的隔离级别,Mysql中InnoDB默认使用可重复读的隔离级别。

总结

Mysql的InnoDB保证事务性,最重要是保证数据的一致性。

通过预写式日志,undo log保证原子性,redo log保证持久性,设置隔离级别,保证并发事务进行的时候,保证数据一致性。

大概就是这样,至于细节处,Mysql语句是如何加锁的?如果加锁对什么位置加锁?后面的文章会慢慢分享。

写得不好,大家见谅。有什么问题,大家可以提出来一起探讨。