DDIA学习笔记4——chapter7:事务(Transaction)

DDIA_Chapter7  学习笔记

 

ACID:原子性(Atomicity)一致性(Consistency)隔离性(Isolation)持久性(Durability)

原子性(Atomicity):

一般来说,原子是指不能分解成小部分的东西。在多线程编程中,如果一个线程执行一个原子操作,这意味着另一个线程无法看到该操作的一半结果。系统只有处于操作之前或操作之后的状态,没有介于两者之间的状态。原子性保证了:如果事务被中止(abort),应用程序可以确定它没有改变任何东西,所以可以安全地重试。也保证了事务提交之前,其他用户无法看到该事务的中间状态。

原子性保证了事务中,对数据库的操作要么全部成功要么全部失败。

一致性(Consistency):

对数据的一组特定陈述必须始终成立。即不变量(invariants)。例如,在会计系统中,所有账户整体上必须借贷相抵。如果一个事务开始于一个满足这些不变量的有效数据库,且在事务处理期间的任何写入操作都保持这种有效性,那么可以确定,不变量总是满足的。

隔离性(Isolation):

4种事务隔离级别:读未提交、读已提交、可重复读、串行化。

隔离性保证了同事执行的事务之间不互相干扰。但是数据库提供了上述4个隔离级别,只有串行化级别能完全保证事务事件不干扰。

读未提交:

事务能查询到其他事务未提交的结果,代表完全不存在事务的隔离。

读已提交:

事务只能查询到其他事务已经提交的结果,保证了没有脏读。脏读——读取到其他事务没有提交的数据;脏写——覆盖了其他事务没有提交的数据。

读已提交的实现方法:写操作——行锁,其他事务对数据进行写操作时,必须等到持有该数据行锁的事务提交后才能进行,保证了写入操作的顺序性;读操作——行锁不适合读操作,因为一个长时间运行的写入事务会迫使许多只读事务等到这个慢写入事务完成,这会损失只读事务的响应时间。因此,对于写入的每个对象,数据库都会记住旧的已提交值,和由当前持有写入锁的事务设置的新值。 当事务正在进行时,任何其他读取对象的事务都会拿到旧值。 只有当新值提交后,事务才会切换到读取新值。

由于读已提交级别的事务隔离机制会在事务执行过程中读取到其他事务已经提交的结果,因此也被成为不可重复读级别——重复读取某一数据的结果可能不同,因为有其他的事务对其进行了更新。

可重复读:

针对不可重复读的问题,数据库提出了快照隔离(Snapshot Isolation)作为该问题的解决方案。事务开始时,拉取数据库中在该时间点的数据作为快照(Snapshot),在整个事务执行过程中的读操作从快照中读取数据,即使这些数据随后被另一个事务更改,每个事务也只能看到该特定时间点的旧数据,保证了每次读取结果的一致性。而写操作利用行锁防止写冲突,只有写入数据的事务提交之后,另一个事务才能对该数据执行写入操作。

yb:快照隔离,只针对读请求。写请求还是和读已提交一样的机制。有点类似Copy-On-Write的思想了,首先每个事务Copy(并不是真的复制了一份数据)出了当前数据的快照,然后在当前快照上进行修改,其他事务读取的时候仍然是读取的原数据,当当前事务Write完之后再写入原数据。

MVCC:多版本并发控制

由于在可重复读隔离级别下,每个事务在创建时都会产生当前时间点的数据的一份Snapshot。因此,为了实现快照隔离,数据库必须保留一个对象的几个不同的提交版本,因为各种正在进行的事务可能需要看到数据库在不同快照中的状态。因为它维护着多个版本的对象,所以这种技术被称为多版本并发控制(MVCC, multi-version concurrentcy control)

如果一个数据库只需要提供读已提交的隔离级别,那么保留一个对象的两个版本就足够了:提交的版本被覆盖但尚未提交的版本。支持快照隔离的存储引擎通常也使用MVCC来实现读已提交隔离级别。一种典型的方法是读已提交为每个查询使用单独的快照,而快照隔离对整个事务使用相同的快照。

串行化:串行化隔离通常被认为是最强的隔离级别。它保证即使事务可以并行执行,最终的结果也是一样的,就好像它们没有任何并发性,连续挨个执行一样。因此数据库保证,如果事务在单独运行时正常运行,则它们在并发运行时继续保持正确 —— 换句话说,数据库可以防止所有可能的竞争条件。

二阶段锁定算法:只要没有写入,就允许多个事务同时读取同一个对象。但对象只要有写入(修改或删除),就需要独占访问(exclusive access) 权限。

实现方法——读与写的阻塞是通过为数据库中每个对象添加锁来实现的。锁可以处于共享模式(shared mode)独占模式(exclusive mode)。锁使用如下:

  • 若事务要读取对象,则须先以共享模式获取锁。允许多个事务同时持有共享锁。但如果另一个事务已经在对象上持有排它锁,则这些事务必须等待。
  • 若事务要写入一个对象,它必须首先以独占模式获取该锁。没有其他事务可以同时持有锁(无论是共享模式还是独占模式),所以如果对象上存在任何锁,该事务必须等待。
  • 如果事务先读取再写入对象,则它可能会将其共享锁升级为独占锁。升级锁的工作与直接获得排他锁相同。
  • 事务获得锁之后,必须继续持有锁直到事务结束(提交或中止)。这就是“两阶段”这个名字的来源:第一阶段(当事务正在执行时)获取锁,第二阶段(在事务结束时)释放所有的锁

 

防止写入的数据被覆盖:写操作并发冲突

如果应用从数据库中读取一些值,修改它并写回修改的值(读取-修改-写入序列),则可能会发生丢失更新的问题。如果两个事务同时执行,则其中一个的修改可能会丢失,因为第二个写入的内容并没有包括第一个事务的修改。

 

 

 解决方案1:原子操作

UPDATE counters SET value = value + 1 WHERE key = 'foo';

许多数据库提供了原子更新操作,从而消除了在应用程序代码中执行读取-修改-写入序列的需要。

 解决方案2:悲观锁(写多读少,写入并发高的情况)

BEGIN TRANSACTION;
SELECT * FROM figures WHERE name = 'robot' AND game_id = 222 FOR UPDATE;
UPDATE figures SET position = 'c4' WHERE id = 1234;
COMMIT;

 使用悲观锁后,如果任何其他事务尝试同时读取同一个对象,则强制等待,直到第一个读取-修改-写入序列完成。

 解决方案3:乐观锁(读多写少,写入并发少的情况,因为并发高会导致大量的重试)

UPDATE wiki_pages SET content = '新内容' WHERE id = 1234 AND content = '旧内容';

 

持久性(Durability):

持久性意味着,一旦事务成功完成,即使发生硬件故障或数据库崩溃,写入的任何数据也不会丢失。

在单节点数据库中,持久性通常意味着数据已被写入非易失性存储设备,如硬盘或SSD。它通常还包括预写日志或类似的文件,以便在磁盘上的数据结构损坏时进行恢复。在带复制的数据库中,持久性可能意味着数据已成功复制到一些节点。为了提供持久性保证,数据库必须等到这些写入或复制完成后,才能报告事务成功提交。但是,完美的持久性是不存在的 ——如果所有硬盘和所有备份同时被销毁,那显然没有任何数据库能救得了你。

posted on 2020-01-21 16:25  ybonfire  阅读(215)  评论(0)    收藏  举报

导航