mysql数据库事务实现原理_数据库事务的概念及其实现原理

前言

事务是数据库系统中的重要概念,了解这一律念是以正确的方式开发和数据库交互的应用程序的前提。可是不少开发者对于事务的认识相对片面和肤浅,仅仅把它等同于ACID,不了解数据库系统引入事务的真正动机,ACID对于事务意味着什么以及最重要的:数据库系统是如何保证事务的ACID特性的?

由于最近在导师手下作一个微服务和分布式事务相关的课题项目,做为必要的前置准备,须要加深下对单机事务的认识并理解其实现原理。这篇文章是在我给组内的小伙伴分享的PPT的基础上改动而成,旨在帮助你们创建关于事务的相对体系的认识。因为当时正好遇上考试周,因此写的比较仓促,里面确定有理解不到位甚至出错的地方,但愿看到的小伙伴可以我指出来。同时还需强调几点:sql

如下全部内容都是针对单机事务而言,不涉及分布式事务相关的东西!

关于事务原理的讲解不针对具体的某个数据库实现,因此某些地方可能和你的实践经验不符。

1. 认识事务

1.1 为何须要数据库事务

转帐是生活中常见的操做,好比从A帐户转帐100元到B帐号。站在用户角度而言,这是一个逻辑上的单一操做,然而在数据库系统中,至少会分红两个步骤来完成:数据库

1.将A帐户的金额减小100元

2.将B帐户的金额增长100元。

6c9b5fdabf84415aef20b22eb594f0f4.png

在这个过程当中可能会出现如下问题:并发

1.转帐操做的第一步执行成功,A帐户上的钱减小了100元,可是第二步执行失败或者未执行便发生系统崩溃,致使B帐户并无相应增长100元。

2.转帐操做刚完成就发生系统崩溃,系统重启恢复时丢失了崩溃前的转帐记录。

3.同时又另外一个用户转帐给B帐户,因为同时对B帐户进行操做,致使B帐户金额出现异常。

为了便于解决这些问题,须要引入数据库事务的概念。mvc

1.2 什么是数据库事务

定义:数据库事务是构成单一逻辑工做单元的操做集合

一个典型的数据库事务以下所示分布式

BEGIN TRANSACTION //事务开始

SQL1

SQL2

COMMIT/ROLLBACK //事务提交或回滚

关于事务的定义有几点须要解释下:微服务

1.数据库事务能够包含一个或多个数据库操做,但这些操做构成一个逻辑上的总体。

2.构成逻辑总体的这些数据库操做,要么所有执行成功,要么所有不执行。

3.构成事务的全部操做,要么全都对数据库产生影响,要么全都不产生影响,即无论事务是否执行成功,数据库总能保持一致性状态。

4.以上即便在数据库出现故障以及并发事务存在的状况下依然成立。

1.3 事务如何解决问题

对于上面的转帐例子,能够将转帐相关的全部操做包含在一个事务中性能

BEGIN TRANSACTION

A帐户减小100元

B帐户增长100元

COMMIT

1.当数据库操做失败或者系统出现崩溃,系统可以以事务为边界进行恢复,不会出现A帐户金额减小而B帐户未增长的状况。

2.当有多个用户同时操做数据库时,数据库可以以事务为单位进行并发控制,使多个用户对B帐户的转帐操做相互隔离。

事务使系统可以更方便的进行故障恢复以及并发控制,从而保证数据库状态的一致性。3d

1.4 事务的ACID特性以及实现原理概述

原子性(Atomicity):事务中的全部操做做为一个总体像原子同样不可分割,要么所有成功,要么所有失败。日志

一致性(Consistency):事务的执行结果必须使数据库从一个一致性状态到另外一个一致性状态。一致性状态是指:1.系统的状态知足数据的完整性约束(主码,参照完整性,check约束等) 2.系统的状态反应数据库本应描述的现实世界的真实状态,好比转帐先后两个帐户的金额总和应该保持不变。

隔离性(Isolation):并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时同样。好比多个用户同时往一个帐户转帐,最后帐户的结果应该和他们按前后次序转帐的结果同样。

持久性(Durability):事务一旦提交,其对数据库的更新就是持久的。任何事务或系统故障都不会致使数据丢失。

在事务的ACID特性中,C即一致性是事务的根本追求,而对数据一致性的破坏主要来自两个方面

1.事务的并发执行

2.事务故障或系统故障

数据库系统是经过并发控制技术和日志恢复技术来避免这种状况发生的。

并发控制技术保证了事务的隔离性,使数据库的一致性状态不会由于并发执行的操做被破坏。

日志恢复技术保证了事务的原子性,使一致性状态不会因事务或系统故障被破坏。同时使已提交的对数据库的修改不会因系统崩溃而丢失,保证了事务的持久性。

