美文网首页
数据库事务基础

数据库事务基础

作者: 李眼镜 | 来源:发表于2018-03-03 20:20 被阅读0次

    一、为什么需要事务?

    举个例子:
    A 和 B 账户各有 50 元余额,现在 A 要给 B 转账 10 元,刨除掉具体的业务逻辑和工作流程,仅考虑数据库层面的操作:A 账户-10,B 账户+10 。
    如果没有事务,就只能让两个 update 操作串行执行:

    // 代码片段1
    run("update account set balance=balance-10 where account_id=A");
    run("update account set balance=balance+10 where account_id=B");
    
    图1

    如图1,对于这个转账操作,我们的期望是:

    • 代码片段1执行前,数据库数据:A 余额 50,B 余额 50。
    • 代码片段1执行后,数据库数据:A 余额 40,B 余额 60。

    但是,如果执行完 A-10 后,系统崩溃了,B+10 没能得到执行。则:

    • 代码片段1执行前,数据库数据:A 余额 50,B 余额50。
    • 代码片段1执行后,数据库数据:A 余额 40,B 余额50。
    • 在此场景下,对 A 来说,这个转账操作是成功了的,毕竟钱都扣掉了,但 B 却没有收到对应的款项,也就是说从 A 账户扣掉的 10 块钱凭空消失了。这在实际业务中是不可接受的。

    要解决这个问题,就必须有一套机制,来保证 A-10 和 B+10 这两个操作同时成功,或同时失败,以确保两个 update 操作执行前后的数据的一致性、正确性和完整性。

    这套机制,就是“事务”。

    二、事务定义

    数据库事务 是可以作为一个完整的逻辑工作单元来执行的不可分割的一组操作,这些操作要么全部执行,要么全部不执行。

    • 在关系型数据库中,事务可以是一条sql语句,也可以是多条sql语句。
    • 在此基础之上,为了保证这一组操作执行结果的正确性和有效性,事务又附加了一些条件,就是ACID了。

    ACID 的关注点和对事务的要求如下:


    ACID

    一致状态,是指数据处于一种语义上的有意义且正确的状态。
    隔离性还有其他的称呼,如:并发控制、可串行化、锁等。

    三、事务并发问题和隔离模式

    并发访问场景下,若没有采取必要的隔离措施,会存在一些读写问题,包括:

    • 3 类数据读问题:脏读、不可重复读和幻读。
    • 2 类数据更新问题:第一类丢失更新、第二类丢失更新。
    并发读写问题

    数据库提供不同级别的事务隔离模式,解决部分或全部上述的读写问题,SQL 规范定义了四种级别的隔离模式(级别由低到高):

    1. Read Uncommitted(读未提交):最低的隔离级别,什么都不需要做,一个事务可以读到另一个事务未提交的结果。所有的并发事务问题都会发生。
    2. Read Committed(读已提交):只有在事务提交后,其更新结果才会被其他事务看见。可以解决脏读问题。
    3. Repeated Read(可重复读):在一个事务中,对于同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。可以解决脏读、不可重复读。
    4. Serialization(串行化):事务串行化执行,最高隔离级别,牺牲系统的并发性,将所有事务串行执行。可以解决并发事务的所有问题。

    事务的隔离级别和数据库并发性是成反比的,隔离级别越高,并发性越低。

    四、ACID的实现技术

    ACID 的实现技术包括:并发控制、日志管理、备份恢复、锁管理、MVCC等内容。
    其中,并发控制和日志技术是核心。

    4.1 并发控制技术

    并发控制技术是实现原子性、一致性和隔离性的重要技术之一。并发控制的本质就是要对并发的事务实现正确又高效的调度。

    从实现思想的角度看,并发控制技术分两类:

    • 乐观并发控制,Optimistic Concurrency Control,OOC,事后检查。
    • 悲观并发控制,Pessimistic Concurrency Control,PCC,提前预防。

    从实现技术角度,并发控制机制有如下几类:

    1. 基于锁的并发控制机制

    基于锁的并发控制机制是最常见的一种并发控制机制,事务中可能涉及到的一些锁的概念如下图:


    1. 基于时间戳/数据版本的并发控制

    基于数据版本的并发访问控制,是通过给数据表加一个版本号或时间戳字段实现。

    • 当读取数据时,将 version字段的值一同读出,数据每更新一次,对此 version 值加一。
    • 当提交更新的时候,判断当前版本信息与第一次取出来的版本值大小,如果数据库表当前版本号与第一次取出来的 version 值相等,则予以更新,否则认为是过期数据,拒绝更新。

    基于时间戳的并发控制类似,把版本号换成时间戳就行了。

    1. 基于MVCC的并发控制

    MVCC(Multi-Version Concurrent Control),即多版本并发控制协议,是个行级锁的变种,它在普通读情况下避免了加锁操作,因此开销更低,同时在保证数据一致性的前提下,提供一种高并发的访问性能。

    虽然不同数据库或数据库引擎对 MVCC 的实现不同,但通常都是实现非阻塞读,对于写操作只锁定必要的行:

    • 第一种实现方式:将数据记录的多个版本保存在数据库中,当这些不同版本数据不再需要时,垃圾回收器回收这些记录。——PostgreSQL 和 Firebird/Interbase 采用。
    • 第二种实现方式:只在数据库保存最新版本的数据,但是会在使用undo时动态重构旧版本数据。——Oracle 和 MySQL/InnoDB 采用。

    4.2 日志技术

    数据库的日志可以大体分为3类:binlog、redo log、undo log。

    其中,binlog是Server层记录的日志, redo log 和 undo log 是数据库存储引擎层的日志。

    大部分关系型数据库系统是通过 redo log 和 undo log 来实现事务的原子性、一致性和持久性,同时也用于支持数据备份和恢复:

    • redo log,记录数据被修改后的值,可以用来恢复未写入 data file 的已成功事务更新的数据。redo log 又包括:redo log buffer 和 redo log file,一个写内存,一个写硬盘。
    • undo log,记录数据被修改前的值,可以用来在事务失败时进行 rollback。

    举个例子,假设 A=1且 B=2,某事务 T 要做 A=3 和 B=4,则

    1. 事务简化过程:
    1.start
    2.A=1——>undo log
    3.set A=3
    4.A=3——>redo log buffer
    5.B=2——>undo log
    6.set B=4
    7.B=4——>redo log buffer
    8.redo log buffer——>redo log file
    9.commit
    
    1. undo 和 redo 日志:
    // undo日志:
    <T,A,1>
    <T,B,2>
    // redo日志:
    <T,A,3>
    <T,B,4>
    
    1. 数据恢复(重做、撤销)
      若执行 9 时出现系统异常,则下次启动时可以通过 redo log 重做该事务。
      若执行 6 时出现异常,则可以通过 undo log 撤销已经做过的修改。
    2. undo/redo日志
      也有把 undo 和 redo 结合起来的做法,叫做 Undo/Redo 日志,在前面中的例子
      Undo/Redo 日志为:
    <T, A, 1, 3>
    <T, B, 2, 4>
    

    五、参考文章

    相关文章

      网友评论

          本文标题:数据库事务基础

          本文链接:https://www.haomeiwen.com/subject/jtbfxftx.html