数据库:事务 - Go语言中文社区

数据库:事务


在开始编写文章前,有几个问题需要思考一下:

  • 什么是事务?
  • 事务有哪些性质?
  • 如何实现事务?
  • 如何管理事务?

1. 什么是事务?

事务是一个工作的逻辑单元,这个单元必须是全部运行或者全部取消,不能接受中间状态。在事务中所有的 SQL 语句必须成功完成。如果 SQL 语句中的任何一个失败,那么整个事务必须回滚到原始的数据库状态,这个状态也就是该事务开始之前的状态。一个成功事务是将数据库从一个一致性状态修改到另一个一致性状态。在数据库的一致性状态里,所有数据的完整性必须满足。

2. 事务有哪些性质?

每个单独的事务必须具有原子性、一致性、排他性和持久性。有时把这些性质称为 ACID。此外,当运行多个事物时,DBMS 必须安排事务同时运行。每个事务的运行调度必须具有可串行性的性质。

  • 原子性要求每个事务的所有操作(SQL 请求)必须是完整的;如果不是,事务撤销。如果事务 T1 有 4 个 SQL 请求,所有这 4 个请求必须成功完成;否则,整个事务撤销。换句话说,事务是被看成单个的、不可分割的逻辑单元。
  • 一致性暗示了数据库的一致性的状态的持久性。事务把数据库从一个一致性状态修改为另一个一致性状态。当事务结束时,数据库必须是一致性状态;如果事务的任何地方违反了完整性约束,整个事务撤销。
  • 排他性意味着在事务运行期间,所用的数据不能被另一个事务使用,直到第一个事务结束。换句话说,如果事务 T1 正在使用数据 X,那么该数据不能被其他事务访问,直到 T1 结束。这个性质用在多用户数据库环境中非常有用,因为多个用户可能同时访问和更新数据库。
  • 持久性确保一个事务的修改已完成(提交),不能不做或者丢失,即使在系统错误发生时也是如此。
  • 可串行性确保事务同时运行的调度产生一致的结果。这个性质在多个用户和分布式数据库中很重要的,其中多个事务可能同时运行。

由于其本身的性质,单用户数据库系统自动确保可串行性和数据库的排他性,因为仅一个事务在一个时刻被运行时。原子性、一致性和事务的持久性必须由单用户 DBMS 来保证(甚至单用户 DBMS 必须从错误中恢复,这些错误包括中断、断点、不正当的应用运行)。

多用户数据库通常受多个同时存在的事务支配。因此,多用户 DBMS 必须运用管理手段,以确保可串行性和事务的排他性(还有原子性和持久性),以保证数据库的一致性和完整性,例如,如果多个同时存在的事务运行相同的数据集,并且在第一个事务完成之前,第二个事务更新了数据库。此时,违背了排他性,并且数据库也不一致的。DBMS 必须通过使用并发控制技术来管理事务,以避免这样不符合要求的情况。

如何实现事务?

美国国家标准学会制定了标准(ANSI),这个标准管理 SQL 数据库事务。事务支持有两个 SQL 语句提供:COMMIT(提交)和 ROLLBACK(回滚)。ANSI 要求事务顺序最终由用户和应用程序来设定,顺序必须支持所有随后的 SQL 语句,直到如下 4 个事件之一发生:

  • 一个 COMMIT 语句到达,在这种情况下,所有的修改都永久的记录在数据库中,这个 COMMIT 语句自动结束 SQL 语句;
  • 一个 ROLLBACK 语句到达,在这种情况下,所有的修改都取消,并且数据库回滚到以前的一致性状态;
  • 一个程序成功结束,在这种情况下,所有的修改都永久地记录在数据库里。这种情况下等同于 COMMIT;
  • 程序不正当的中断,在这种情况下,所有在数据库上的修改都要取消,并且数据库回滚到以前一致性状态。这种情况和 ROLLBACK 一样。

事务日记