4d2f7e7cedfcbba732a898440727f5b5.png

2.并发异常与并发控制技术

2.1 常见的并发异常

在讲解并发控制技术前,先简单介绍下数据库常见的并发异常。

脏写

脏写是指事务回滚了其余事务对数据项的已提交修改,好比下面这种状况

4d4a01d023a00834d4e65096ed3cb8d6.png

在事务1对数据A的回滚,致使事务2对A的已提交修改也被回滚了。

丢失更新

丢失更新是指事务覆盖了其余事务对数据的已提交修改,致使这些修改好像丢失了同样。

fc9c0507718d5d12b4f238b68b7d263f.png

事务1和事务2读取A的值都为10,事务2先将A加上10并提交修改,以后事务2将A减小10并提交修改,A的值最后为,致使事务2对A的修改好像丢失了同样

脏读

脏读是指一个事务读取了另外一个事务未提交的数据

26ee64cc34cf90ce5336e97b5f874677.png

在事务1对A的处理过程当中,事务2读取了A的值,但以后事务1回滚,致使事务2读取的A是未提交的脏数据。

不可重复读

不可重复读是指一个事务对同一数据的读取结果先后不一致。脏读和不可重复读的区别在于:前者读取的是事务未提交的脏数据,后者读取的是事务已经提交的数据,只不过由于数据被其余事务修改过致使先后两次读取的结果不同,好比下面这种状况

f00b9dd4870430083e0713327982f7e1.png

因为事务2对A的已提交修改,事务1先后两次读取的结果不一致。

幻读

幻读是指事务读取某个范围的数据时,由于其余事务的操做致使先后两次读取的结果不一致。幻读和不可重复读的区别在于,不可重复读是针对肯定的某一行数据而言,而幻读是针对不肯定的多行数据。于是幻读一般出如今带有查询条件的范围查询中,好比下面这种状况:

1b5f697d0ffcfc780865648fa41e1e34.png

事务1查询A<5的数据,因为事务2插入了一条A=4的数据,致使事务1两次查询获得的结果不同

2.2 事务的隔离级别

事务具备隔离性,理论上来讲事务之间的执行不该该相互产生影响,其对数据库的影响应该和它们串行执行时同样。

然而彻底的隔离性会致使系统并发性能很低,下降对资源的利用率,于是实际上对隔离性的要求会有所放宽,这也会必定程度形成对数据库一致性要求下降

SQL标准为事务定义了不一样的隔离级别,从低到高依次是

读未提交(READ UNCOMMITTED)

读已提交(READ COMMITTED)

可重复读(REPEATABLE READ)

串行化(SERIALIZABLE)

事务的隔离级别越低,可能出现的并发异常越多,可是一般而言系统能提供的并发能力越强。

不一样的隔离级别与可能的并发异常的对应状况以下表所示,有一点须要强调,这种对应关系只是理论上的,对于特定的数据库实现不必定准确,好比mysql

的Innodb存储引擎经过Next-Key Locking技术在可重复读级别就消除了幻读的可能。

100308ce9bdee015928361df2cfc7dae.png

全部事务隔离级别都不容许出现脏写,而串行化能够避免全部可能出现的并发异常,可是会极大的下降系统的并发处理能力。

2.3 事务隔离性的实现——常见的并发控制技术

并发控制技术是实现事务隔离性以及不一样隔离级别的关键,实现方式有不少,按照其对可能冲突的操做采起的不一样策略能够分为乐观并发控制和悲观并发控制两大类。

乐观并发控制:对于并发执行可能冲突的操做,假定其不会真的冲突,容许并发执行,直到真正发生冲突时才去解决冲突,好比让事务回滚。

悲观并发控制:对于并发执行可能冲突的操做,假定其一定发生冲突,经过让事务等待(锁)或者停止(时间戳排序)的方式使并行的操做串行执行。

2.3.1 基于封锁的并发控制

核心思想:对于并发可能冲突的操做,好比读-写,写-读,写-写,经过锁使它们互斥执行。

锁一般分为共享锁和排他锁两种类型

1.共享锁(S):事务T对数据A加共享锁,其余事务只能对A加共享锁但不能加排他锁。

2.排他锁(X):事务T对数据A加排他锁,其余事务对A既不能加共享锁也不能加排他锁

基于锁的并发控制流程:

事务根据本身对数据项进行的操做类型申请相应的锁(读申请共享锁,写申请排他锁)

申请锁的请求被发送给锁管理器。锁管理器根据当前数据项是否已经有锁以及申请的和持有的锁是否冲突决定是否为该请求授予锁。

若锁被授予,则申请锁的事务能够继续执行;若被拒绝,则申请锁的事务将进行等待,直到锁被其余事务释放。

可能出现的问题:

死锁:多个事务持有锁并互相循环等待其余事务的锁致使全部事务都没法继续执行。

饥饿:数据项A一直被加共享锁,致使事务一直没法获取A的排他锁。

对于可能发生冲突的并发操做,锁使它们由并行变为串行执行,是一种悲观的并发控制。

2.3.2 基于时间戳的并发控制

核心思想:对于并发可能冲突的操做,基于时间戳排序规则选定某事务继续执行,其余事务回滚。

系统会在每一个事务开始时赋予其一个时间戳,这个时间戳能够是系统时钟也能够是一个不断累加的计数器值,当事务回滚时会为其赋予一个新的时间戳,先开始的事务时间戳小于后开始事务的时间戳。

每个数据项Q有两个时间戳相关的字段:

W-timestamp(Q):成功执行write(Q)的全部事务的最大时间戳

R-timestamp(Q):成功执行read(Q)的全部事务的最大时间戳

时间戳排序规则以下:

假设事务T发出read(Q),T的时间戳为TS

a.若TS(T)

read操做将被拒绝,T回滚。

b.若TS(T)>=W-timestamp(Q),则执行read操做,同时把

R-timestamp(Q)设置为TS(T)与R-timestamp(Q)中的最大值

假设事务T发出write(Q)

a.若TS(T)

b.若TS(T)

c.其余状况:系统执行write操做,将W-timestamp(Q)设置

为TS(T)。

基于时间戳排序和基于锁实现的本质同样:对于可能冲突的并发操做,以串行的方式取代并发执行,于是它也是一种悲观并发控制。它们的区别主要有两点:

基于锁是让冲突的事务进行等待,而基于时间戳排序是让冲突的事务回滚。

基于锁冲突事务的执行次序是根据它们申请锁的顺序,先申请的先执行;而基于时间戳排序是根据特定的时间戳排序规则。

2.3.3 基于有效性检查的并发控制

核心思想:事务对数据的更新首先在本身的工做空间进行,等到要写回数据库时才进行有效性检查,对不符合要求的事务进行回滚。

基于有效性检查的事务执行过程会被分为三个阶段:

读阶段:数据项被读入并保存在事务的局部变量中。全部write操做都是对局部变量进行,并不对数据库进行真正的更新。

有效性检查阶段:对事务进行有效性检查,判断是否能够执行write操做而不违反可串行性。若是失败,则回滚该事务。

写阶段:事务已经过有效性检查,则将临时变量中的结果更新到数据库中。

有效性检查一般也是经过对事务的时间戳进行比较完成的,不过和基于时间戳排序的规则不同。

该方法容许可能冲突的操做并发执行,由于每一个事务操做的都是本身工做空间的局部变量,直到有效性检查阶段发现了冲突才回滚。于是这是一种乐观的并发策略。

2.3.4 基于快照隔离的并发控制

快照隔离是多版本并发控制(mvcc)的一种实现方式。

其核心思想是:数据库为每一个数据项维护多个版本(快照),每一个事务只对属于本身的私有快照进行更新,在事务真正提交前进行有效性检查,使得事务正常提交更新或者失败回滚。

因为快照隔离致使事务看不到其余事务对数据项的更新,为了不出现丢失更新问题,能够采用如下两种方案避免:

先提交者获胜:对于执行该检查的事务T,判断是否有其余事务已经将更新写入数据库,是则T回滚不然T正常提交。

先更新者获胜:经过锁机制保证第一个得到锁的事务提交其更新,以后试图更新的事务停止。

事务间可能冲突的操做经过数据项的不一样版本的快照相互隔离,到真正要写入数据库时才进行冲突检测。于是这也是一种乐观并发控制。

2.3.5 关于并发控制技术的总结

以上只是对常见的几种并发控制技术进行了介绍,不涉及特别复杂的原理的讲解。之因此这么作一是要真的把原理和实现细节讲清楚须要涉及的东西太多,篇幅太长,从做者和读者角度而言都不是一件轻松的事,因此只对其实现的核心思想和实现要点进行了简单的介绍,其余部分就一笔带过了。二是并发控制的实现的方式太过多样,基于封锁的实现就有不少变体,mvcc多版本并发控制的实现方式就更是多样,并且不少时候会和其余并发控制方式好比封锁的方式结合起来使用。

3. 故障与故障恢复技术

3.1 为何须要故障恢复技术

数据库运行过程当中可能会出现故障,这些故障包括事务故障和系统故障两大类

事务故障:好比非法输入,系统出现死锁,致使事务没法继续执行。

系统故障:好比因为软件漏洞或硬件错误致使系统崩溃或停止。

