1. 什么是事务?
事务就是一组SQL语句,作为一个工作单元以原子方式进行处理。如果数据库引擎能够成功地对数据库应用整组语句,那么就执行该组语句。如果其中有任何一条语句因为崩溃或其他原因无法执行,那么整组语句都不执行。也就是说,作为事务的一组语句,要么全部执行成功,要么全部执行失败。
2. 什么是ACID?
对于事务,ACID是必不可少的四大特性:
- Atomicity(原子性):一个事务必须被视为一个不可分割的工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚。对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。
- Consistency(一致性):数据库总是从一个一致性状态转换到下一个一致性状态。如果事务最终没有提交,该事务所做的任何修改都不会被保存到数据库中。
- Isolation(隔离性):通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的,这就是隔离性带来的结果。
- Durability(持久性):一旦提交,事务所做的修改就会被永久保存到数据库中。此时即使系统崩溃,数据也不会丢失。持久性是一个有点模糊的概念,实际上持久性也分很多不同的级别。有些持久性策略能够提供非常强的安全保障,而有些则未必。而且不可能有100%的持久性保障(如果数据库本身就能做到真正的持久性,那么备份又怎么能增加持久性呢?)。
3. 如何实现ACID?
-
大多数场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务称之为本地事务(Local Transaction)。本地事务的ACID特性是数据库直接提供支持。
-
以MySQL 的InnoDB 存储引擎为例,通过日志和锁来保证的事务的ACID特性,具体如下:
- 通过 Undo Log 来保障事务的Atomicity(原子性);
- 通过数据库锁的机制来保障事务的Isolation(隔离性);
- 通过 Redo Log 来保障事务的Durability(持久性);
- 通过上述三项来共同保障事务的Consistency(一致性);
-
Undo Log 如何保障事务的原子性呢?在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为 Undo Log),然后进行数据的修改。如果出现了错误或者用户执行了 Rollback 语句,系统可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态。
-
Redo Log如何保障事务的持久性呢?Redo Log 记录的是新数据的备份(和 Undo Log 相反)。在事务提交前,只要将 Redo Log 持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是 Redo Log 已经持久化。系统可以根据 Redo Log 的内容,将所有数据恢复到崩溃之前的状态。
4. 什么是脏读、幻读、不可重复读?
- 脏读(Dirty Read):在一个事务处理过程里读取了另一个未提交的事务中的数据。
- 幻读(Phantom Read):当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(phantom row)。
- 不可重复读(Unrepeatable Read):同一事务中两次执行相同语句,可能会看到不同的数据结果,因为可能有其他事务修改了该事务要读取的数据。
5. 事务的隔离级别
对于Isolation(隔离性),有四种不同的隔离级别:
- Read Uncommitted(未提交读):一个事务在进行中,尚未提交,其所做的改变已经可以被其他事务读到,相当于没有隔离,会导致脏读。
- Read Commited(提交读):大多数数据库系统的默认隔离级别是Read Commited(但MySQL不是)。Read Commited满足前面提到的隔离性的简单定义:一个事务可以看到其他事务在它开始之后提交的修改,但在该事务提交之前,其所做的任何修改对其他事务都是不可见的。这个级别仍然允许不可重复读。
- Repeatable Read(可重复读):Repeatable Read解决了Read Commited级别的不可重复读问题,保证了在同一个事务中多次读取相同行数据的结果是一样的。但是理论上,可重复读隔离级别还是无法解决幻读(phantom read)的问题。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。Repeatable Read是MySQL默认的事务隔离级别。
- Serializable(可串行化):Serializable是最高的隔离级别。该级别通过强制事务按序执行,使不同事务之间不可能产生冲突,从而解决了前面说的幻读问题。简单来说,Serializable会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中很少用到这个隔离级别,除非需要严格确保数据安全且可以接受并发性能下降的结果。
6. 多版本并发控制(MultiVersion Concurrency Control)
- 可以认为MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。根据其实现方式,不仅实现了非阻塞的读操作,写操作也只锁定必要的行。
- MVCC的工作原理是使用数据在某个时间点的快照来实现的。这意味着,无论事务运行多长时间,都可以看到数据的一致视图,也意味着不同的事务可以在同一时间看到同一张表中的不同数据。
- InnoDB的MVCC是通过在每行记录后面增加两个隐藏的列来实现的,一列用于保存记录的创建时间,一列用于保存记录的删除时间。当然,这里的时间并不是真实的时间,而是系统版本号或事务ID。每开始一个新的事务,系统版本号就会递增。事务开始时的版本号将会作为该事务的版本号。
- 在SELECT时,InnoDB会将要读取的记录的系统版本号与当前执行的事务的版本号进行对比,如果将要读取的记录的系统版本号小于等于当前执行的事务的版本号,才能说明该记录应可见,能返回作为查询结果。除此之外,要读取的记录的删除版本号必须不存在或者小于当前事务,说明该记录未被删除或者在该事务结束后才应被删除,因此可以作为查询结果。
- 在INSERT时,InnoDB为新插入的记录保存当前事务版本号作为版本号;
- 在DELETE时,InnoDB为新删除的记录保存当前事务版本号作为删除版本号;
- 在UPDATE时,InnoDB会插入一条新纪录,并保存当前事务版本号作为该新纪录的版本号,同时会删除一条旧记录,为删除的旧记录保存当前事务版本号作为删除版本号;
- 保存这两个额外的版本号,使得大部分读操作都可以不用加锁,使得读取数据很简单,且具有很好的性能,也能够保证只会读取到符合标准的记录。但是不足之处在于每条记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。
- MVCC只在Read Commited(提交读)和Repeatable Read(可重复读)两个隔离级别下工作,因为Read Uncommitted(未提交读)总是读取最新行,而Serializable(可串行化)需要对所有的读操作加锁。