DBMS 用事务日记来跟踪所有更新数据库的事务。DBMS 使用存储在日记中的信息来恢复命令,ROLLBACK 语句、程序中不正常中断或者系统错误(比如网路错误或磁盘损坏)都会触发恢复命令。一些 RDBMS 使用事务日记,把数据库恢复到当前的一致性状态。当服务器发生错误时,Oracle 自动回滚到未提交事务,并且回滚前面已提交但未写到物理数据库中的事务。这个行为要求事务的正确性,并且这是任何 DBMS 的典型情况。

当 DBMS 运行修改数据库的事务时,它总是自动更新事务日记。事务日记存储如下信息:

1、事务开始记录;

2、每个事务组件(SQL 语句):

  • 操作运行的类型(UPDATE、DELETE、INSERT)。
  • 事务影响到事件名(表名)。
  • 更新之前的值和之后的值。
  • 同一个事务,指针指向前一个和下一个事务日记。

3、事务的结束(COMMIT);

尽管使用事务日记增加 DBMS 的处理开销,但是能够恢复已毁坏的数据库,是物有所值的。

3. 如何管理事务?

当两个或两个以上的并发事务同时运行时,可能发生严重的问题。数据库事务包括很多数据库 I/O 操作,这些操作涉及把数据库从一个一致性状态转到另一个一致性状态。如果事务更新了多个表/行,那么数据库总是在事务运行期间,有过暂时的不一致状态(如果事务仅包含一个更新,那么就没有暂时的不一致性状态)。暂时的不一致状态之所以存在,是因为计算机有序地运行操作。在这个有序的过程中,事务的排他性阻止他们访问其他事务还没释放的数据。此时,调度器的工作非常重要,它使用多核处理器,可以在同一时间运行多个操作。如果两个事务同时运行并且它们同时访问同一数据,将会发生什么呢?

在事务中的操作是随机运行的,只要两个事务 T1 和 T2 访问的是不相关的数据,就不会发生冲突,而且运行的顺序不会影响最后的结果。但是,如果事务要对相关(相同)数据进行操作,冲突可能发生在事务内部以及运行的顺序中,对另一个可能产生不满意的结果。因此,怎么确定的顺序,谁决定这个顺序?值得庆幸的是,DBMS 可以使用内置调度器来处理这种复杂的任务。

调度器是一种特殊的 DBMS 过程,它在并发事务的运行过程中创建操作的运行顺序。调度器使数据库的操作交替运行,以便保证事务的可串行化和排他性。调度器根据事务在并发控制算法中的行为(如锁或时间戳方法),确定合理的顺序。但是,并不是所有的事务都是可串行化的,理解这一点很重要。DBMS 确定哪些事务是可串行化和交替运行的。通常,DBMS 根据先来先服务的原则,先运行那些不可串行化的事务。调度器的主要工作是生成一个事务操作的顺序安排。可串行化调度是一种事务操作的调度,在这个调度中,事务交替运行的结果与事务顺序运行的结果相同。

调度器可以提高计算机的中央处理器和存储系统的效率。如果不安排事务的运行顺序,那么所有事务都按照先进先出服务的原则。这样的问题是,当 CPU 等待一个 READ 或者 WRITE 操作完成时,浪费了处理时间,从而浪费了 CPU 的时间。简而言之,在多用户 DBMS 环境中,先进先服务调度往往产生不可接受的响应时间。因此,需要其他一些调度方法来提高整体系统的效率。此外,为了确保两个事务同时更新同一数据,调度有助于数据的隔离。数据库操作中的 READ 与 WRITE 或者 WRITE 与 WRITE 都可能产生冲突。在并发事务中有几种方法来调度冲突操作的运行。这方法是锁、时间戳、优化等。

