转账是生活中常见的操作,比如从A账户转账100元到B账号。站在用户角度而言,这是一个逻辑上的单一操作,然而在数据库系统中,至少会分成两个步骤来完成:
在这个过程中可能会出现以下问题:
转账操作的第一步执行成功,A账户上的钱减少了100元,但是第二步执行失败或者未执行便发生系统崩溃,导致B账户并没有相应增加100元。
定义:数据库事务是构成单一逻辑工作单元的操作集合,一个典型的数据库事务如下所示:
BEGIN TRANSACTION //事务开始
SQL1
SQL2
COMMIT/ROLLBACK //事务提交或回滚
关于事务的定义有几点需要解释下:
对于上面的转账例子,可以将转账相关的所有操作包含在一个事务中
BEGIN TRANSACTION
A账户减少100元
B账户增加100元
COMMIT
事务使系统能够更方便的进行故障恢复以及并发控制,从而保证数据库状态的一致性。
原子性(Atomicity):事务中的所有操作作为一个整体像原子一样不可分割,要么全部成功,要么全部失败。
一致性(Consistency):事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态。
一致性状态是指:
隔离性(Isolation):并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样。
比如多个用户同时往一个账户转账,最后账户的结果应该和他们按先后次序转账的结果一样。
持久性(Durability):事务一旦提交,其对数据库的更新就是持久的。任何事务或系统故障都不会导致数据丢失。
在事务的ACID特性中,C即一致性是事务的根本追求,而对数据一致性的破坏主要来自两个方面:
数据库系统是通过并发控制技术和日志恢复技术来避免这种情况发生的。
并发控制技术保证了事务的隔离性,使数据库的一致性状态不会因为并发执行的操作被破坏。
日志恢复技术保证了事务的原子性,使一致性状态不会因事务或系统故障被破坏。
同时使已提交的对数据库的修改不会因系统崩溃而丢失,保证了事务的持久性。
在讲解并发控制技术前,先简单介绍下数据库常见的并发异常。
脏写:脏写是指事务回滚了其他事务对数据项的已提交修改,比如下面这种情况
在事务1对数据A的回滚,导致事务2对A的已提交修改也被回滚了。
丢失更新:丢失更新是指事务覆盖了其他事务对数据的已提交修改,导致这些修改好像丢失了一样。
事务1和事务2读取A的值都为10,事务2先将A加上10并提交修改,之后事务2将A减少10并提交修改,A的值最后为,导致事务2对A的修改好像丢失了一样
脏读:脏读是指一个事务读取了另一个事务未提交的数据
在事务1对A的处理过程中,事务2读取了A的值,但之后事务1回滚,导致事务2读取的A是未提交的脏数据。
不可重复读:指一个事务对同一数据的读取结果前后不一致。
脏读和不可重复读的区别在于:前者读取的是事务未提交的脏数据,后者读取的是事务已经提交的数据,只不过因为数据被其他事务修改过导致前后两次读取的结果不一样
比如下面这种情况
由于事务2对A的已提交修改,事务1前后两次读取的结果不一致。
幻读:幻读是指事务读取某个范围的数据时,因为其他事务的操作导致前后两次读取的结果不一致。
幻读和不可重复读的区别在于,不可重复读是针对确定的某一行数据而言,而幻读是针对不确定的多行数据。
因而幻读通常出现在带有查询条件的范围查询中,比如下面这种情况:
事务1查询A<5的数据,由于事务2插入了一条A=4的数据,导致事务1两次查询得到的结果不一样
事务具有隔离性,理论上来说事务之间的执行不应该相互产生影响,其对数据库的影响应该和它们串行执行时一样。
然而完全的隔离性会导致系统并发性能很低,降低对资源的利用率,因而实际上对隔离性的要求会有所放宽,这也会一定程度造成对数据库一致性要求降低
SQL标准为事务定义了不同的隔离级别,从低到高依次是
事务的隔离级别越低,可能出现的并发异常越多,但是通常而言系统能提供的并发能力越强。
不同的隔离级别与可能的并发异常的对应情况如下表所示
有一点需要强调,这种对应关系只是理论上的,对于特定的数据库实现不一定准确
比如mysql的Innodb存储引擎通过Next-Key Locking技术在可重复读级别就消除了幻读的可能。
所有事务隔离级别都不允许出现脏写,而串行化可以避免所有可能出现的并发异常,但是会极大的降低系统的并发处理能力。
并发控制技术是实现事务隔离性以及不同隔离级别的关键,实现方式有很多。按照其对可能冲突的操作采取的不同策略可以分为乐观并发控制和悲观并发控制两大类。
核心思想:对于并发可能冲突的操作,比如读-写,写-读,写-写,通过锁使它们互斥执行。
锁通常分为共享锁和排他锁两种类型
基于锁的并发控制流程:
可能出现的问题:
对于可能发生冲突的并发操作,锁使它们由并行变为串行执行,是一种悲观的并发控制。
核心思想:对于并发可能冲突的操作,基于时间戳排序规则选定某事务继续执行,其他事务回滚。
系统会在每个事务开始时赋予其一个时间戳,这个时间戳可以是系统时钟也可以是一个不断累加的计数器值,当事务回滚时会为其赋予一个新的时间戳,先开始的事务时间戳小于后开始事务的时间戳。
每一个数据项Q有两个时间戳相关的字段:
时间戳排序规则如下:
假设事务T发出read(Q),T的时间戳为TS
假设事务T发出write(Q)
基于时间戳排序和基于锁实现的本质一样:对于可能冲突的并发操作,以串行的方式取代并发执行,因而它也是一种悲观并发控制。
它们的区别主要有两点:
核心思想:事务对数据的更新首先在自己的工作空间进行,等到要写回数据库时才进行有效性检查,对不符合要求的事务进行回滚。
基于有效性检查的事务执行过程会被分为三个阶段:
有效性检查通常也是通过对事务的时间戳进行比较完成的,不过和基于时间戳排序的规则不一样。
该方法允许可能冲突的操作并发执行,因为每个事务操作的都是自己工作空间的局部变量,直到有效性检查阶段发现了冲突才回滚。因而这是一种乐观的并发策略。
快照隔离是多版本并发控制(mvcc)的一种实现方式。
其核心思想是:数据库为每个数据项维护多个版本(快照),每个事务只对属于自己的私有快照进行更新,在事务真正提交前进行有效性检查,使得事务正常提交更新或者失败回滚。
由于快照隔离导致事务看不到其他事务对数据项的更新,为了避免出现丢失更新问题,可以采用以下两种方案避免:
事务间可能冲突的操作通过数据项的不同版本的快照相互隔离,到真正要写入数据库时才进行冲突检测。因而这也是一种乐观并发控制。
数据库运行过程中可能会出现故障,这些故障包括事务故障和系统故障两大类
这些故障可能会对事务和数据库状态造成破坏,因而必须提供一种技术来对各种故障进行恢复,保证数据库一致性,事务的原子性以及持久性。
数据库通常以日志的方式记录数据库的操作从而在故障时进行恢复,因而可以称之为日志恢复技术。
事务的执行过程可以简化如下:
由于数据库存在立即修改和延迟修改,所以在事务执行过程中可能存在以下情况:
<T,X,V1,V2>
:描述一次数据库写操作,T是执行写操作的事务的唯一标识,X是要写的数据项,V1是数据项的旧值,V2是数据项的新值。<T,X,V1>
:对数据库写操作的撤销操作,将事务T的X数据项恢复为旧值V1。在事务恢复阶段插入。<T start>
: 事务T开始<T commit>
: 事务T提交<T abort>
: 事务T中止关于日志,有以下两条规则
<T abort>
记录。事务正常回滚/因事务故障中止将进行redo,系统从崩溃中恢复时将先进行redo再进行undo。
以下事务将进行undo:日志中只包括<T start>
记录,但既不包括<T commit>
记录也不包括<T abort>
记录.
以下事务将进行redo:日志中包括<T start>
记录,也包括<T commit>
记录或<T abort>
记录。
假设系统从崩溃中恢复时日志记录如下
<T0 start>
<T0,A,1000,950>
<T0,B,2000,2050>
<T0 commit>
<T1 start>
<T1,C,700,600>
由于T0既有start记录又有commit记录,将会对事务T0进行重做,执行相应的redo操作。
由于T1只有start记录,将会对T1进行撤销,执行相应的undo操作,撤销完毕将写入一条abort记录。
<T,X,V1,V2>
的记录,将旧值V1写入数据项X中。<T,X,V1>
,表示将数据项恢复成旧值V1,这是一个只读的补偿记录,不需要根据它进行undo。<T start>
日志记录,就停止继续扫描,并往日志中写一个
<T abort>
日志记录。检查点是形如<checkpoint L>
的特殊的日志记录,L是写入检查点记录时还未提交的事务的集合
系统保证在检查点之前已经提交的事务对数据库的修改已经写入磁盘,不需要进行redo。检查点可以加快恢复的过程。
系统奔溃时的恢复过程分为两个阶段:重做阶段和撤销阶段。
重做阶段:
<T,X,V1,V2>
的更新记录或<T,X,V>
的补偿撤销记录,就重做该操作。<T start>
记录,就把T加入到undo-list中。<T abort>
或<T commit>
记录,就把T从undo-list中去除。撤销阶段:
总结:先将日志记录中所有事务的更新按顺序重做一遍,在针对需要撤销的事务按相反的顺序执行其更新操作的撤销操作。
恢复前的日志如下,写入最后一条日志记录后系统崩溃
<T0 start>
<T0,B,2000,2050>
<T2 commit>
<T1 start>
<checkpoint {T0,T1}> //之前T2已经commit,故不用重做
<T1,C,700,600>
<T1 commit>
<T2 start>
<T2,A,500,400>
<T0,B,2000>
<T0 abort> //T0回滚完成,插入该记录后系统崩溃
事务是数据库系统进行并发控制的基本单位,是数据库系统进行故障恢复的基本单位,从而也是保持数据库状态一致性的基本单位。
ACID是事务的基本特性,数据库系统是通过并发控制技术和日志恢复技术来对事务的ACID进行保证的,从而可以得到如下的关于数据库事务的概念体系结构。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。