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

    一、 事务

    事务(Transaction),即满足 ACID 特性的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。

    ACID

    • A 原子性 Atomicity
      事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。
      回滚可以用回滚日志来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。

    • C 一致性 Consistency
      数据库在事务执行前后都保持一致性状态。
      在一致性状态下,所有事务对一个数据的读取结果都是相同的。

    • I 隔离性 Isolation
      一个事务所做的修改在最终提交以前,对其它事务是不可见的。

    • D 持久性 Durability
      一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。
      使用重做日志来保证持久性。

    关系

    原子性 + 隔离性 -> 一致性

    原子性与隔离性共同在并发的情况下保证了一致性,即数据的正确性,最后正确的数据在数据库中持久化保存。

    因此,一致性是核心。

    一致性问题

    串行的情况下,隔离性自然保证,保证了基本的原子性,就可以保证一致性。

    并发的情况下,隔离性难以保证,由此引发了一系列不一致的问题:

    • 写问题

      • 更新丢失
        • 第一类:A 的撤销覆盖了 B 的修改,B 的更新丢失
        • 第二类:A 的修改覆盖了 B 的修改,B 的更新丢失
    • 读问题:

      • 读脏数据
        B 读取 A 已修改但未提交的数据,但随后 A 再次进行了修改(或回滚),B 读到了脏数据(中间数据,垃圾数据)。

      • 不可重复读
        A 修改/删除了数据,B 在 A 修改/删除数据前后两次读取,B 读取的内容不一致。
        与读脏数据的区别在于 A 是否已提交。

      • 幻影读
        A 新增了数据,B 在 A 新增数据前后两次读取,B 读取的内容不一致。
        与不可重复读的区别在于是同一条数据的修改,还是读到了不同的数据。

    解决这些一致性问题,重点在于解决隔离性问题,因此设置了 隔离级别

    二、 隔离级别

    1. 未提交读 READ_UNCOMMITTED:事务中的修改,即使没有提交,对其它事务也是可见的。

    2. 提交读 READ_COMMITTED:事务只能读取已经提交的事务所做的修改。

    3. 可重复读 REPEATABLE_READ:保证在同一个事务中多次读取同样数据的结果是一样的。

    4. 可串行化 SERIALIZABLE:强制事务串行执行。

    四种级别,并发性依次下降,安全性依次上升。

    对于 3 种读问题,4 种级别分别位于 4 个区间。即:

    级别 读脏数据 不可重复读 幻影读
    未提交读 允许 允许 允许
    提交读 禁止 允许 允许
    可重复读 禁止 禁止 允许
    可串行化 禁止 禁止 禁止

    隔离级别的具体实现是通过 来完成的。

    三、 锁

    读写锁

    • 读锁:共享锁,S 锁(Shared)
      加读锁后,其他事务只能进行读取,不能进行更新,即只能加读锁,不能加写锁。

    • 写锁:排他锁,X 锁(Exclusive)
      加写锁后,其他事务不能读取和更新,即不能加任何锁。

    读写锁实现隔离级别

    1. 未提交读

      • 读:不加锁
      • 写:行级共享锁

      B 可以读到 A 已修改未提交的数据。因此会造成读脏数据问题。

    2. 提交读

      • 读:行级共享锁,读完该行立即释放
      • 写:行级排他锁,事务结束才能释放

      A 修改数据,会对该行加锁,直至事务结束,B 不会读到未提交的数据。因此可以解决脏读问题。
      A 和 B 可以同时读取某行,保证该行不会被修改。但读取结束后,数据可以被修改。因此会造成可重复读问题。

    3. 可重复读

      • 读:行级共享锁,事务结束才能释放
      • 写:行级排他锁,事务结束才能释放

      读锁写锁都是直到事务结束才能释放。因此可以解决不可重复读问题。
      但无法解决幻影读问题:

      A B
      1 select * from users where age < 20
      2 insert into users(age) values(18)
      3 select * from users where age < 20

      (1) A 对 1 检索出的 n 条数据加共享锁,B 无法修改或删除这 n 条数据。
      (2) B 新增 1 条数据。
      (3) A 检索出 n + 1 条数据。

      A 两次读取得到的数据条数并不一致,出现了幻影读的现象。

    4. 可串行化

      • 读:表级共享锁,事务结束才能释放
      • 写:表级排他锁,事务结束才能释放

      解决幻影读的问题。
      但是效率极为低下。

    意向锁

    如果想为一张表加表锁,需要检查表的每一行是否有冲突的行锁,扫描全表消耗很大,为了解决这个问题,引入意向锁。

    意向锁是表级锁,是一种虚锁,仅代表有加锁的意图,同样分为 IS 和 IX。

    • 一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁;
    • 一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁。

    乐观锁

    乐观锁指 乐观 地认为操作不会导致冲突,在操作数据前不加锁,在修改后,再去判断是否冲突。

    数据库本身不提供乐观锁,需要程序员自己实现,通常可以利用 CAS 的思想。

    1. 为表添加版本字段,每次操作数据时版本号 + 1
    2. 取出数据时,记录版本号
    3. 计算数据后,再次取出版本号,判断是否一致
      • 一致:这段时间内,数据没有被修改过,将版本号 + 1 后放回数据
      • 不同:这段时间内,数据被修改过,重新取出数据,即执行 2

    与乐观锁相对应,悲观锁 悲观 地认为此次操作会出现数据冲突,所以在进行操作前就加锁,保证操作的正确性。

    悲观锁直接由数据库提供,如共享锁,排他锁,都是悲观锁的实现。

    四、 多版本并发控制

    隔离级别可以使用锁来实现,但效率不高。

    多版本并发控制( MVCC )用于实现提交读和可重复读这两种隔离级别。

    未提交读隔离级别总是读取最新的数据行,无需使用MVCC。
    可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。

    MVCC是通过在每行记录后面保存两个隐藏的列来实现的。

    • 行的创建时间
    • 行的过期时间(或删除时间)

    这两列用系统版本号表示。每开始一个新的事务, 系统版本号 都会自动递增。
    事务开始时刻的系统版本号会作为 事务版本号,用来和查询到的每行记录的版本号进行比较。

    实现可重复读

    SELECT

    • 只查找版本小于或等于当前事务版本的数据行:确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
    • 只查找删除版本大于当前事务版本或未定义删除版本的数据行:确保事务读取到的行,在事务开始之前未被删除。

    INSERT

    新插入的每一行保存当前系统版本号作为行版本号。

    DELETE

    删除的每一行保存当前系统版本号作为行删除标识。

    UPDATE

    • 插入一行新记录,保存当前系统版本号作为行版本号。
    • 同时保存当前系统版本号到原来的行作为行删除标识。

    相当于 INSERT + DELETE。




    参考资料:
    事务四大特征:原子性,一致性,隔离性和持久性(ACID)
    CyC2018/CS-Notes

  • 相关阅读:
    sql测试
    sql时间和日期函数
    递归算法
    冒泡排序
    Ajax
    省市区下拉框三级联动
    Repeater用法
    WIndows form Linq多表联合
    C# 递归算羊
    C# 定义一个学生的结构体,输入学生信息,学号,姓名,身高,按身高排序输出
  • 原文地址:https://www.cnblogs.com/JL916/p/12650421.html
Copyright © 2011-2022 走看看