在多用户数据库系统中,多个事务同时运行的协作。并发控制的目的是为了确保在多用户数据库环境中,事务的可串行化。并发控制是很重要的,因为在同一个数据库,事务同时运行会产生数据的完整性和一致性问题。3 个主要的问题是更新丢失、未提交数据和不一致检索。

  • 更新丢失:更新丢失发生在两个并发事务 T1 和 T2 在同时更新同一数据库时,其中一个更新丢失(其他事务重复写)。
  • 未提交数据:未提交数据发生在两个事务 T1 和 T2 同时运行,并且在第二个事务(T2)已经访问未提交数据之后,第一个事务(T1)发生了回滚——因此违反了事务的排他性。
  • 不一致检索:不一致检索发生在事务访问数据之前和其他事务运行完成之后。

锁保证了当前事务对数据的独立性。换句话说,当事务 T1 正在使用一个数据时,事务 T2 不能访问这个数据。事务在获得对数据访问之前,先获得锁。只有在这个事务完成并释放锁之后,其他事务才可以对这个数据加锁。

大部分的多用户 DBMS 自动启动和实施锁程序。锁管理器管理所有的锁信息,并且负责分配和监督所有事务使用的锁。

3.1 锁粒度

锁粒度表示的锁使用级别。锁可有如下级别决定:数据库级、表级、页级、行级、甚至字段(属性)级。

1、数据库级

在数据库级锁中,当事务 T1 在运行时,为了防止事务 T2 使用数据库中的表,锁住整个数据库。锁的级别对成批的处理很好,但是对多用户 DBMS 则是不合理的。试想一下,在新的事务申请数据库之前,如果有成百上千的事务必须等待前面事务完成,那么数据库的访问有多么慢?下图说明数据库级锁。注意,由于是数据库级锁,即使是要访问数据库中的 T1 和 T2 也不能同时访问这个数据库。

2、表级锁

在表级锁中,当事务 T1 正在使用表时,为了防止事务 T2 访问该表的任何一行,锁住整个表。如果一个事务需要访问多个表,那么需要锁住每个表。但是只要访问的是不同表,那么两个事务是可以访问同一个数据库的。

当多个事务等待同一个表时,因为表级锁比数据库级锁限制更少,所以会引起堵塞。当不同的事务需要访问不同的表时,也就是说,当事务不会相互影响时,锁导致延迟,这样的话是很令人厌烦的。因此,表级锁对多用户的 DBMS 不适合。下图说明表级锁的作用。注意,在下图中,即使事务 T1 和事务 T2 使用不同的行,也同时不能访问同一表,T2 必须等 T1 释放表。

3、页级锁

在页级锁中,DBMS 将锁住这个磁盘页。磁盘页或者页相当于磁盘块,是直接从磁盘寻址的。页有固定的大小,如 4k、8k、16k。例如仅想把 73 字节写到 4K 的页里,则必须把整个 4K 页从磁盘中读出,然后在内存中更新,最后写会磁盘。一个表中可以存放许多页,一个页中可以包含一个或多个表中的行。下图所示的是一个页级锁例子。注意,当锁住的是不同磁盘页时,事务 T1 和 T2 才可以访问同一表。如果 T2 需要使用 T1 锁住的页中的某些行时,T2 必须等 T1 释放该页。

4、行级锁

行级锁比前面讨论的锁的严格性更低。DBMS 允许并发事务访问同一表中的不同行,即使这些行在同一页中也是如此。尽管行级锁提高了数据的可用性,但是它的管理成本却很高,因为数据库表可能含有冲突事务。当应用程序在同一页中需要更多个锁时,现代的 DBMS 会自动地把行级锁提升为页级锁。如下图,即使行请求是在同一页中,两个事务也可以同时运行。如果 T2 和 T1 同时请求同一行,T2 必须等待。

5、字段级锁

只要并发事务同时请求的是同一行的不同字段,字段级锁就允许并发事务去访问该行。尽管字段级锁产生更多灵活的多用户数据访问,但是它仅在 DBMS 中运行,因为它将使得计算机的负担极高。

3.2 锁类型

锁有各种级别,DBMS 可以使用不同锁类型:二元锁、共享锁/排他锁。

