Last Updated on

前言

数据库的四大特性是什么,为神马要满足这些特性,事务是怎么被创造并用来满足这些特性,了解数据库本质的一些概念,有助于更好的理解数据库。

当然,这也是面试中常见的基础问题,下面就来想来详细分析一下

正文

一 事务的起源

对于大部分程序员来说,他们的任务就是把现实世界的业务场景映射到数据库世界。那么就会碰到一个问题,比如典型的银行存款问题,假设银行为了保存用户的信息,建立了一个account表:

CREATE TABLE account (
    id INT NOT NULL AUTO_INCREMENT COMMENT '自增id',
    name VARCHAR(100) COMMENT '客户名称',
    balance INT COMMENT '余额',
    PRIMARY KEY (id)
) Engine=InnoDB CHARSET=utf8;

假设A和B他们都到银行开一个账户,他们在现实世界中拥有的资产就会体现在数据库世界的account表中。比如现在A有11元,B只2元,那么现实中的这个情况映射到数据库的account表就是这样:

+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | A      |      11 |
|  2 | B      |       2 |
+----+--------+---------+

这里A与B在银行里的资产的值,就是此时此刻现实世界的一个状态,随着时间流逝,A与B在银行间进行存取转账等操作,他们的余额就会发生变动,每一个操作就想相当于一次状态转换

那么对应数据库也就要进行进行变动来反映现实世界的映射,但是却不是那么容易,比如假设B急需用钱买彩票,找A借5元,然后A就去ATM,向B转账了5元,然后按下确定后,就走了。对于数据库的世界来说,就是在A的记录的余额上减去5元,B的余额加上5元,翻译成sql就是:

UPDATE account SET balance = balance - 5 WHERE id = 1;
UPDATE account SET balance = balance + 5 WHERE id = 2;

但是,这里就有个问题了,如果保证上面两条语句一定能执行成功呢? 如果中间服务器挂了,停电了,系统崩溃了,等等问题,都可能导致执行失败,或执行了一半,A钱少了,B钱却没增加等奇奇怪怪的状况。然后B因为没钱没买到彩票,本来是来中500万的,你说这个损失要谁来负责呢是吧。

要怎么让这样的情况不出现呢?这就要求数据库要符合现实世界的状态转换规则,而现实世界的状态转换规则则归纳为如下四条,这也是数据库的四大基本特性:

原子性(Atomicity)

现实世界中转账操作是一个不可分割的操作,也就是说要么压根儿就没转,要么转账成功,不能存在中间的状态,也就是转了一半的这种情况。设计数据库的大叔们把这种要么全做,要么全不做的规则称之为原子性。但是在现实世界中的一个不可分割的操作却可能对应着数据库世界若干条不同的操作,数据库中的一条操作也可能被分解成若干个步骤(比如先修改缓存页,之后再刷新到磁盘等),最要命的是在任何一个可能的时间都可能发生意想不到的错误(可能是数据库本身的错误,或者是操作系统错误,甚至是直接断电之类的)而使操作执行不下去。为了保证在数据库世界中某些操作的原子性,设计数据库的大叔需要费一些心机来保证如果在执行操作的过程中发生了错误,把已经做了的操作恢复成没执行之前的样子。

隔离性(Isolation)

现实世界中的两次状态转换应该是互不影响的,比如说A向B同时进行的两次金额为5元的转账(假设可以在两个ATM机上同时操作)。那么最后A的账户里肯定会少10元,B的账户里肯定多了10元。但是到对应的数据库世界中,事情又变的复杂了一些。为了简化问题,我们粗略的假设A向B转账5元的过程是由下边几个步骤组成的:

  • 步骤一:读取A账户的余额到变量中,这一步骤简写为read(A)
  • 步骤二:将A账户的余额减去转账金额,这一步骤简写为A = A - 5
  • 步骤三:将A账户修改过的余额写到磁盘里,这一步骤简写为write(A)
  • 步骤四:读取B账户的余额到变量中,这一步骤简写为read(B)
  • 步骤五:将B账户的余额加上转账金额,这一步骤简写为B = B + 5
  • 步骤六:将B账户修改过的余额写到磁盘里,这一步骤简写为write(B)

我们将A向B同时进行的两次转账操作分别称为T1T2,在现实世界中T1T2是应该没有关系的,可以先执行完T1,再执行T2,或者先执行完T2,再执行T1,对应的数据库操作就像这样:

image_1d1stskva1vp4a7f5kjdi7pf19.png-74.2kB

但是很不幸,真实的数据库中T1T2的操作可能交替执行,比如这样:

image_1d1sut47o5tk13ul4gb1qibuct2j.png-67.9kB

如果按照上图中的执行顺序来进行两次转账的话,最终A的账户里还剩6元钱,相当于只扣了5元钱,但是猫爷的账户里却成了12元钱,相当于多了10元钱,这银行岂不是要亏死了?