这些故障可能会对事务和数据库状态形成破坏,于是必须提供一种技术来对各类故障进行恢复,保证数据库一致性,事务的原子性以及持久性。数据库一般以日志的方式记录数据库的操做从而在故障时进行恢复,于是能够称之为日志恢复技术。

3.2 事务的执行过程以及可能产生的问题

27c8ebdda8d20c4bff6e09a237e6c5bc.png

事务的执行过程能够简化以下:

系统会为每一个事务开辟一个私有工做区

事务读操做将从磁盘中拷贝数据项到工做区中,在执行写操做前全部的更新都做用于工做区中的拷贝.

事务的写操做将把数据输出到内存的缓冲区中,等到合适的时间再由缓冲区管理器将数据写入到磁盘。

因为数据库存在当即修改和延迟修改,因此在事务执行过程当中可能存在如下状况:

在事务提交前出现故障,可是事务对数据库的部分修改已经写入磁盘数据库中。这致使了事务的原子性被破坏。

在系统崩溃前事务已经提交,但数据还在内存缓冲区中,没有写入磁盘。系统恢复时将丢失这次已提交的修改。这是对事务持久性的破坏。

3.3 日志的种类和格式

:描述一次数据库写操做,T是执行写操做的事务的惟一标识,X是要写的数据项,V1是数据项的旧值,V2是数据项的新值。

:对数据库写操做的撤销操做,将事务T的X数据项恢复为旧值V1。在事务恢复阶段插入。

: 事务T开始

: 事务T提交

: 事务T停止

关于日志,有如下两条规则

1.系统在对数据库进行修改前会在日志文件末尾追加相应的日志记录。

2.当一个事务的commit日志记录写入到磁盘成功后,称这个事务已提交,但事务所作的修改可能并未写入磁盘

3.4 日志恢复的核心思想

撤销事务undo:将事务更新的全部数据项恢复为日志中的旧值,事务撤销完毕时将插入一条记录。

重作事务redo:将事务更新的全部数据项恢复为日志中的新值。

事务正常回滚/因事务故障停止将进行redo

系统从崩溃中恢复时将先进行redo再进行undo。

如下事务将进行undo:日志中只包括记录,但既不包括记录也不包括记录.

如下事务将进行redo:日志中包括记录,也包括记录或记录。

假设系统从崩溃中恢复时日志记录以下

因为T0既有start记录又有commit记录,将会对事务T0进行重作,执行相应的redo操做。

因为T1只有start记录,将会对T1进行撤销,执行相应的undo操做,撤销完毕将写入一条abort记录。

3.5 事务故障停止/正常回滚的恢复流程

从后往前扫描日志,对于事务T的每一个形如的记录,将旧值V1写入数据项X中。

往日志中写一个特殊的只读记录,表示将数据项恢复成旧值V1,

这是一个只读的补偿记录,不须要根据它进行undo。

一旦发现了日志记录,就中止继续扫描,并往日志中写一个

日志记录。

2d4b6483478038c85d019376aefff6ba.png

3.6 系统崩溃时的恢复过程(带检查点)

检查点是形如的特殊的日志记录,L是写入检查点记录时还未提交的事务的集合,系统保证在检查点以前已经提交的事务对数据库的修改已经写入磁盘,不须要进行redo。检查点能够加快恢复的过程。

系统奔溃时的恢复过程分为两个阶段:重作阶段和撤销阶段。

重作阶段:

系统从最后一个检查点开始正向的扫描日志,将要重作的事务的列表undo-list设置为检查点日志记录中的L列表。

发现的更新记录或的补偿撤销记录,就重作该操做。

发现记录,就把T加入到undo-list中。

发现或记录,就把T从undo-list中去除。

撤销阶段:

系统从尾部开始反向扫描日志

发现属于undo-list中的事务的日志记录,就执行undo操做

发现undo-list中事务的T的记录,就写入一条记录,

并把T从undo-list中去除。

4.undo-list为空,则撤销阶段结束

总结:先将日志记录中全部事务的更新按顺序重作一遍,在针对须要撤销的事务按相反的顺序执行其更新操做的撤销操做。

3.6.1 一个系统崩溃恢复的例子

恢复前的日志以下,写入最后一条日志记录后系统崩溃

//以前T2已经commit,故不用重作

//T0回滚完成,插入该记录后系统崩溃

ebd17fe0afd47225bc5de8fd8b57c2c1.png

4. 总结

事务是数据库系统进行并发控制的基本单位,是数据库系统进行故障恢复的基本单位,从而也是保持数据库状态一致性的基本单位。ACID是事务的基本特性,数据库系统是经过并发控制技术和日志恢复技术来对事务的ACID进行保证的,从而能够获得以下的关于数据库事务的概念体系结构。

3bd92db591fb605f9ec458e970d995a8.png

5. 参考资料

《数据库系统概论》

《数据库系统概念》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值