数据库数据库入门

【数据库】数据库入门(十二): 数据库事务(Database T

2020-01-09  本文已影响0人  Ulrich蚊子

定义

DBMS 提供了事务(Transactions)支持。事务是作为 DBMS 中的逻辑单元分组执行的一系列数据库操作。与在DBMS之外执行程序(例如,C程序)在许多方面都不同!

image

数据库应用程序通常通过事务而不是单个操作访问数据库。例如,大型数据库和数百个并发用户:银行、超市收银台、机票预订、在线购买等。
之所以使用事务是因为它们可以在以下情况下实施数据完整性:多个用户可以同时修改和共享数据;事务、系统和媒体故障可能不时发生。

BEGIN TRANSACTION
SELECT balance FROM Account WHERE name = `Steve';
UPDATE Account
    SET balance = balance-500 WHERE name=`Steve';
SELECT balance FROM Account WHERE name = `Bob';
UPDATE Account
    SET balance = balance+500 WHERE name = `Bob';
COMMIT
步骤 Transactions
1 read(A)
2 write(A) (A := A - 500)
3 read(B)
4 write(B) (B:= B + 500)
5 commit

其中,A 是 Steve 的账户余额;B 是 Bob 的账户余额。

ACID 属性

DBMS 确保了事务的以下属性:

需要注意的是,这些属性不是相互独立的,但原子性是中心属性

一致性(Consistency)由应用开发者实现,因为这要求开发者理解应用程序的数据模型,并且确保每个事务都以一种与现实世界中的合法更改相一致的方式更改数据。

而其他三种属性则由事务管理器实现,最常用的技术有两种:

  1. 锁(Locking)—— 用于并发控制,如两阶段锁(2PL:Two-phase Locking)
  2. 日志(Logging)—— 用于恢复,如预写式日志(WAL:Write-Ahead Log)

锁(Locking)

锁是一种用于并发控制的技术,可保证事务的隔离性。锁在数据库中一般作用在对象上,如文件、表、记录、页等。

锁的用法分成两类:

两阶段锁(2PL:Two-phase Locking)

锁的使用和移除分为两个阶段:

2PL 基本协议使用两种类型的锁:

当读写锁同时出现在一个对象身上的时候,锁之间的兼容满足以下关系:

锁的类型 读锁(read-lock) 写锁(write-lock)
读锁(read-lock) 兼容 不兼容
写锁(write-lock) 不兼容 不兼容

缺点:在某些情况下,2PL可以从根本上限制事务间的交叉
优点:2PL使交叉变得安全,比如保证事务的可串行性。

可串行化(Serializability)意味着产生的数据库状态等于以串行方式运行事务的数据库状态。
可串行性是并发事务的主要正确性标准。

但是,2PL可能会出现死锁,即,两个或多个事务的相互阻塞,比如一下的情况:

T1 T2 解释
lock-r(A) T1获取了A的读锁
read(A)
lock-r(B) T2获取了B的读锁,与A的读锁相互不影响
read(B)
lock-w(B) T1想获取B的写锁,但是需要等待T2释放B的读锁才能获取到
write(B)
lock-w(A)
write(A) T2想获取A的写锁,但是需要等待T1释放A的读锁才能获取到

如何使用锁定技术提高并发性?

日志(Logging)

日志是一种用于恢复的技术,可保证事务的原子性和持续性。日志是一个仅追加(append-only)的文件,它记录对对象的建议更改。当多个事务并发运行时,日志记录是交错的。

恢复相当于撤销或重做日志中的更改:

原子性(Atomicity)是通过定义当且仅当日志中记录了更改时才提交事务来实现的。
持久性(Durability)是通过在系统启动时读取日志并确保每个提交的事务都在数据库中应用了其更改来实现的。

预写式日志(WAL:Write-Ahead Log)

预写式日志(WAL)是最基本的规则,它确保在尝试从崩溃中恢复时,数据库的每个更改记录都是可用的。

主要思想:

因此,提交事务的定义为:“日志记录(包括提交记录)已写入持久存储的事务”。

对于一个日志记录来说,其典型的字段为:

WAL 对性能的提升体现在:

并发事务(Concurrent Transactions)

交错处理:事务交错在单个CPU中。


image

并行处理:事务在多个cpu中并行执行。


image

使用并发同时执行事务将提高数据库性能,具体包括:

但是 DBMS 必须保证事务的交错不会导致数据库的不一致,也就是说要实现并发控制。

并发控制的实现主要是为了要防止以下的问题:

如果使用串行执行事务的话,就不会出现 肮脏读取、不可重复读取问题 和 幽灵读取问题 这几种情况了。

未重复读取与幽灵读取的差别

不可重复读取 幽灵读取
执行相同的选择(SELECT)两次会产生相同的元组集合,但是属性值可能不同 执行相同的选择(SELECT)两次会产生两组不同的元组
读取受另一个事务更新(UPDATE)影响的对象时可能发生 当查询一组从另一个事务中插入(INSERT)、删除(DELETE)、更新(UPDATE)中受影响的元组时可能发生
可以使用记录级锁定(record-level lock)来防止 可以使用表格级锁定(table-level lock)来防止

也就是说,对于由更新(UPDATE)造成的读取不一致的情况,可以分别通过记录级锁定(record-level lock)和表格级锁定(table-level lock)两种类型的锁来防止相应的问题。

SQL 对事务的支持

核心思想就是,权衡性能(更好的并发访问)与一致性(数据库的完整性)

SQL-92 定义了事务隔离的4个级别:

通过以下命令可以指定事务隔离的级别:

SET TRANSACTION ISOLATION LEVEL serializable;

不同的隔离级别排除了不同的问题:(限制由弱到强)

隔离级别 含义 肮脏读取 不可重复读取 幽灵读取
未提交读(Read Uncommitted) 一个事务可以看到尚未提交的其他事务所做的更改。这可能相当危险。在对只读数据执行查询时使用它,或者在查询是否返回未提交数据无关紧要的情况下使用它。 出现 出现 出现
已提交读(Read Committed) 一个事务只看到其他事务提交的更改。它是数据库应用程序中最常用的隔离级别。当希望最大化应用程序之间的并发性,但不希望查询看到未提交的数据时,可以使用它。 不出现 出现 出现
可重复读(Repeatable Read) 事务所触及的对象被锁定,并且不能被并发事务更新或删除。当希望应用程序之间具有某种程度的并发性,但不希望在事务期间更改单个对象时,可以使用它 不出现 不出现 出现
串行(Serializable) 所有事务都与其他事务完全隔离。它是安全的,但可能会导致显著的性能下降。当希望应用程序之间具有某种程度的并发性,但不希望在不同的时间运行查询时返回不同的结果集时,可以使用它。 不出现 不出现 不出现

更高的隔离级别减少了用户可能遇到的并发问题的类型,但是需要更多的系统资源,并且增加了一个事务阻塞其他事务的机会。

不同的 DBMS 实现隔离级别的方式非常不同。丢失更新所需的隔离级别取决于数据库管理系统的实现。但一般来说,它可能需要最高的水平序列化来防止它。

较低的隔离级别增加了许多用户同时访问数据的能力,但也增加了用户可能会遇到并发性影响的数量

相反,更高的隔离级别减少了用户可能遇到的并发影响的类型,但是需要更多的系统资源,并增加了一个事务阻塞另一个事务的机会。

因此,选择适当的隔离级别取决于平衡应用程序的数据完整性需求和每个隔离级别的开销。

上一篇下一篇

猜你喜欢

热点阅读