所以对于现实世界中状态转换对应的某些数据库操作来说,不仅要保证这些操作以原子性的方式执行完成,而且要保证其它的状态转换不会影响到本次状态转换,这个规则被称之为隔离性。这时设计数据库的大叔们就需要采取一些措施来让访问相同数据(上例中的A账户和B账户)的不同状态转换(上例中的T1T2)对应的数据库操作的执行顺序有一定规律,以满足隔离性

一致性(Consistency)

我们生活的这个世界存在着形形色色的约束,比如身份证号不能重复,性别只能是男或者女,高考的分数只能在0~750之间,人民币面值最大只能是100,红绿灯只有3种颜色,房价不能为负的,学生要听老师话,吧啦吧啦有点儿扯远了~ 只有符合这些约束的数据才是有效的,比如有个小孩儿跟你说他高考考了1000分,你一听就知道他胡扯呢。数据库世界只是现实世界的一个映射,现实世界中存在的约束当然也要在数据库世界中有所体现。在数据库从一个状态转换到另一个状态时,如果数据库中的数据全部符合现实世界中的约束规则(all defined rules),我们说这些数据就是一致的,或者说符合一致性的。

比如说,像上面的A转账给B5元后,A和B加起来的总额还是一样,没有发生变化,这符合现实世界的约束规则,也就是符合一致性

那么如何保证数据库中数据的一致性(就是符合所有现实世界的约束)呢?这其实靠两方面的努力:

  • 数据库本身能为我们保证一部分一致性需求(就是数据库自身可以保证一部分现实世界的约束永远有效,比如通过主键,唯一索引,不为空,外键等进行约束)。
  • 更多的一致性需求需要靠写业务代码的程序员自己保证。

持久性(Durability)

当现实世界的一个状态转换完成后,这个转换的结果将永久的保留,这个规则被设计数据库的大叔们称为持久性。比方说A向B转账,当ATM机提示转账成功了,就意味着这次账户的状态转换完成了,A就可以拔卡走人了。如果当A走掉之后,银行又把这次转账操作给撤销掉,恢复到没转账之前的样子,那B的500万不就没了,所以这个持久性是非常重要的。

当把现实世界的状态转换映射到数据库世界时,持久性意味着该转换对应的数据库操作所修改的数据都应该在磁盘上保留下来,不论之后发生了什么事故,本次转换造成的影响都不应该被丢失掉。

二 事务的概念

上边说的现实世界状态转换过程中需要遵守4个特性,设计数据库的大叔为了方便起见,把需要满足原子性隔离性一致性持久性的一个或多个数据库操作称之为一个事务(英文名是:transaction)。也就是使用事务来满足数据库的四大特性。

我们现在知道事务是一个抽象的概念,它其实对应着一个或多个数据库操作,设计数据库的大叔根据这些操作所执行的不同阶段把事务大致上划分成了这么几个状态:

  • 活动的(active): 事务对应的数据库操作正在执行过程中时,我们就说该事务处在活动的状态。
  • 部分提交的(partially committed): 当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我们就说该事务处在部分提交的状态。
  • 失败的(failed): 当事务处在活动的或者部分提交的状态时,可能遇到了某些错误(数据库自身的错误、操作系统错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的状态。
  • 中止的(aborted): 如果事务执行了半截而变为失败的状态,比如我们前边说的A向B转账的事务,当A账户的钱被扣除,但是B账户的钱没有增加时遇到了错误,从而当前事务处在了失败的状态,那么就需要把已经修改的A账户余额调整为未转账之前的金额,换句话说,就是要撤销失败事务对当前数据库造成的影响。书面一点的话,我们把这个撤销的过程称之为回滚。当回滚操作执行完毕时,也就是数据库恢复到了执行事务之前的状态,我们就说该事务处在了中止的状态。
  • 提交的(committed): 当一个处在部分提交的状态的事务将修改过的数据都同步到磁盘上之后,我们就可以说该事务处在了提交的状态。

随着事务对应的数据库操作执行到不同阶段,事务的状态也在不断变化,一个基本的状态转换图如下所示:

image_1d7bvq3401fpe1eum1d7qkop1f479.png-69.6kB

从图中大家也可以看出了,只有当事务处于提交的或者中止的状态时,一个事务的生命周期才算是结束了。对于已经提交的事务来说,该事务对数据库所做的修改将永久生效,对于处于中止状态的事务,该事务对数据库所做的所有修改都会被回滚到没执行该事务之前的状态。

结束

以上,就是数据库四大特性和事务的概念,而不同数据库,会有不同的实现,对于常用的Mysql来说,也是一样,下一篇,就来说下《Mysql 事务的隔离级别与MVCC

版权声明:本文为给予掘金博主「小孩子」的原创文章,进行的简化说明,详细的可以参考掘金小册