一、最简单的例子来说明事务
“A账户向B账号汇钱”来说明事务
1、从A账号中把余额读出来。
2、对A账号做减法操作。
3、把结果写回A账号中。
4、从B账号中把余额读出来。
5、对B账号做加法操作。
6、把结果写回B账号中。
为了数据的一致性,这6件事,要么都成功做完,要么都不成功。而且这个操作的过程中。对A、B找好的其他访问必须锁死,所谓锁死就是要排除其他的读写操作,不然会有脏数据问题,这就是事务。
二、数据库事务特性
-
Atomic(原子性)
事务中包含的操作被看做一个逻辑单元,这个逻辑单元中的操作要么全部成功,要么全部失败。事务的原子性也体现在事务对数据的读取上,例如一个事务对同一数据项的多次读取的结果一定是相同的。
-
Consistency(一致性)
只有合法的数据可以被写入数据库,否则事务应该将其回滚到最初的状态。
事务需要保持数据库的正确性、完整性和一致性。
有些时候这种一致性由数据库的内部规则保证,例如数据的类型必须正确,数据值必须在规定的范围内,等等。
另外一些时候这种一致性由应用保证的,例如,** 一般情况下银行账务余额不能是负数,信用卡消费不能超过该卡的信用额度等。** -
Isolation(隔离性)
事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性和完整性。
同时,并行事务的修改必须与其他并行事务的修改相互独立。
事务的隔离性一般由事务的锁来进行控制。许多时候数据库在并发执行多个事务,每个事务可能需要对多个表进行修改和查询,与此同时,更多的查询请求可能要在执行。数据库需要保证每一个事务在它的修改全部完成之前,对其他的事务是不可见的。
换句话说,不能让其他事务看到该事务的中间状态,例如,从银行账户A转一比款项a到账户B,不能让其他事务(例如账户查询)看到A账户已经扣除款项a但B账户却没有增加款项a的状态。
-
Durability(持久性)
事务结束后,事务处理的结果必须能够得到固化,即使系统出现各种异常也是如此。
三、数据库的隔离级别
由于性能的考虑,许多数据库允许使用牺牲隔离属性来换取并发度,从而获取性能的提升。
Read uncommitted(RU):读取未提交的数据(未授权读取),即其他事务已经修改单未commit的数据,这是最低的隔离级别。允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。
Read committed(RC):允许不可重复读取,在一个事务中,对同一个项,前面的读取跟后面的读取结果可能不一样。例如第一次读取时另一个事务的修改还没有提交,第二次读取时已经提交了。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
Repeatable read(RR):可重复读取,在一个事务中,对同一个项,前面的读取跟后面的读取结果一样。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
Serializable(S):可序列化,即数据库的事务市可串行化执行的,就像一个事务执行的时候没有别的事务同时在执行,这是最高的隔离级别。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
隔离级别的降低可能导致读取到脏数据或事务执行异常
Lost update(LU) :两个事务同时修改一个数据项,但后一个事务中途失败退出,则对数据项的两个修改可能都丢失。
Dirty Reads(DR): 一个事务读取某数据项,但另一个事务更新了此数据项却没有提交,这样所有的操作可能都得回滚。
Non-repeatable Reads(BRR):一个事务对同一数据项的多次读取可能得到不同的结果。
Second lost updates problem(SLU):无法重复读取的特例,两个并发事务同时读取和修改同一数据项,则后面的修改可能使得前面的修改失效。
Phantom Reads(PR):也称为幻读,例如在事务执行过程中,由于前面的查询和后面的查询的期间有另外一个事务插入数据,后面的查询结果中未出现的数据。
隔离级别与读写异常(不一致)的关系如下
LU | DR | NRR | SLU | PR | |
---|---|---|---|---|---|
RU | Y | Y | Y | Y | Y |
RC | N | N | Y | Y | Y |
RR | N | N | N | N | Y |
S | N | N | N | N | N |
四、数据库的事务与线程相似的地方
- 线程之间共享同一片资源,而事务共享的则是数据库内部的数据
- 多线程的意义在于并发执行,提高效率;事务并发执行也能提高程序与数据库交互的效率。
五、用例子来说明事务隔离性
假设账户A有1000元,B有1000元,C有1000A
1、操作员u1执行一次转账事务m1
从A转移500元到B,再从A的余额中转移50%元平均分配到 A B C D E余额中
2、操作员u2执行一次转账事务m2
从B转移1000元到A
3、操作员u3执行一次转账事务m3
从A转移200元到B
4、操作员u4开户D
账户表为T_C,其包含字段为 账户名称cname 余额money
记录为{A,1000},{B,1000}
事务m1的操作包括
读A,读B,
写A,写B,
提交AB,读A,
读C,写C,写C,
提交AC
事务m2的操作包括
读B,读A,
写B,写A,
提交AB
事务m3的操作包括
读A,读B,
写A,写B,
提交AB
事务m4的操作包括
写D,
提交D
1.若未授权读取ReadUncommitted
m1读A
,B
,写了A
但没写B
此时m2不可以写B,可以读取A
和B
,但是B
是脏读。
隔离级别使用了“排他写锁”。
2.若授权读取ReadCommitted
m1读A
,B
,写了A
但没写B
此时m2不可以写B
,可以读取A
,不能读取B
,因为B
是脏读。
隔离级别使用了“排他写锁”。
m1读写了A
,B
,提交A``B
,
m3提交了A``B
此时m1准备第二次A是允许的。
隔离级别使用了“瞬间共享读锁”。
(但由于第二次读产生了不可重复读的问题,事务1脱力了元自行,因为逻辑上看事务1中被插入了3,影响了A的余额50%的计算。)
3.若可重复读取RepeatableRead
m1读A
,B
,写了A
。
但没写B
此时m2不可以写B
,可以读取A
,不能读取B
,因为B
是脏读。
隔离级别使用了“排他写锁”。
m1读写了A
,B
,提交A``B
,
m4提交了D
此时m3是能读不能写A
并更新提交的。
此时m4是能读能能插入D
的。隔离级别使用了“共享读锁”。
(和ReadCommitted比RepeatableRead区别对已经提交的事务可以进行读,但不能写,但是同一张表可以插入新的记录。)
4.序列化Serializable
任何事务都只能等前一事务完全执行完再执行。 但是失去了并发性。
转载,有修改,我只是个搬运工。
参考
事务的ACID
分布式系统的事务处理
关于数据库事务、隔离级别、锁的理解与整理