隔离级别定义了数据库系统中一个操做产生的影响何时以哪一种方式能够对其余并发操做可见,隔离性是事务的ACID中的一个重要属性,核心是对锁的操做。html
共享锁(Shared Lock)mysql
读锁,保证数据只能读取,不能被修改。
若是事务A对数据M加上S锁,则事务A能够读记录M但不能修改记录M,其余事务(这里用事务B)只能对记录M再加上S锁,不能加X锁,直到事务A释放了记录M上的S锁,保证了其余事务(事务B)能够读记录M,但在事务A释放M上的S锁以前不能对记录M进行任何修改。程序员
例子: MySql 5.5 证实S锁的特性。sql
数据准备数据库
CREATE TABLE `test1` (`id` bigint(1) NOT NULL DEFAULT 0 ,`name` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,PRIMARY KEY (`id`))
INSERT INTO test1 VALUES(1,1),(2,2),(3,3)
设置事务为手动提交,方便证实上诉结论(mysql默认为自动提交,执行单句sql其实包含 开启事务,执行sql,提交事务,3个步骤)。微信
客户端A开启一个事务A。并发
客户端A给test1表加上读锁。性能
查询test1表原有的数据(加上S锁后,能够读数据)。学习
事务A修改数据(加上S锁后,没法修改数据)。spa
B客户端开启事务B。
事务B对记录M查询(由于是S锁,其余事务能够对记录A进行select )。
事务B对记录M加读锁(事务A对记录A加上S锁后,事务B一样也能够对记录A加上S锁,证实了,mysql里的读锁就是S锁,具备共享)。
事务B对记录M加写锁(一直处于等待状态,被挂起,具体挂起的缘由能够见:Innodb行锁源码学习。
事务B对记录M修改(一直处于等待状态,被挂起)。
事务A释放M上的S锁。
此时事务B才获得响应。
说明了,只有释放了读锁,另一个事务才能加写锁,或者更新数据。
排他锁(X 锁)
写锁,若事务A对数据对象M加上X锁,事务A能够读记录M也能够修改记录M,其余事务(事务B)不能再对记录M加任何锁,直到事务A释放记录M上的锁,保证了其余事务(事务B)在事务A释放记录M上的锁以前不能再读取和修改记录M。
例子:Mysql 5.5,证实X锁的特性。
客户端A设置手动提交,而且开启事务A。
客户端B设置手动提交,而且开启事务B。
事务A给记录M加上X锁。
事务A能够读记录M也能够修改记录M。
事务B不能对记录M加任何锁。
事务B也不能对记录M进行查询和修改。
事务A释放记录M上的X锁。
事务B阻塞的进程被执行,中断了6秒。
悲观锁(Pessimistic[ˌpesɪˈmɪstɪk] Lock)
对数据被外界修改保持保守态度,在整个数据处理过程当中,数据处于锁定状态,依赖于数据库提供的锁机制。
乐观锁(Optimistic[ˌɑ:ptɪˈmɪstɪk]Lock)
采用宽松的加锁机制,基于数据版本记录机制,具体作法:数据库表增长一个"version"字段来实现,读取数据时,将版本号一同读出,以后更新,对版本号加1,将提交数据的版本数据与数据库对应记录的当前版本信息进行比对,若是提交的数据版本号大于数据库的数据,则予以更新,不然,被认为是过时数据。
事务A和事务B,同时得到相同数据,而后在各自的事务中修改数据M,事务A先提交事务,数据M假如为M+,事务B后提交事务,数据M变成了M++,最终结果变成M++,覆盖了事务A的更新。
例子:
事务A | 事务B |
---|---|
读取X=100 | 读取X=100 |
写入X=X+100 | |
事务结束X=200 | |
写入X=X+100 | |
事务结束X=300(事务A的更新丢失) |
容许事务B能够读到事务A修改而未提交的数据,可能会形成了脏读(脏读本质就是无效的数据,只有当事务A回滚,那么事务B读到的数据才为无效的,因此这里只是可能形成脏读,当事务A不回滚的时候,事务B读到的数据就不为脏数据,也就是有效的数据,脏数据会致使之后的操做都会发生错误,必定要去避免,不能凭借侥幸,事务A不能百分之百保证不回滚,因此这种隔离级别不多用于实际应用,而且它的性能也不比其余级别好多少)。
例子:
事务A | 事务B |
---|---|
写入X=X+100(x=200) | |
读取X=200(无效数据,脏读) | |
事务回滚X=100 | |
事务结束X=100 | |
事务结束 |
不可重复读是指在一个事务范围中2次或者屡次查询同一数据M返回了不一样的数据,例如:事务B读取某一数据,事务A修改了该数据M而且提交,事务B又读取该数据M(多是再次校验),在同一个事务B中,读取同一个数据M的结果集不一样,这个很蛋疼。
例子:
事务A | 事务B |
---|---|
读取X=100 | 读取X=100 |
写入X=X+100 | 读取X=100 |
事务结束,X=200 | |
读取X=200(在一个事务B中读X的值发生了变化) | |
事务结束 |
当用户读取某一个范围的数据行时,另外一个事务又在该范围内查询了新行,当用户再读取该范围的数据行时,会发现会有新的“幻影行”,例如:事务B读某一个数据M,事务A对数据M增长了一行并提交,事务B又读数据M,发生多出了一行形成的结果不一致(若是行数相同,则是不可重复读)。
例子:
事务A | 事务B |
---|---|
读取数据集M(3行) | |
在数据集M插入一行(4行) | |
事务结束 | |
读取数据M(4行) | |
事务结束 |
在事务B里,同一个数据集M,读到的条数不一致(新增,删除)。
在运用S锁和X锁对数据M加锁的时候,须要约定一些规则,例如什么时候申请S锁或者X锁,持锁时间,这些规则就是封锁协议。
其中不一样的封锁协议对应不一样的隔离级别。
一级封锁协议对应READ-UNCOMMITTED 隔离级别,本质是在事务A中修改完数据M后,马上对这个数据M加上共享锁(S锁)[当事务A继续修改数据M的时候,先释放掉S锁,再修改数据,再加上S锁],根据S锁的特性,事务B能够读到事务A修改后的数据(不管事务A是否提交,由于是共享锁,随时随地都能查到数据A修改后的结果),事务B不能去修改数据M,直到事务A提交,释放掉S锁。
缺点:
可能会形成以下后果
丢失更新。
脏读。
不可重复读。
幻读。
例子:MySql 5.5 证实一级封锁协议会形成脏读,不可重复读。
A客户端修改数据M,B客户端设置不一样的隔离级别去查看数据M,论证该级别下会发生脏读,不可重复读(至关于客户端A修改的数据已经写到表里,客户端B传不一样版本号[隔离级别],去查看数据M,所得的查询结果也不一样)。
客户端A设置手动提交,而且开启事务A。
客户端B设置手动提交,修改事务隔离级别为read-uncommitted,而且开启事务B(必定要在开启事务前修改事务的隔离级别,否则当前仍是保持着原来的事务隔离级别,直到当前事务提交)。
事务B查询数据M。
事务A修改其中一行数据(查询原有基础数据,而后把id = 1 的name 修改成4 )。
事务B查看数据M,发现事务B读到了事务A未提交的数据,发生了脏读。
事务A回滚。
客户端B查询的状况。
在同一个事务B里,查询同一个数据M,竟然2次不同,形成不可重复读,其中有一次数据是无效的数据,脏读了。
假如事务A不回滚呢? 那么事务B就没形成脏读,不可重复读。
例子:MySql 5.5 证实一级封锁协议会形成更新丢失
事务A提交数据M。
事务B查询数据M,事务B查询的数据M,没有脏数据,而且2次结果一致,没出现不可重复读。
事务B修改数据M。
此时事务A对数据M的修改被事务B给覆盖,形成了更新丢失。
例子:MySql 5.5 证实一级封锁协议会形成幻读
客户端A设置手动提交,而且开启事务A。
客户端B设置手动提交,修改事务隔离级别为read-uncommitted,而且开启事务B(必定要在开启事务前修改事务的隔离级别,否则当前仍是保持着原来的事务隔离级别,直到当前事务提交)。
事务B查询数据M。
事务A插入一条数据。
事务B查询数据M。
事务B第二次查询的时候,数据M多了一行,像是发生了幻觉似的,有可能这一行是无效数据(当事务A回滚)。
二级封锁协议对应READ-COMMITTED隔离级别,本质是事务A在修改数据M后马上加X锁,事务B不能修改数据M,同时不能查询到最新的数据M(避免脏读),查询到的数据M是上一个版本(Innodb MVCC快照)的。
优势:
1.避免脏读。
缺点:
可能会形成以下后果
丢失更新。
不可重复读。
幻读。
例子:MySql 5.5 证实二级封锁协议不会形成脏读,可是会形成不可重复读(幻读,丢失更新,和上面证实方式同样,这里暂不证实了)
A客户端修改数据M,B客户端设置不一样的隔离级别去查看数据M,论证该级别下会发生不可重复读(至关于客户端A修改的数据已经写到表里,客户端B传不一样版本号[隔离级别],去查看数据M,所得的查询结果也不一样)。
客户端A设置手动提交,而且开启事务A。
客户端B设置手动提交,修改事务隔离级别为read-committed,而且开启事务B(必定要在开启事务前修改事务的隔离级别,否则当前仍是保持着原来的事务隔离级别,直到当前事务提交)。
事务A修改其中一行数据(查询原有基础数据,而后把id = 1 的name 修改成4 )。
事务B查询数据M,数据仍是和以前的同样(没有发生脏读),事务B读不到了事务A未提交的数据。
事务A提交。
事务B查询数据,数据M被修改。
在同一个事务B中,查询数据M,2次结果不一致,证实发生了不可重复读。
三级封锁协议对应REPEATABLE-READ隔离级别,本质是二级封锁协议基础上,对读到的数据M瞬间加上共享锁,直到事务结束才释放(保证了其余事务没办法修改该数据),这个级别是MySql 5.5 默认的隔离级别。
优势:
1.避免脏读。
2.避免不可重复读。
缺点:
幻读。
丢失更新。
例子:MySql 5.5 证实三级封锁协议不会形成脏读,不可重复读(形成幻读,丢失更新,和上面证实方式同样,可是要在非mysql数据库上证实,后面有解释,这里暂不证实了)
客户端A设置手动提交,而且开启事务A。
客户端B设置手动提交,修改事务隔离级别为repeatable-read,而且开启事务B(必定要在开启事务前修改事务的隔离级别,否则当前仍是保持着原来的事务隔离级别,直到当前事务提交)。
事务A查询数据M。
事务B查询数据M。
事务A更新数据M。
事务B查询数据M,发现查询的结果没变化,避免了脏读。
事务A提交事务。
事务B查询数据M,仍是和以前查询的结果同样,没有变化,避免了不可重复读。
事务B提交事务
这个时候事务B才能查询到最新的数据M+。
例子:MySql 5.5 证实Mysql Innodb引擎的三级封锁协议不会形成幻读
mysql innodb的reapetable read级别是避免了幻读,mysql的实现和标准定义的RR隔离级别有差异,详情见 how-to-produce-phantom-reads。
客户端A设置手动提交,而且开启事务A。
客户端B设置手动提交,修改事务隔离级别为repeatable-read,而且开启事务B(必定要在开启事务前修改事务的隔离级别,否则当前仍是保持着原来的事务隔离级别,直到当前事务提交)。
事务A查询数据M。
事务B查询数据M。
事务A对记录M插入一条数据。
事务A提交。
事务B查看数据M。
看不到事务A新增长的一条数据,说明避免了幻读。
事务B插入一条记录。
明明刚刚查询到没有ID为4的,如今竟然插不进去,哈哈哈哈哈。
例子:MySql 5.5 证实三级封锁协议在读到数据的瞬间加上共享锁,等事务结束才释放以及三级封锁协议会形成更新丢失
客户端A从新开启事务A。
客户端B设置手动提交,修改事务隔离级别为read-committed,而且开启事务B(必定要在开启事务前修改事务的隔离级别,否则当前仍是保持着原来的事务隔离级别,直到当前事务提交)。
事务A修改数据M。
事务B也修改数据M,事务B的修改进程被挂起,由于事务A在对数据M修改后瞬间加上了共享锁,对于其余事务只能读。
事务A提交事务。
事务B的修改进程被唤起(等待4.98秒)。
事务B提交修改。
最终事务A,事务B查询数据M。
事务B的修改把事务A的修改给覆盖了,形成了更新丢失。
最强封锁协议对应Serialization隔离级别,本质是从MVCC并发控制退化到基于锁的并发控制,对事务中全部读取操做加S锁,写操做加X锁,这样能够避免脏读,不可重复读,幻读,更新丢失,开销也最大,会形成读写冲突,并发程度也最低。
例子:MySql 5.5 证实三级封锁协议不会形成幻读
客户端A从新开启事务A
客户端B设置手动提交,修改隔离级别为SERIALIZABLE,而且开启事务B。
事务A插入数据,而且提交。
事务B查询数据,依然仍是以前的数据,避免的幻读。
事务A修改数据,被挂起。
证实了Serialization级别下写操做是对数据M加的是X锁。
ANSI SQL 隔离级级别
隔离性 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 | 加锁读 |
---|---|---|---|---|
READ-UNCOMMITTED | Y | Y | Y | N |
READ-COMMITTED | N | Y | Y | N |
REPEATABLE-READ | N | N | Y | N |
SERIALIZABLE | N | N | N | Y |
感谢您的耐心阅读,若是您发现文章中有一些没表述清楚的,或者是不对的地方,请给我留言,您的鼓励是做者写做最大的动力,
若是您认为本文质量不错,读后以为收获很大,不妨请我喝杯咖啡,让我更有动力继续写出高质量的文章。
支付宝
微信
做 者 : @mousycoder
原文出处 : http://mousycoder.com/2016/02/19/explain-transaction-in-simple-language-3/
创做时间:2016-2-19
更新时间:2016-2-22