大家好,欢迎来到Tlog4J课堂,我是Jensen。面试官:数据库事务的四大特点是什么?考生:ACID,分别指原子性、一致性、隔离性、持久化(骄傲~)面试官:MySQL的InnoDB是如何保证ACID的??考生:啊,这个……ACID大家不陌生吧。ACID指的是数据库事务的基本特性:原子性、一致性、隔离性和持久性。那么这四个特性在MySql中是如何保证的呢?或者,在InnoDB存储引擎中,ACID是如何实现的?学校里的老师没教过。。。今天就来看看,MySQL为了实现这四个特性做了什么。首先,要回答这个问题,你必须了解MySQL的日志系统。MySQL在InnoDB存储引擎层面有两种日志:undolog日志和redolog日志,在MySQLServer层面也有binlog日志。让我们结合这些日志来说明ACID属性。原子性保证事务的原子性。事务的原子性是指事务中的所有操作要么完成,要么不完成,不会在中间某个环节结束。如果事务执行过程中出现错误,则会回滚(Rollback)到事务开始前的状态,就好像事务从未执行过一样。原子性由undolog保证,undolog记录了需要回滚的日志信息。也就是说,我们的事务还没有提交,需要回滚。那么事务回滚就是根据undolog来撤销执行成功的SQL。undolog说白了其实就是SQL的逆向执行。记录反向执行的SQL语句,回滚正向语句。Consistency一致性保证事务的一致性是指数据库在一个事务执行前后必须处于一致的状态。如果交易成功完成,系统中的所有更改将被正确应用,系统处于有效状态;如果事务发生错误,系统中的所有更改将自动回滚,系统将返回到原始状态。一致性是ACID的目的,也就是说只需要保证原子性、隔离性、持久性,数据的一致性自然就得到了保证。比如我们的ID在数据库中是唯一的。如果此时插入唯一ID,数据库会帮我们检查,告诉我们是否发生了主键冲突。如果主键冲突,则无法插入数据。另一部分是业务数据的一致性,需要程序代码来保证。比如转账这个场景,假设我要转100块钱,但实际上数据库里只有90块钱,那么此时转账应该不会成功。这种情况数据库无法保证,只能通过程序来保证。隔离性隔离保证事务的隔离性是指在并发环境下,当不同事务同时操作同一个数据时,每个事务都有自己完整的数据空间,并发事务所做的修改必须与任何其他并发事务保持一致.公司所做的修改是隔离的。一个事务查看数据更新时,数据的状态要么是另一个事务修改前的状态,要么是另一个事务修改后的状态,中间状态下事务不会查看数据。MySQL中的隔离性是通过MVCC多版本并发控制机制来保证的。它是事务隔离级别中最重要的概念。它是如何实现的?多版本并发控制:读取数据时,通过一个类似于快照的方式保存数据,这样读锁和写锁就不会冲突。不同交易的会话会看到自己特定版本的数据,即版本链。版本链的概念用于实现读写能力。同时。MVCC只工作在READCOMMITTED(提交读)和REPEATABLEREAD(可重复读)两种隔离级别下。另外两个隔离级别不兼容MVCC,因为READUNCOMMITTED(未提交读)总是读取最新的数据行,而不是符合当前事务版本的数据行,而ZERIALIZABLE(序列化)会锁定所有读取的行,所以MVCC是没有意义的。在MySQL的InnoDB下,聚集索引记录中有两个必要的隐藏列:trx_id:用于存储每次修改聚集索引记录时的事务ID,这个事务ID由MySQL分配。roll_pointer:每次修改聚集索引记录时,都会将旧版本写入undolog。这个roll_pointer存储了一个指针,指向上一版本聚集索引记录的位置。通过用于获取之前版本的记录信息(注意插入操作的undolog没有这个属性,因为它没有旧版本)。OK,了解了这些概念,我们再来看看MVCC。CommittedRead和RepeatableRead的区别在于它们生成ReadView的策略不同。MVCC就是这样一个由版本链+ReadView组成的概念。当我们掌握了版本链和ReadView这两个概念,我们就理解了MVCC。我们来看看这个ReadView。我们在开始事务时创建ReadView。ReadView维护当前活跃的事务ID,即未提交的正在进行的事务ID,排序生成数组访问数据,获取需要修改的记录中的事务ID(获取事务ID最大的记录),然后比较ReadView:如果获取到的事务ID在ReadView左边(比ReadView小),说明可以访问(左边表示事务已经提交)。如果获取到的事务ID在ReadView右侧(大于ReadView),或者在ReadView中,则表示无法访问,获取roll_pointer,与之前的版本进行比较(在右侧表示事务ReadView生成后出现,在ReadView中表示事务还没有提交)。committedread隔离级别下的事务会在每次查询开始时生成一个独立的ReadView,也就是说我每次select的时候都会重新生成一个ReadView,所以ReadView可能会不一样,也就是读取的数据会是会不一样;而repeatableread隔离级别在第一次读取时会生成一个ReadView,后续读取时会复用之前的ReadView,每次select查询都是一样的。这里我们发现两者的表现是不一样的。MySQL为了提高查询性能,默认使用repeatableread的隔离级别(原因之一)。这就是MySQL的MVCC。通过版本链,可以实现多个版本并发读写和写读,通过不同的ReadView生成策略实现不同的隔离级别。持久性保证了事务的持久性,这意味着只要事务成功结束,它对数据库所做的更新就必须永久保存。即使发生系统崩溃,重启数据库系统后,也可以将数据库恢复到事务成功结束时的状态。状态。持久化是指事务操作最终会持久化到数据库中。持久化是靠内存+重做日志来保证的。MySQL的InnoDB在修改数据的时候,会在内存中记录这次操作,同时记录redolog。当它宕机时,可以从重做日志中恢复数据。同时,我们都知道MySQLServer的主从同步是通过binlog实现的,从服务器使用binlog文件中的SQL再次执行,保证主服务器中的数据一致,而binlog和redolog都是存储表中的数据,可以用于数据恢复,那么如何保证binlog和redolog数据一致呢?下面是InnoDB下redolog的流程:将redolog写入磁盘,写入后事务进入prepare状态。如果前面的prepare成功了,会立即将binlog写入磁盘,然后将事务日志持久化到binlog中。如果binlog持久化成功,则事务进入commit状态(在redolog中写一条commit记录)。这意味着事务是否成功由两个方面来保证:首先是重做日志中是否有提交记录。如果有commit记录,那么binlog一定是持久化成功了,也就是事务成功了。此外,重做日志最终会被刷新,并且它的刷新将在系统空闲时执行,而不是在写入重做日志时立即执行。以上就是MySQL中如何保证数据库ACID的基本特性。ACID通过InnoDB的多个日志和MVCC来保证原子性、一致性、隔离性和持久性。最后再问大家一个问题:MySQL是先设计ACID特性的底层实现,还是先实现底层再总结ACID特性?
