在使用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语句是如何加锁的?如果加锁对什么位置加锁?后面的文章会慢慢分享。
写得不好,大家见谅。有什么问题,大家可以提出来一起探讨。