zoukankan      html  css  js  c++  java
  • 数据库事务

    1、什么是数据库事务

    (1)使用场景

      比如下单,会操作订单表,资金表,物流表等等,这个时候需要让这些操作都在一个事务里面完成。当一个业务流程涉及多个表的操作的时候,我们希望它们要么是全部成功的,要么都不成功,这个时候就会启用事务。又比如行内转账的这种操作,如果把它简单地理解为一个账户的余额增加,另一个账户的余额减少的情况(当然实际上要比这复杂),那么这两个动作一定是同时成功或者同时失败的,否则就会造成银行的会计科目不平衡。

      可以通过注解或是配置切面的方式开启事务。

    (2)事务的定义

      维基百科的定义:事务是数据库管理系统(DBMS)执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。这里面有两个关键点,第一个,它是数据库最小的工作单元,是不可以再分的。第二个,它可能包含了一个或者一系列的DML语句,包括insert delete update。

    2、事务的四大特性——ACID

    (1)原子性,Atomicity。意味着对数据库的一系列操作要么全部成功,要么全部失败。在Innodb中,通过undo log来实现原子性,它记录了数据修改之前的值(逻辑日志),一旦发生异常,可以用undo log进行回滚。

    (2)一致性,consistent。指的是数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。比如主键必须是唯一的,字段长度符合要求。还有一个是用户自定义的完整性。

      比如说转账的这个场景,A账户余额减少1000,B账户余额只增加了500,这个时候因为两个操作都成功了,按照原子性的定义,它是满足原子性的, 但是它没有满足一致性,因为它导致了两个账户的不平衡。所以也违反了一致性。用户自定义的完整性通常要在代码中控制。

    (3)隔离性,Isolation。事务之间彼此是透明的,互不干扰的,通过这种方式,保证业务数据的一致性。通过MVCC和LBCC(基于锁)协调控制

    (4)持久性,Durable。对数据库的任意的操作,增删改,只要事务提交成功,那么结果就是永久性的,不会因为系统宕机或者重启了数据库的服务器,数据又恢复到原来的状态。

      持久性是通过redolog和doublewrite双写缓冲来实现的,操作数据的时候会先写到内存的buffer pool里面,同时记录redo log,如果在刷盘之前出现异常,在重启后就可以读取redo log的内容,写入到磁盘,保证数据的持久性。当然,恢复成功的前提是数据页本身没有被破坏,是完整的,这个通过双写缓冲

    (double write)保证。

    原子性,隔离性,持久性,最后都是为了实现一致性。

    3、数据库什么时候会出现事务?

      无论是在 Navicat 这种可视化工具里面去操作,还是在Java 代码里面通过API 去操作,加上@Transactional 注解或者 AOP 配置,最终都是发送一个指令到数据库去执行,Java的JDBC只不过是把这些命令封装起来了。

      开启事务的两种方式:

    *  自动提交

      InnoDB里面有一个autocommit的参数(默认开启),当执行更新操作(增删改)时,它会自动开启一个事务,并且自动提交。

    *  手动开启

      如果autocommit设置为关闭时,就需要手动开启和结束事务了。手动开启事务有两种方式:一种是用begin;一种是用start transaction。结束事务也有几种方式:一种是提交事务,commit;一种是回滚事务,rollback;还有就是当客户端连接断开时,事务也会结束。

    4、事务并发带来的问题——(数据库读一致性问题)

      当很多事务并发地去操作数据库的表或行时,如果没有上面提到的Isolation(隔离性),就会产生很多问题:

      (1)脏读——事务A读到了事务B未提交的数据

        例如:在转账场景中,第一个事务基于读取到的第二个事务未提交的余额进行了操作,但是第二个事务进行了回滚,这个时候就会导致数据不一致。

      (2)不可重复读——由于其他事务修改导致事务A两次读到的数据不一致

        例如:有两个事务,第一个事务通过id=1查询到了一条数据。然后在第二个事务里面执行了一个update操作,执行了update以后它通过一个commit提交了修改。然后第一个事务中再次查询id=1的数据,便读取到了其他事务已提交的数据导致前后两次读取的数据不一致。这种一个事务读取到了其他事务                  已提交的数据导致前后两次读取数据不一致的情况,叫做不可重复读。

      (3)幻读——由于其他事务插入导致事务A两次读到的数据不一致

        幻读和不可重复读的区别:不可重复读是修改或者删除导致的,幻读是插入导致的

      以上三个问题都是数据库读一致性问题,是在同一个事务中前后两次读取出现了不一致的情况。

    5、事务隔离级别

    (1)SQL92对四种隔离级别定义

      第一种叫:Read Uncommitted(未提交读),一个事务可以读取到其他事务未提交的数据,会出现脏读,它没有解决任何问题。

      第二种叫:Read Committed(已提交读),一个事务只能读取到其他事务已经提交的数据,不能读取到其他事务未提交的数据,它解决了脏读的问题,但是会出现不可重复读问题。

      第三种叫:Repeatable Read(可重复读),在同一个事务里面多次读取同样的数据结果是一样的,它解决了不可重复读问题,但是会出现幻读问题。

      第四种叫:Serializable(串行化),所有事务都是串行执行的,也就是数据的操作需要排队,所以不存在并发,也就不存在事务并发带来的问题。

    (2)InnoDB对隔离级别的支持 

             

       RC隔离级别:

        1)没有解决不可重复读和幻读的原因:与其MVCC快照数据创建时机有关,在该级别下,每次select都会创建新的数据快照。

        2)加锁可以解决不可重复读,但是不能解决幻读:通过加共享锁,可以保证数据不被修改,但是因为该级别只有记录锁,没有间隙锁和临键锁,所以不能解决幻读问题。

      RR隔离级别:

        因为MVCC在该级别下只在第一次select时创建数据快照,可以保证只能查找创建时间小于等于当前事务ID的数据,和删除时间大于当前事务ID的行(或未删除),所以能解决不可重读和幻读问题。

     6、解决读一致性问题的实现方案——隔离级别的实现  InnoDB支持的四个隔离级别和SQL92定义的基本一致,隔离级别越高,事务的并发度就越低。唯一的区别就在于,InnoDB在RR的级别就解决了幻读的问题。这个也是InnoDB默认使用RR作为事务隔离级别的原因,既保证了数据的一致性,又支持较高的并发度。

    *  LBCC——基于锁的并发控制

      第一种,既然要保证前后两次读取数据一致,那么读取数据的时候,锁定要操作的数据,不允许其他的事务修改就行了。这种方案叫做基于锁的并发控制Lock Based Concurrency Control(LBCC)。如果仅仅是基于锁来实现事务隔离,一个事务读取的时候不允许其他时候修改,那就意味着不持并发的读写操作,这样会极大地影响操作数据的效率。

    MVCC——多版本并发控制

      另一种解决方案,如果要让一个事务前后两次读取的数据保持一致,可以在修改数据的时候给它建立一个备份或者叫快照,后面再来读取这个快照就行了。

      MVCC的核心思想是: 可以查到在这个事务开始之前已经存在的数据,生成数据快照,在该事务中就以该快照为依据,即使该数据在后面被修改或者删除了也不会影响当前事务。在这个事务之后新增的数据,也是查不到的。

    数据快照何时创建?

      这里要说下数据快照的意思:数据快照是对全局数据设定的一个视图,并不是将数据拷贝一份。

      1)在RR隔离级别下,数据快照是在第一个select请求时创建的,也就是整个整个事务使用统一视图。

      2)在RC隔离级别下,每一个select请求都会创建一个新的数据快照,所以RC级别下普通select不能解决不可重复读问题,但是可以通过加锁的方式来解决。

    实现原理:利用undo log实现多版本,InnoDB为每行记录都实现了两个隐藏字段:

      DB_TRX_ID,6字节:插入或更新行的最后一个事务的事务ID,事务编号是自动递增的(可以把它理解为创建版本号,在数据新增或者修改为新数据的时候,记录当前事务ID)

      DB_ROLL_PTR,7字节:回滚指针(可以把它理解为删除版本号,数据被删除或记录为旧数据的时候,记录当前事务ID)

    原理如下图演示:    

       可以将事务ID理解为版本号。

      1)第一个事务,初始化数据。此时的数据,创建版本是当前事务ID,删除版本为空。

      2)第二个事务,执行第一次查询,读取到两条原始数据,此时事务ID是2。

      3)第三个事务,插入数据,此时的数据,多了一条tom,它的创建版本号是当前事务ID是3。

                                

      4)此时第二个事务执行第2次查询: 

         MVCC的查找规则:只能查找创建时间小于等于当前事务ID的数据,和删除时间大于当前事务ID的行(或未删除)。也就是不能查到在当前事务开始之后插入的数据,tom的创建ID大于2,所以还是只能查到qingshan和jack两条数据。

      5)第四个事务,删除了id=2 jack这条记录。此时的数据,jack这条记录的删除版本被记录为当前事务ID=4,其他数据不变。

             

      6)此时第二个事务执行第3次查询:

         根据MVCC查找规则,在当前事务开始之后删除的数据依然可以查出来,因为jack的删除版本为4,大于2,所以还是查询出qingshan和jack两条数据。

      7)第五个事务,执行更新操作,这个事务的ID是5。此时的数据,更新数据时,旧数据的删除版本被记录为当前事务ID=5,产生一条新数据

              

       8)此时第二个事务执行第4次查询:

          根据MVCC查找规则,因为更新后的数据 ‘盆鱼宴’ 创建版本大于 2,代表是在事务之后增加的,查不出来。而旧数据qingshan的删除版本大于2,代表是在事务之后删除的,可以查出来。

      通过以上的演示可以看到,通过版本号控制,无论其他事务是插入、修改、删除,第二个事务查询到的数据都没有变化。

    7、再来看一下update语句的执行流程

    update table_test set num = num+1 where id = 1;

      执行流程:

        (1)MySQL执行器先找InnoDB引擎读取id=1这一行的数据,InnoDB引擎直接用树查找主键id=1那条数据,如果数据所在页直接在内存中,那么直接返回,否则先从磁盘读取到缓存中再返回;

        (2)执行器获得数据后,执行num = num + 1,再调用InnoDB引擎接口写入新的数据;

        (3)InnoDB引擎将新的数据更新到内存中,再将这个更新操作记录到redo log中,此时redo log日志处于prepare状态,并通知执行器已就绪,随时可以提交事务;

        (4)MySQL执行器写入binlog日志并持久化到磁盘,并调用InnoDB引擎的事务提交接口,InnoDB引擎将刚刚写入的redo log日志的状态改为commit状态,至此,事务完成。

      从上面的流程可以看到redo log的写入分为两步:prepare、commit,这就是所谓的两阶段提交。而且两阶段提交一定是成功的写入了两个日志文件:redo log & binlog,只有这样事务才能提交,数据才能满足一致性原则。

  • 相关阅读:
    Qt安装
    Windows下查看进程的工具
    編譯 Boost 1.35.0 (Visual Studio 2005 (VC 8.0) + Windows XP
    boost1.35.0编译日志
    Linux
    Tool
    word cup
    IIS Study
    Oracle PL/SQL语言基础1 [初级] (http://www.cnmpa.com/edu/a1/8/892f4a44496ef382.asp)
    Psychology
  • 原文地址:https://www.cnblogs.com/jing-yi/p/12871955.html
Copyright © 2011-2022 走看看