3.1 ACID
ACID
什么是ACID?
其实针对科班的学生来说,这是一个很熟知的概念,因为在数据库概述这门课中就会被提到。但是教科书上的知识,对我们来说还是太过浅薄,所以在下面,我们需要重新认识一下这几个概念。
Wiki 中的 ACID
ACID,是指数据库管理系统(DBMS)在写入或更新资料的过程中,为保证事务(transaction)是正确可靠的,所必须具备的四个特性:原子性(atomicity,或称不可分割性)、一致性(consistency)、隔离性(isolation,又称独立性)、持久性(durability)。在数据库系统中,一个事务是指:由一系列数据库操作组成的一个完整的逻辑过程。例如银行转帐,从原账户扣除金额,以及向目标账户添加金额,这两个数据库操作的总和,构成一个完整的逻辑过程,不可拆分。这个过程被称为一个事务,具有ACID特性。
原子性 - atomicity
一个事务中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。让我们以一个银行转账的经典栗子来描述一下,A 转账给 B 和 B 收到 A 的转帐,这个两个过程,是一个原子操作。A 账户出款,A 账户的金额减少,B 账户收款,B 账户的金额变多,这两个步骤一定要么都做,要么都不做。
一致性 - consistency
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。还是以刚刚银行转账的栗子来说明,在转账前,A、B 两个账户的总额等于转账后A、B 两个账户的总额,数据的整体是完整的,不会丢失。
隔离性 - isolation
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读、提交读、可重复读和串行化。事务之间的执行是有顺序的,比如等待前一个事务执行完成,才会执行后一个事务。让我们来看一个错误的场景,A 账户有1000块钱,B账户有0块钱,C账户有0块钱,这个时候 A 向 B 发起了转账 1000 的请求,但是这个请求还没有结束,即 A 账户的余额还没有更新,这个时候又发起了一笔100的转账从A到C,那么这个时候,就会出现数据不一致的问题。第一个转账操作的结果会让A账户余额变为 0,而第二个请求的结果会让A账户的余额变为900。这样子就出现了我们所说的“资损”。
持久性 - durability
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
并发事务下面临的问题
下面所需要用到的测试表 create table animal ( id int null, name varchar(255) null, age int null ); insert into animal (id, name, age) values (1, 'dog', 10), (2, 'cat', 15);
脏读
当一个事务允许读取另外一个事务修改但未提交的数据时,就可能发生脏读。当事务1进行查询时,第一次查询到 id 为1的数据的年龄是10,然后事务2进行了update操作,但是我们知道,事务是分两个阶段的begin和 commit/rollback,此时事务2的update操作还没有commit,这个时候事务1又进行了刚才的查询操作,读到了事务2未提交的修改,这就是脏读。
不可重复读
在一次事务中,当一行数据获取两遍得到不同的结果表示发生了不可重复读。事务1在进行第一次查询的时候,得到的结果是10,但是此时,事务1未提交。此时事务2进行了数据修改的操作,且提交。然后事务1重复进行了查询操作,但是对比第一次事务1的查询结果,在一个事务中,两次的查询结果是不一致的。
和脏读不一样的是脏读会读取到其他事务未提交的操作,但是不可重复读读到的是其他事务提交的操作。
幻读
在事务执行过程中,当两个完全相同的查询语句执行得到不同的结果集。这种现象称为“幻读”。幻读是不可重复读的一个特殊场景。
其实不难看出,幻读针对的是不可重复读中的插入场景。
事务隔离级别
未提交读
未提交读是最低的隔离级别。可能会读到其他事务未提交的数据,也就是说允许“脏读”。
提交读
一个事务只能读取到另一个事务已经提交的数据。但是这个隔离级别下,会出现不可重复读的场景。
可重复读
故名思义,这个隔离级别下,不会存在不可重复读的现象。在同一个事务中的select都是事务开始时的状态,但是避免不了幻读的。
但是 mysql 的 innodb 引擎通过 mvvc 和 next-key locks 解决了这个幻读的问题。因为关于这个case的篇幅太长,所需要拓展的知识太多,所以读者可以自行去了解。
串行化
每次读写都需要获取锁,读写操作会相互阻塞。可以理解成指令变成一个队列按顺序去执行,从而避免了脏读、不可重读复读和幻读问题。