0%

数据库事务中“一致性”的理解和例子

事务必须具有4的个基本特性:原子性、一致性、隔离性、持久性。其中一致性(Consistency)的概念难以从字面意思去理解。

一致性的定义

  • 百度百科-一致性

    一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。

  • 百度百科-事务一致性:

    一个或多个事务执行后,原来一致的数据和数据库仍然是一致的。它主要涉及事务的原子性。

  • 维基百科-一致性(数据库)

    一致性是数据库系统的一项要求:任何数据库事务修改数据必须满足定义好的规则,包括数据完整性(约束)、级联回滚、触发器等。

我对一致性的理解

“一致”是指数据库中的数据是正确的,不存在矛盾。事务的一致性是指事务执行前后,数据都是正确的,不存在矛盾。如果执行后数据是矛盾的,事务就会回滚到执行前的状态(执行前是一致的)。

满足一致性的例子

  • 学生表中的学号是唯一的。
  • 账户的余额减少了,账单中要有对应的扣款记录,且减少的金额和账单的扣款金额一致。
  • 一篇文章浏览量为100次,则浏览记录表有该文章的100条浏览记录。
  • 发布文章的用户ID是100,则用户表中存在ID为100的用户。

不满足一致性的例子

  • 学生表中有重复的学号。
  • 转账成功了,但是付款的人余额没扣,或者收款的人余额没有增加。
  • 付款多次,只扣款1次(或者付款1次,扣款多次)。
  • 发布文章的用户ID是100,但用户表中没有ID=100的用户。
  • 文章的发布时间是空的。
  • 用户的年龄是负数。
  • 用户的年龄是几个字母。

数据库如何实现一致性?

  • 唯一索引

    给学号添加唯一索引,创建学生信息时,如果已存在相同学号,则创建失败。

  • 外键约束

    给文章表的用户ID创建外键,创建文章时,如果不存在对应的用户,则创建失败;删除用户时,如果文章表有该用户的文章,则无法删除用户,或者将用户与文章一起删除。

  • 触发器

    插入文章的浏览记录时,使用触发器去更新对应文章的浏览量(保证每增加一条浏览记录,对应文章浏览量+1)。

  • 指定数据类型

    设置年龄的类型为非负整数,则年龄为负数、字母时保存失败。

  • 设置默认值

    如果没有指定文章的发布时间,则默认以文章记录的插入时间作为发布时间。

  • 设置字段不能为空

    设置文章的发布时间NOT NULL,没指定发布时间,或发布时间为NULL时,文章创建失败。

  • 事务的原子性
    事务的原子性是指同一个事务中的操作,要么都成功,要么都失败。以A向B转账100元转账为例,需要执行以下2个操作:

    1.将A的余额减少100元;
    2.将B的余额增加100元。

    这两个操作要么都成功,要么都失败,否则最后账目就会对不上(破坏一致性)。事务的原子性可以保证以上两个操作同时成功,或者同时失败。

  • 事务的隔离性
    在并发的情况下,只靠事务的原子性并不能保证一致性。举个例子,A有100元,A同时向B发起两笔转账请求,转账金额分别是99元和1元。

    时间 请求1 请求2
    1 查询A余额(100元)
    2 查询A余额(100元)
    3 转账99元,修改A余额为1元
    4 B余额增加99元
    5 转账1元,修改A余额为99元
    6 B余额增加1元

    两笔转账都执行成功了,理论上A的余额为0,但实际上A的余额被请求2修改为99元,数据的一致性被破坏了!

    再举一个例子,A同时发起两笔转账,其中一笔因为某些原因操作失败,事务回滚,而另外一笔转账执行成功。执行时间线如下:

    时间 请求1 请求2
    1 查询A余额(100元)
    2 转账99元,修改A余额为1元
    3 查询余额(1元)
    4 发生错误,事务回滚,A余额被改回100
    5 转账1元,修改A余额为0
    B余额增加1元

    在这个场景下,A实际转账成功了1元,但是A的余额最终为0,数据的一致性又被破坏了!

    事务的隔离性可以在多个事务并发执行的情况下,通过加锁等方式,保证数据的一致性。