事务

场景

想象一个电商分享APP中的用户注册过程,添加用户->为用户分配邀请码->为用户分配跟单标识。
添加用户、分配邀请码、分配跟单标识这三个数据库动作必须同时完成注册才能成功,只要有一个失败注册就是失败,而且可能会造成脏乱数据,因此需要使用事务,有一个失败就必须回滚保证数据库数据正确。

定义

计算机中访问并可能更新数据库中各数据项的程序执行单元。

属性

事务具有ACID四个属性。

  • A(atomicity)原子性
    最小的程序执行单元。要么都做,要么不做。事务只有2种结果,所有的数据库操作全部完成并提交或发生错误撤销所有操作到事务开始时的状态。
  • C(consistency)一致性
    使数据库从一个一致性状态变为另一个一致性状态,与原子性是密切相关的。
  • I(isolation)隔离性
    一个事务的执行不能被其它事务干扰。即一个事务内部的操作和使用的数据对并发的其它事务是隔离的。
  • D(durability)持久性
    一个事务一旦提交它对数据库的修改就是永久性的。

传播机制

以Spring事务传播机制为例,Spring在TransactionDefinition接口中定义了7个事务传播行为:

  • propagation_requierd
    如果当前没有事务就新建一个事务,如果已经存在一个事务就加入这个事务中。这是最常用的方式。
  • propagation_supports
    支持当前事务,如果没有当前事务就以非事务方法执行。
  • propagation_mandatory
    使用当前事务,没有则抛出异常。
  • propagation_requierd_new
    新建事务,如果当前存在事务则把当前事务挂起。
  • propagation_not_supported
    以非事务方式执行,如果存在当前事务,就把当前事务挂起。
  • propagation_never
    以非事务方式执行,如果当前事务存在则抛出异常。
  • propagation_nested
    如果当前存在事务则在嵌套事务内执行,如果不存在则执行类似propagation_requierd的操作。

隔离级别

SQL标准定义了4种隔离级别,包括一些具体规则来限定事务内外的哪些改变是可见的哪些改变是不可见的。一般低隔离级别支持更高的并发,并拥有更低的系统开销。

  • (1)Read Uncommitted(读未提交-脏读)
    字面理解就是读到了别人未提交的数据。A事务执行过程种读到了B事务修改但未提交的数据,产生脏读。这个级别很少实际应用,因为它的性能比其它级别好不了多少。
  • (2)Read Committed(读已提交-不可重复读)
    大多数数据库的默认隔离级别,A事务执行过程种B事务更改了数据M,A读取M时是B更改后的。
  • (3)Repeatable Read(可重复读-幻读)
    这是MYSQL的默认隔离级别,它确保同一事务的多个实例在并发读取数据时会看到同样的行。理论上会造成幻读,A事务在读取数据时B事务插入了新的行,A事务再读取数据时发现有新的幻影行。InnoDB和FaIcon通过MVCC(Multiversion concurrency control)机制解决了这个问题。
  • (4)Serializable(串行化)
    这是最高的隔离级别,强制事务排序,使之不可能相互冲突。但可能会造成大量的超时现象和锁竞争。

MySQL隔离级别测试

  • (1)、Read Uncommitted
    首先开启2个命令行客户端连接本地的mysql服务。两个客户端此处命名左边为A,右边为B。
    connect
    设置A的隔离级别为Read Uncommitted。
    a_isolation_ru
    启动事务,查询User。
    a_startt_ru
    B开启事务,更改jiay的age为10但不提交。A查询当前数据,数据发生改变。
    a_result_ru
    B回滚事务,A查看当前数据,数据还原。
    rollback_ru
    由此可以看出读未提交会造成脏读。
  • (2)、Read Committed
    还是2个客户端,左边A,右边B。
    设置A的隔离级别Read Committed,A、B分别开启事务,查看User数据。
    2_startt_rc
    B更改jiay的age为10但不提交。A查询当前数据,数据没有发生改变。不会出现脏读。
    2_result_rc
    B提交事务,A查询当前数据,数据发生改变。B事务提交的数据被A事务读到了,这就会出现不可重复读。
    2_result2_rc
    由此可以看出读已提交解决了脏读问题,但还会有不可重复读的问题。
  • (3)、Repeatable Read
    还是2个客户端,左边A,右边B。
    A、B分别开启事务,查看User数据。
    3_startt_rr
    B更改jiay的age为1但不提交。A查询当前数据,数据没有发生改变。不会出现脏读。
    3_result1_rr
    B提交事务,A查询当前数据,数据没有发生改变。不会出现不可重复读。
    3_result2_rr
    B插入一个用户jone,主键4ba03142-5d1b-11ea-b76b-e86a647ee7f5,A查询当前数据,数据依然没有发生改变。没有查询到最新数据出现幻读。
    3_result3_rr
  • 幻读的影响
    A、B查看User数据,B的结果是有jone的。
    3_result4_rr
    此时若是A事务中要新增一个记录到User,主键也是4ba03142-5d1b-11ea-b76b-e86a647ee7f5就会出现问题,MySQL中很多情况主键是自增就会更容易抛出异常,主键冲突,怎么会这样,是幻觉吗?这就是幻读。
    3_result5_rr
    A提交事务后查询User。正常代码中这次注册就失败了,这里为了明确状况,提交事务后再查询结果确实有一个jone用户主键冲突。
    3_result6_rr
  • (4)、Serializable
    依然2个客户端,左边A,右边B。
    设置A的隔离级别Serializable,A、B分别开启事务,查看User数据,B事务尝试更改jiay的age为30,B进入等待状态。
    4_result1_s
    这是因为A事务未提交,B进入等待,等待超时后抛出异常。
    4_result2_s
    A提交事务,B再次尝试更改jiay的age为30,执行成功。
    4_result3_s
    A查询User数据,数据没有发生变化,这是可重复读机制保证的结果,这正符合MySQL的隔离级别设计。
    4_result4_s
    B提交事务,A再查询,数据是更新后的最新数据。
    4_result5_s
    serializable会锁定对应的数据表格,因而会有效率的问题。

欢迎来访

  • 有问题欢迎留言或加交流qq:825121848
  • 转载请注明出处
  • 请小编喝茶~
Last Updated: 4/16/2022, 11:05:56 AM