玄幻小说排行榜百度风云榜,如何做企业网站优化,亿牛网,群晖网站建设处理错误500关于相关术语的专业解释#xff0c;请自行百度了解#xff0c;本文皆本人自己结合参考书和自己的理解所做的阐述#xff0c;如有不严谨之处#xff0c;还请多多指教。
**不可重复读的重点是修改: **同一事务#xff0c;两次读取到的数据不一样。
幻读的重点在于新增或者…关于相关术语的专业解释请自行百度了解本文皆本人自己结合参考书和自己的理解所做的阐述如有不严谨之处还请多多指教。
**不可重复读的重点是修改: **同一事务两次读取到的数据不一样。
幻读的重点在于新增或者删除同样的条件 , 第 1 次和第 2 次读出来的记录数不一样
脏读 强调的是第二个事务读到的不够新。
事务有四种基本特性叫ACID它们分别是
Atomicity-原子性Consistency-一致性Isolation-隔离性Durability-持久性。 接着关于ACID的理解和隔离性语法都是转的网上资料大家可以顺便再了解熟悉下。
1、原子性Atomicity事务开始后所有操作要么全部做完要么全部不做不可能停滞在中间环节。事务执行过程中出错会回滚到事务开始前的状态所有的操作就像没有发生一样。 2、一致性Consistency事务开始前和结束后数据库的完整性约束没有被破坏 。比如A向B转账不可能A扣了钱B却没收到。 3、隔离性Isolation同一时间只允许一个事务请求同一数据不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱在A取钱的过程结束前B不能向这张卡转账。 4、持久性Durability事务完成后事务对数据库的所有更新将被保存到数据库不能回滚。 而其中的隔离性特点说的就是在并发的多个事务中事务之间是互不影响的这种情形。Mysql里支持四种不同的隔离级别这也为解决并发问题提供了选择。 为了更好的理解隔离级别我们需要给每个会话设置不同的隔离级别从而辅助自己实践。
相关语法
SET GLOBAL TRANSACTION ISOLATION LEVEL ;
SET SESSION TRANSACTION ISOLATION LEVEL ;
SET TRANSACTION ISOLATION LEVEL ;
上面语法设置的值选项就是mysql的四种隔离级别
READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE 由于mysql默认隔离级别是可重复读Repeatable Read
show variables like %tx_isolation%; //查询数据库当前的隔离级别
所以实践过程中咱们需要给会话设置隔离级别就如下所示
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED 其中全局事务(global transaction)的隔离级别设置,是对现有已经建好的会话是没有影响的。
set global tx_isolationREAD-COMMITTED
select tx_isolation;
show variables like tx_isolation; 注意设置的全局默认事务隔离级别适用于从设置时起所有新建立的会话连接。现有连接不受影响。 有关实践过程这里不再赘述请参考这位博主的文章MySQL的四种事务隔离级别 接着主要着重帮助自己加强对脏读、脏写、可重复读、更新丢失、幻读、写偏离等的理解。
在此先上一张隔离级别的对比图 图中红框仅表示提醒 脏读
如果一个事务A向数据库写了数据但事务还没提交或终止另一个事务B就看到了事务A写进数据库的数据这就是脏读。 经过前面的实践就能得知在读未提交(Read Uncommitted)隔离级别下是会出现脏读的。 仔细体会读未提交(Read Uncommitted)隔离级别的命名--读取事务还未提交的数据就会发现说的就是脏读。 脏读会导致什么问题呢
1. 给用户带来数据混乱的感觉。
例如在一个多对象的事务A里A需要生成一条邮件发送记录同时需要在用户未读取邮件的计数里1这里涉及两张表的业务情形就是对多对象的诠释。如果事务A insert邮件发送记录时还没执行计数1这个后面的操作就被事务B查询了可事务B此时看到的邮件计数还是1之前的这样就会导致事务B看到的未读取邮件条数与计数数据不一致。 2. 让用户看到根本不存在的数据。
例如事务A 是转账业务由乔峰转给段誉在更新段誉账户余额时假定此时事务A还未提交事务B下段誉正查询自己账户余额发现乔峰给自己转账了。可是事务A下乔峰突然意识段誉是大理太子家底丰实得很于是撤销了转账事务A回退或终止)。整个过程段誉就看到了根本不存在的转账记录。本以为钱来了结果还没眨眼就没了你说用户是段誉生不生气。 脏写
当两个事务同时尝试去更新某一条数据记录时就肯定会存在一个先一个后。而当事务A更新时事务A还没提交事务B就也过来进行更新覆盖了事务A提交的更新数据这就是脏写。 文上提到的4种隔离级别下都不存在脏写情况。因为在这些隔离级别下当两个事务A和B尝试去更新同一条数据时假定A先更新数据会对更新的数据行记录加上排他锁(也叫写锁悲观锁)除非事务A提交或终止从而释放排他锁否则事务B都是无法更新数据的。设计数据密集型应用只是说读提交隔离级别一定可以杜绝脏写问题并未提到读未提交隔离级别经过实践读未提交下事务B的更新操作也是需要等待事务A的排他锁释放才得以执行 脏写会带来什么问题呢
脏写是会导致更新丢失的一种情形具体会带来什么问题可看后面的更新丢失这块内容。 可重复读
我本来认为不可重复读之下的结果也正是所谓的正确结果也就没必要去避讳。就如下图里的Alice只要再查询下Account1下的余额就可以拼出正确的总额600400 1000。 如图所示假设转账行为是由银行操控的Alice一开始看两个账户的总额是500500后又无意看到Account2账户变为了400这种Alice纳罕总额怎么从1000变为了900的现象我们就称之为读偏离(read skew)。但我们知道Alice的Account2下的钱的的确确是400并没有说少了的100元被谁给私吞了。所以说这种现象勉强还是可以接受的毕竟Alice的钱也没变少只要再查询一次Account1就能释疑了。 那Mysql为啥默认级别是可重复读呢不是读提交呢说明可重复读还是有非常的必要。通过以下几点可以看出
1. mysql分布式多节点同步数据时可重复读可以保证多个节点数据的一致性。具体请参考下图 图中有两个从库A和B主库同步数据时会有多个事务并发的执行由于不可重复读的特点就会导致从库A同步到的数据里我的余额是100元而从库B里我的余额数据是90元从而导致AB两个从库之间以及主库和从库A之间数据的不一致。 2. 备份数据库时不可重复读会导致备份一部分是旧数据一部分是更新后的新数据从这样的备份来恢复数据就会导致数据的不一致例如钱变少了此点本人也不是很清楚大概了解即可。 3.对于分析查询需要的就是遍历大量数据来进行分析和数据的完整性检查。如果是不可重复读就会导致一前一后数据不一致影响到分析结果。 更新丢失
当多个事务并发写同一数据时先执行的事务所写的数据会被后写的覆盖这也就是更新丢失。前面的脏写情形就属于会导致更新丢失问题的一种情形。
除了这个更新丢失主要发生在read-modify-write类型的事务当中就是要先查询数据然后计算新的数据最后写回新的数据。下面是几个具体的情形例子
1. 数值更新例如计数或账户余额更新先要查询当前值再计算出要更新的值最后执行更新操作写进数据库
2. 更新一个复杂的数据例如要往json对象里添加数据。先查询获取json对象数据进行解析再添加数据得到新的数据写回数据库
3. 两个用户同时编辑Wiki保存wiki内容。 结合读未提交和读提交的区别就可知道带来更新丢失的根本原因 在读提交以及更高级的隔离级别下只要事务A没有提交事务B永远也无法查到事务A所做的更新从而事务B在计算要更新的数据时必定忽略掉了事务A所产生的变更。 在读未提交下实践只要事务B的查询操作是发生在事务A的更新操作之后就不会有更新丢失问题。但前提是要保证事务B的查询操作是发生在事务A的更新操作之后。这很难控制所以说读未提交下也是需要应对更新丢失问题的。 针对这个问题数据库给了一些解决方法
1.原子写操作。
就是将上面的read-modify-write情形下的3步骤直接转化为1个步骤来执行。下面就是两种情形的mysql语句对比
原子写操作(atomic write operations):
update news set counter counter 1 where id 1;
查询-计算-更新(read-modify-write):
select counter from news;
new_counter counter 1 //此行逻辑由程序语言代码(php,java等)执行
update news set counter new_counter where id 1;
用过PHP框架的就知道框架原本支持的都采用查询-计算-更新这种方式下面是phalcon框架的例子
use PhalconMvcModelTransactionFailed as TxFailed;
use PhalconMvcModelTransactionManager as TxManager;$m new TxManager();
$t $m-get();$model_wallet new Wallet();
$row $model_wallet-findFirst(user_id.$uid.);$model_wallet-setTransaction($t);
$row-money $row-money 100;
$row-operation_time date(Y-m-d H:i:s);if(!$row-save()){$t-rollback();
}else{$t-commit();
}
所以使用框架时就要注意这种写法在并发情形下带来的潜在数据更新丢失的问题。 2.加锁(Explicit locking,显示锁定)
通过for update来给即将更新的数据记录添加锁。也就是说事务A下执行查询时用select for update那么事务B下的select for update就无法进行只有等待事务A提交或终止事务B才得以进行。这样就相当于将并发的两个事务给串行化了事务B查询的结果一定是在事务A提交之后从而解决了数据更新丢失问题。 mysql下有select ... for update 和select ... lock in share mode两种显示加锁的语句具体用途这里就不拓展了。 3.自动检测
方法1和2实质上都是将事务给串行化了。自动检测说的就是当检测到事务A造成更新丢失问题就立即终止事务A让事务A再一次尝试查询-计算-更新的流程,事务仍然是并行执行的。
PostgreSQL的可重复读Oracle的串行以及SQL Server的快照隔离能够自动检测更新丢失但Mysql的Innodb引擎下的可重复读没有此功能。 4.CAS比较和设置(compare and set)
说简单点就是在sql更新语句里加一个判断原本旧数据的条件例如
update news set counter 30 where id 1 and counter 29; //29是一开始的文章点赞数
如果两个事务A和B现在都要对id1的新闻文章进行点赞操作只要其中任何一个事务执行了更新操作另一个事务执行时counter29的条件都不会满足从而就规避了更新丢失的问题。 但是有的数据库where条件里counter获取的本就是旧的快照数据即不是某一个事务更新后的新数据那这里的更新丢失问题还是要发生的了。
经过实践mysql下可重复读隔离级别下使用此方法的确可以避免更新丢失问题。一旦事务A提交(对counter做更新)事务B里的类似counter29的判断就不满足了如此一来事务B的写入操作就没有执行成功就更不用说造成数据的更新丢失了。实践里事务A不提交事务B里的更新操作会一直等待事务A的排它锁释放否则是不会执行的 好啦最后就来说说幻读的问题。 幻读(phantom)
看网上很多博客都对幻读的理解不太准确。他们的理解可见此链接进行了解MySQL的InnoDB的幻读问题
通过提交事务B之后来发现事务A出现莫名奇妙的数据遗失或数据增多这个实践中已经涉及到事务B的提交操作这也就已经确定了对幻读的误解。 当事务A和B各自写入一笔数据不像更新丢失里事务A和B是往同一笔记录写入数据破坏了潜在的竞争条件造成的结果我们称之为“写偏离”(write skew),而造成这种结果的事务B里的查询操作的结果才是我们所说的幻读。至于幻读的前提是不是得一定导致了写偏离这个待确定现姑且当做是的 由上可见幻读出现的前提是出现了写偏离而出现写偏离是只有当事务A和B都是read-write型事务时才会出现的这也是为何有的书上说快照隔离级别下read-only型事务是没有幻读的read-write型事务才导致幻读。 什么样的结果是写偏离 如图所示一个医院每个班次必须有一个医生值班所以每个医生请假的前提是当下该班次必须至少有两个医生值班可是图中Alice和Bob很遗憾的同时点击了提交请假的按钮导致最终1234班次无人守班这就是破坏了潜在的竞争条件----必须至少要有一人值班造成的这种结果就是我们上述所说的写偏离。 假设Alice下的事务是先执行的那么Bob下执行的查询1234班次在班的医生人数结果导致Bob也能请假这就对潜在的竞争条件造成了破坏我们就称Bob下事务的查询操作带来了幻读现象。 我们再总结一下写偏离和更新丢失的区别 更新丢失是多个事务并发写同一笔数据记录造成的。 而写偏离是多个事务并发写不同数据记录影响到了潜在的竞争条件而造成的。 写偏离的情景还有
1. 抢注用户名两个用户同时抢注某一个用户名并且都成功了破坏了用户名必须唯一性的潜在竞争条件。
2. 游戏里多人移动不同人物到同一位置破坏了某一时刻某一位置只允许一个人物的潜在竞争条件。 那针对这些写偏离问题该如何解决呢
上文中的医生请假问题我们可以用select...for update就保证了第二个事务执行更新操作时必须先等待第一个事务释放排它锁。
可是这方法对于游戏移动人物位置就不适用了因为select查询有结果才能用for update来加排它锁而游戏里移动人物select操作结果是要保证在某一时刻某一位置必须没有人物也就是select查询根本没有结果就更不用谈加排它锁了。 针对这种情形有一种物化冲突(Materialiing conflicts)的解决方法。
就是既然select查询没有结果供添加排它锁来保证串行执行那我就想方法让select查询有结果。
针对多人游戏这个例子假设画面是1280*720且由1*1的像素组成的屏幕游戏人物有貂蝉、吕布、虞姬和项羽时间维度以秒为单位游戏开始时间从0开始计时。如此下来我们就可以先创建一张表的数据如下
时间x轴y轴英雄111貂蝉111吕布111虞姬111项羽211貂蝉211吕布211虞姬
将任意秒任意位置可能出现的英雄情形全都列举出来在多个事务并发移动英雄人物时就给某一时间某一位置的记录加上for update以上表只提供添加排它锁不做实际修改和更新。例如
//如此就能保证在第1秒(1,1)这个同一位置绝对不会出现多个英雄
select * from table_name where time 1 and x 1 and y 1 for update
当然解决这个问题还有一种方法就是采用串行化隔离级别了也是最高的隔离级别简单理解就是严格确保了事务串行执行避免了脏读幻读现象但是由于性能问题实际生产环境很少用到。这个我以后再好好了解本文就不细说了。