1、二元锁

二元锁仅有两种状态:加锁(1)或者解锁(0)。如果一个对象——可能是一个数据库、表、页或行——被一个事务锁住,其他事务不可以使用这个对象。如果一个对象没有加锁,任何事务如果使用它就可以对它进行加锁。每个数据库运行需要对被作用的对象进行加锁。基于这个规则,一个事务在运行结束之后必须解锁。因此,每个事务对要访问的每个数据项进行加锁或解锁。DBMS 自动地管理操作和安排时间,用户不需要关心数据项的加锁和解锁(每个 DBMS 有自己默认的加锁机制,如果终端用户想要覆盖这种默认机制,可以使用 LOCK TABLE 或其他 SQL 命令)。

2、共享/排他锁

“共享” 和 “排他” 表示的是锁的性质。当事务访问对象并且要修改时,对象就加排它锁。当冲突可能发生时,必须加排它锁。如果要让并发事务可以读取数据,就加共享锁。只要并发事务只读,共享锁不会产生冲突。

如果一个事务想从数据库中读数据且这个数据没有排它锁,那么就加共享锁。当一个事务想更新(写)一个数据项且没有其他事务对这个数据项加锁时,那么就加排它锁。使用共享/排他锁思想,锁有 3 个状态:解锁、共享(读)、排他(写)。

两个事务会发生冲突的条件是,其中至少有一个是 WRITE 事务,因为两个 READ 使用可以安全地同时运行,共享锁允许多个 READ 事务同时读取相同的数据项。例如,如果事务 T1 在数据项 X 上有一个共享锁,而 T2 要读取数据项 X,那么 T2 也可以获得数据项 X 上的共享锁。

如果 T2 要更新数据项 X,就要求 T2 在数据项 X 上加一个排它锁。当且仅当数据项没有其他锁时,才可以在数据项上加排它锁。因此,如果事务 T1 已经在数据项 X 上加了共享锁或排它锁,事务 T2 就不能在加排它锁了,T2 必须等待,知道 T1 提交完成。这种条件就称为互斥原则:在同一个对象上,不能有多个事务同时施加排它锁。

尽管共享锁可以使得数据访问更高效,但共享/排他锁模式增加了锁管理器的负担,因为:

  • 在加锁之前,锁的类型必须是已知的。
  • 需要使用 3 个锁操作:READ_LOCK(检查锁的类型)、WRITE_LOCK(加锁)和 UNLOCK(解锁)。
  • 这种模式被提升,以便允许锁的升级(从共享锁到排他锁)和降级(从排它锁到共享锁)。

3.3 死锁

尽管锁防止了严重的数据不一致性问题,但是也可以带来下面两个问题:

  • 事务调度可能不是不可串行化的。
  • 调度可能导致死锁。数据库死锁,相当于大城市里的交通堵塞。当两个或两个以上的事务相互等待将导致数据库死锁。

庆幸的是,上面两个问题都已经解决了:通过加锁协议(如两阶段加锁)来保证可串行化,也可使用死锁检查和保护技术来防止死锁的发生。

两阶段加锁

两阶段加锁定义了事务如何获得和解锁。两阶段加锁保证了可串行化,但不能阻止死锁。这两阶段是:

  • 增长阶段:事务获得所有需要的锁,但不能释放任何数据。一旦获得了所有锁,事务就位于加锁点上。
  • 缩减阶段:事务释放所有锁且不可获得任何新锁。

两阶段加锁协议有如下规则:

  • 同一事务没有冲突的锁。
  • 同一事务中,解锁都是在获得锁之后进行的。
  • 在获得所有锁之前(也就是说,在事务位于加锁点上之前),任何数据都不会发生变化。

参考

https://www.souyunku.com/2018/07/30/mysql

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/dilixinxixitong2009/article/details/80256732
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-07 15:14:43
  • 阅读 ( 1143 )
  • 分类:数据库

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