ACID特性与事务的隔离级别

  事务的 ACID 特性是保证事务正确执行的必要因素,这四个特性不仅是数据库对事务安全的保障机制,拓展开来,也为我们在多线程编程环境下,提供了保证任务正确执行的参考。数据库事务是一类特殊的多道编程任务,之所以特殊是因为这些任务对周遭环境是存在函数副作用的,因为它们改变了任务外定义的共享资源的状态(数据库中存储的数据)。

  原子性(Atomicity)
  原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

  在多道编程中,我们所说的原子性大多是强调,整个操作不可分割,是线程排它的。但事务的原子性是强调对共享数据的副作用,这些副作用要么全部发生,要么都不发生。定义一个事务就是定义了一个原子操作,如果原子操作没有被完成,那么任务不应该对周围环境产生副作用,因为这些副作用是错误的。保证复杂操作的逻辑完整性,并避免对外产生错误的任务副作用,数据库提供了 提交/回滚 机制,这为我们处理类似的场景提供了参考。

  一致性(Consistency)
  事务前后数据的完整性必须保持一致。这里并不是指 CAP 定理中的数据一致性,CAP 定理中的数据一致性是指集群环境下各节点中数据的一致。而 ACID 中的一致性是指事务的执行需要符合我们对事务逻辑的定义,保证逻辑的完整性。

  最简单的例子就是转账,A 账户减钱,B 账户就要加钱,我们需要保证 AB 账户的总金额是不变的。
  隔离性(Isolation)
  事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。

  多道编程中最通用的一点,保证共享变量的数据安全,围绕共享数据定义多线程间动作的互斥关系,防止数据污染。

  多道编程下最简单的保证隔离性的例子便是 a++,因为该动作不是原子操作。修改操作与其前置动作 - 读取操作存在数据依赖,为了保证程序的正确运行,它们必须是不可分割且线程排它的。保证两个动作执行的过程中 a 的值不会被其它线程改变。但事务的情况比这复杂的多,因为事务涉及的共享数据更多,而且一个事务内对数据的操作间可能存在数据依赖关系,为了效率,我们不能一棍子打死,将整个事务与其它事务的整体进行互斥,所以要以更细的力度处理事务中对数据操作的互斥关系。

  以事务为单位,多道编程下可能存在的情况有:

  脏读:一个事务可以读到另一个事务没有提交的数据。

  幻读:一个事务对表中所有数据修改时,另一个事务插入了一条新数据,导致第一个事务发现还有一条数据没有修改。

  不可重复读:在一个事务中反复读取同一个数据,会有不同的结果,因为在两次读取的间隙,有其它事务修改了这条数据。

  为此,大部分数据库提供了四种事务的隔离级别:

  读未提交:查询操作不加锁,脏读幻读和不可重复读都会发生。

  读已提交:最简单的实现方式便是查询操作对数据进行加锁,但这样会导致并发性能急剧下降,尤其是查询操作非常多的情况下。作为补偿,目前常用 “快照读” 的机制来避免脏读。这样可以在不加锁的情况下避免脏读,但不能避免不可重复读与幻读。这是 SQLSERVER 的默认隔离级别。

  可重复读:针对不可重复读的问题提供的隔离级别,事务一旦开启,不允许其它事务进行数据修改操作,这样便可以避免可重复读的问题。但是因为没有禁止插入与删除操作,所以无法避免幻读。这是 Mysql 的默认隔离级别。

  串行化:安全级别最高,但效率奇差。事务一个一个的串行化执行,这样就不会出现脏读/幻读/不可重复读的并发问题。

  数据库在处理事务的并发问题时,将锁的粒度分的很细。表锁/行锁等,而且锁的种类非常多,分别针对 增/删/查/改 四种操作。数据库的隔离级别,便是四种操作在以事务为单位的情况下,对可能造成的并发问题的总结以及它们的解决方案。如果日常开发中会经常遇到设计线程同步互斥关系场景的话,一定会被如此条理清晰的总结和设计惊艳到。多道编程最完善的设计一定是考虑到所有可能存在的问题,后根据问题针对性的制定最细粒度的解决方案,问题明确的力度越细,解决方案的效率越高。

  持久性(Durability)
  持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

  

posted @ 2020-04-05 23:53  牛有肉  阅读(217)  评论(0编辑  收藏  举报