赞
踩
在同一个事务内部的一组操作必须全部执行成功(或者全部失败)。
为了保证事务操作的原子性,必须实现基于日志的REDO/UNDO机制:将所有对数据的更新操作都写入日志,如果一个事务中的一部分操作已经成功,但以后的操作,由于断电/系统崩溃/其它的软硬件错误而无法继续,则通过回溯日志,将已经执行成功的操作撤销,从而达到“全部操作失败”的目的。 最常见的场景是,数据库系统崩溃后重启,此时数据库处于不一致的状态,必须先执行一个crash recovery的过程:读取日志进行REDO(重演将所有已经执行成功但尚未写入到磁盘的操作,保证持久性),再对所有到崩溃时尚未成功提交的事务进行UNDO(撤销所有执行了一部分但尚未提交的操作,保证原子性)。crash recovery结束后,数据库恢复到一致性状态,可以继续被使用。
但是,原子性并不能完全保证一致性。在多个事务并行进行的情况下,即使保证了每一个事务的原子性,仍然可能导致数据不一致的结果。 例如,事务1需要将100元转入帐号A:先读取帐号A的值,然后在这个值上加上100。但是,在这两个操作之间,另一个事务2修改了帐号A的值,为它增加了100元。那么最后的结果应该是A增加了200元。但事实上,事务1最终完成后,帐号A只增加了100元,因为事务2的修改结果被事务1覆盖掉了
为了保证并发情况下的一致性,引入了隔离性,即保证每一个事务能够看到的数据总是一致的,就好象其它并发事务并不存在一样。
数据库四种隔离级别,以及常见的几种读异常,大家应该都是耳熟能详的,但数据库底层是怎么实现隔离性的呢?都采用了哪些技术呢? 主要有两个技术:MVCC(多版本并发控制)和锁。
(1)MVCC(多版本并发控制)
多版本并发控制,顾名思义,在并发访问的时候,数据存在版本的概念,可以有效地提升数据库并发能力,常见的数据库如MySQL、MS SQL Server、IBM DB2、Hbase、MongoDB等等都在使用。
简单讲,如果没有MVCC,当想要读取的数据被其他事务用排它锁锁住时,只能互斥等待;而这时MVCC可以通过提供历史版本从而实现读取被锁的数据的历史版本,从而避免了互斥等待。
InnoDB采用的MVCC实现方式是:在需要时,通过undo日志构造出历史版本。
(2)锁
1) 锁的分类
若事务T对数据对象A加上S锁,则事务T只能读A;其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
若事务T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。它防止任何其它事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁。
注意:排他锁会阻止其它事务再对其锁定的数据加读或写的锁,但是不加锁的就没办法控制了。
行锁,顾名思义,是加在索引行
(对!是索引行!不是数据行!)上的锁。比如select * from user where id=1 and id=10 for update
,就会在id=1
和id=10
的索引行上加Record Lock。
间隙锁,它会锁住两个索引之间的区域。比如select * from user where id>1 and id<10 for update
,就会在id为(1,10)的索引区间上加Gap Lock。
也叫间隙锁,它是Record Lock + Gap Lock形成的一个闭区间锁。比如select * from user where id>=1 and id<=10 for update
,就会在id为[1,10]的索引闭区间上加Next-Key Lock。
这样组合起来就有,行级共享锁,表级共享锁,行级排它锁,表级排它锁
之后,在每条SQL语句执行的时候,根据隔离级别判断是不是要使用一个新的快照,如果是可重复读,则不使用新快照,沿用老的快照,这样就能保证所有的读操作看到的是同一个数据状态;同时也确保了读已提交隔离级别下一个事务块内的不同语句的读操作看到的不是同一个数据状态。
在这个事例中,涉及到了两个事务(程序员事务和妻子事务),当程序员事务开启时,收费系统读取程序员卡里钱的操作还没完成,此时妻子这个事务就将卡里的钱进行了转账,即对数据进行了修改,导致收费系统两次读取到的数据不一样。出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读,这是由于数据更新导致的,不能重复读取相同的数据。
事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,那么事务A再读该数据,读到的还是原来的内容。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。