1.说明
事务,是一个把若干读写放入同一个逻辑单元的方式。
概念上,事务的读写要么全成功(commit),要么全失败(abort,rollback)。即使失败,程序也能安全的重试。
有了事务,处理错误会变得很简单,因为不用担心部分错误的问题。
有了事务,db也会有安全保障,应用程序可以忽视特定的错误场景以及并发的问题。
实际上,不是所有应用都需要事务,那么怎么知道自己需不需要事务,这就要理解事务能够提供的安全保证以及对应的代价。下面看一系列问题,如并发控制,多种竞争的触发以及db实现的隔离级别
2.概念
如今几乎所有的关系db以及部分非关系db都支持事务
部分非关系db默认提供分区,备份的支持,而这种场景下,事务则成了受害者。这些新生代的db放弃了食物或者提供了非常弱的保证。
如今关于分布式db以及事务的关系,有两种观点
第一种观点是事务制约了拓展性,任何大型系统应该抛弃事务来保证高可用性以及效率。
另一种观点认为,db必须提供事务的保证,完成对“有价值数据”的某些处理。
两种观点都夸张了。
实际上,所有技术选型都有优势以及制约,稍后会深入细节讲解事务的trade-off.
2.1 ACID
事务提供的安全保证,常称为ACID,即原子性,隔离性,一致性,持久性。这是1983年Andreas Reuter和 Theo Härder提出的。
然而,不同db对他们的实现各不一样,甚至对于I(隔离性)都有不同的理解。
不幸的是,ACID几乎成了一个市场用语。
Atomicity
广义来说,原子性表示一个东西不能够拆解的更细了。
在事务中,原子性表示:
所有事务的所有操作要么都成功,要么都失败。
不会因为某些原因(断电,网络中断)等原因造成数据处于部分成功,部分失败的状态。
这使得失败的事务可以进行重试,而不会使得数据出现duplicate或者错误。
Consistency
一致性这个词到处都在用
备份一致性(如最终一致性)
一致性hash
CAP理论(后续第9章再介绍)
然而在事务中,一致性表示:
在应用的层面上,数据处于一个正确的状态
ACID中,AID其实都是db的特性。然而C(一致性),是一个应用程序的性质
应用程序需要db的原子性和隔离性,来达到应用程序的一致性
比如一个会计系统,数据要保证收支平衡。这是程序自己保证的。
如果程序自己写入收支不平衡的错误数据,db并不能阻止。
Isolation
多个client同时访问db的同一份数据时,会出现并发竞争问题。
如下图实例,两个client获取当前值并进行+1操作
图1
由于出现了竞争,数据本该从42变成44,结果变成了43
隔离,表示同时执行的事务之间互不干扰。
db保证并发执行的事务的结果要和他们串行执行的结果一样.
实现中,串行化的隔离基本不用,因为代价很高。有些db如Oracle中会用快照隔离,是比串行化隔离弱一点的隔离级别,后面会讲
Durability
持久性是一个承诺,一旦事务成功提交,它所写的任何数据将不会丢失,即使有硬件故障或数据库崩溃。
在单节点数据库中,持久性通常意味着数据已写入非易失性存储(如硬盘驱动器或SSD)。它通常还需要写入日志,以便出现文件损坏时恢复工作。
在分布式数据库中,持久性可能意味着数据已成功复制到一些节点上。
当然,完美的持久性是不存在的,比如所有备份,硬盘同时被损坏。
2.2 单obj以及多obj的操作
ACID中的AI描述了一个事务中包含多个写时,db应该处理的事情
原子:提供要么全成功要么全失败的保证,不会出现中间状态
隔离:并发执行的事务互不影响,一个事务要么看到另一个事务的所有写,要么看不到任何写
上述定义假定多个事务在同时修改多个obj(记录)
下图以收发邮件为例,查找收件人未读的邮件,以及从邮箱中查找未读邮件的个数
上图违背了事务的隔离性,是不满足ACID要求的。称之为脏读
多对象的事务请求 要求有一种方式决定哪些读写属于同一个请求,这可以通过client的tcp链接知道
在BEGIN TRANSACTION以及COMMIT之间的语句都被认为是同一个事务
很多非关系型db对于原子性支持不好(即使有multi operation)
会存在部分数据更新成功但是部分数据失败的情况
单obj的写
原子性能够用崩溃恢复日志实现
隔离性能用对象锁实现
有些db提供复杂的原子操作,利用自增操作解决图1中的read-modify-write的问题
类似的是CAS操作,即compare and set,这里不展开
这些操作十分有用,能够阻止多个client同时写一个obj时出现的lost updates问题,后面会介绍
对于 多obj的写 的需求
现状:
很多分布式存储放弃了多obj的事务,因为在分区中太难实现了,另外还会影响性能。是否需要多obj的写:
很多场景单object操作就够了,但是也有场景要处理多object,即外键,二级索引等等。
这些场景当然也可以不用事务实现,但是缺乏了原子性和隔离性会有下述后果
缺乏原子性,错误处理变得更复杂。
缺乏隔离性,会出现并发问题。下一节会介绍Weak Isolation levels。
2.3 处理错误以及丢弃错误
事务的特性决定了 它不会出现执行了一半的情况。
现状:
不是所有系统遵从这个规矩,比如在无leader模型中,会尽量执行,如果遇到了错误他们也不会回滚,由应用程序自行从错误中恢复。
错误最终会发生,但是很多开发缺盲目乐观,忽视了对于错误的错综复杂的处理。
如何处理:
事务出错之后,重试是一种简单有效的方法,但是并不完美
1.执行成功但是网络原因导致重试,会执行两次
2.如果负载压力过大,重试会加重负载
3.短暂的错误之后重试才有用(如死锁,网络中断等),永久的错误是不值得重试的
4.如果事务对db以外的部分有影响,如发送邮件,这个影响可能会持续,即使事务已经被丢弃了
5.如果client重试失败了,所有写的数据都会丢失
名词总结
主要涉及名词如下,在本章内容都会有详细介绍
ACID
快照隔离
脏读
read-modify-write
lost updates
Weak Isolation levels
思考
无leader模式是不支持事务的:
因为无法回滚,保证不了原子性图1的例子中,两个事务之间怎么就互相干扰了呢?
可以理解为,第一个事务开始执行却还没有commit时,第二个事务也处于了同样的状态。
出现了race condition错误的解决方式是重试,重试的代价如何
文章上面有写
总结
本章介绍了事务以及ACID的定义
对于单obj以及多obj的操作,现状进行了一定的展开
对于错误处理以及重试的代价进行了分析
暂时不知道的问题
是否有分布式事务
事务和分区,备份是怎样的结合,能全部结合还是部分结合
zk的multi op与事务的关系,以及支持
网友评论