新乡商城网站建设哪家专业,宁波市城市建设档案馆网站,seo网站代码,常州企业自助建站最近准备写一篇关于Spanner事务的分享#xff0c;所以先分享一些基础知识#xff0c;涉及ACID、隔离级别、MVCC、锁#xff0c;由于太长#xff0c;只好拆分成上下两篇#xff1a;上#xff1a;并发问题与隔离级别主要讲事务所要解决的问题、思路#xff0c;先理解为什么…最近准备写一篇关于Spanner事务的分享所以先分享一些基础知识涉及ACID、隔离级别、MVCC、锁由于太长只好拆分成上下两篇上并发问题与隔离级别主要讲事务所要解决的问题、思路先理解为什么需要事务以及事务并发控制中面临的问题。下隔离级别实现——MVCC与锁隔离性是为了更好地做到并发控制事务的并发表现会对业务有直接影响所以这篇会详细讲如何实现隔离主要是讲两种主流技术方案——MVCC与锁理解了MVCC与锁就可以举一反三地看各种数据库并发控制方案并理解每种实现能解决的问题以及需要开发者自己注意的并发问题以更好支撑业务开发。文章开始前先给一个小思考考虑一个情况 像下面这样实现User提现100元是否一定不会出问题Start TransactionSELECT balance FROM users WHERE user_namex; 此次读取在Transaction中在代码中判断balance是否大于等于100如果小于100元End Transaction并且返回余额不足提现失败如果大于等于100元则 UPDATE users SET balance balance - 100 WHERE user_namex; 然后Commit Transaction返回提现成功如果你已经很理解数据库事务了一定知道什么情况有问题以及为什么出现这个问题这篇文章对你太入门不用继续看。如果不太清楚那希望你看完上、下两篇就非常理解了否则就是我写得太烂。一、重新理解 ACID1. 数据操作中面临的问题技术中的所有方案必定是为了解决特定问题先理解问题再看方案学起来更简单、理解更深入所以先从数据库面临的问题说起。首先要理解为什么数据库会有事务的需求先理解数据库要解决的根本问题不是存储存储问题已经被文件系统解决了数据库的目的是如何帮助开发者更可靠、更快速、更便利地使用存储更好地帮助开发者完成业务业务中一个高频需求是有一批连续的操作这一批操作要么全部成功要么就可以像没有发生过一样不要由于部分未成功而导致脏数据产生。如果没有事务我们处理用户下单的业务场景就要超级多的代码去handle各种错误、清理各种脏数据、避免可能的bug比如下单成功却由于数据库宕机导致没有扣款。为了提高开发效率、降低开发成本就需要数据库能提供一种保证将一组操作看作一个单元这一单元可以全部成功在部分失败的情况下可以完全回滚就像没有发生这一组操作称为事务Transaction。但是仅仅做到上面那一点是不够的因为这一个简单需求其实引入了另一个问题请注意重点——“一组操作”事务中可能存在着多个独立操作他们组合为一组操作理解多线程编程的同学一定会马上想到这就会出现经典的并发问题多个事务间如果不进行并发控制就会产生各种意外结果这不是使用者想要的。总结一下数据操作中面临的问题如何将一组操作看作一个整体要么全部成功要么全部回滚。如何在满足上一条需求的情况下能够对它进行并发控制保证不要出现意外结果。2. 我们需要什么ACIDACID 是为了解决上述问题所总结出为保证事务是正确可靠的所必须具备的四个特性1. 原子性Atomicity事务中的原子性是一个常常被大家误解的特性因为这个原子性的意思和我们通常语境下的原子性不太一样大多数时候原子性是指一条不可再分割、不会被中断影响的指令比如读取一个内存地址的值、将值写回内存地址、redis的SETNXset if not exists这些操作都符合我们常说的原子性。可是事务中的原子性并不是指事务具有不被中断影响的特点它仅仅是指事务中的所有操作应该被看作不可分割的一组指令任何一个指令不能独立存在要么全部成功执行要么全部不发生也就是回滚。还有很多同学对这里所说的“成功执行”有误解成功执行是指数据库层面的而不是业务层面的举个例子客户购买商品A可是在购买时商家刚好下架了商品那么此时执行 update products where product_idA and status销售中 由于product的status已经变成“下架”导致被更新的行数为0这个算成功执行吗算数据库不报错、不宕机、正常运行就是成功更新行数为0是数据库的正常返回结果这在业务上是失败在数据库层面是成功这种情况数据库不会执行回滚需要程序员判断更新行数如果为0手动回滚。如果数据库由于硬件或者系统问题发生宕机、报错这样才算是指令执行失败此时数据库会重试或者直接回滚然后将错误返回给开发者。原子性不止为开发者保证了事务的可靠性不会因为数据库出错而产生脏数据还能让开发者手动回滚提供了业务的便利性。2. 一致性Consistency这个名词也是相当令人困惑与数据库主从复制中所说的“一致性”不同主从复制的一致性是指多个副本间是否完成同步、数据相同而这里的一致性是指事务是否产生非预期中间状态或结果。比如脏读和不可重复读产生了非预期中间状态脏写与丢失修改则产生了非预期结果。一致性实际上是由后面的隔离性去进一步保证的隔离性达到要求则可以满足一致性。也就是说隔离不足会导致事务不满足一致性要求所以务必理解各个隔离级别才能少写Bug。3. 隔离性Isolation简单来说隔离性就是多个事务互不影响感觉不到对方存在这个特性就是为了做并发控制。在多线程编程中如果大家都读写同一块数据那么久可能出现最终数据不一致也就是每条线程都可能被别的线程影响了。按理说最严格的隔离性实现就是完全感知不到其他并发事务的存在多个并发事务无论如何调度结果都与串行执行一样。为了达到串行效果目前采用的方式一般是两阶段加锁Two Phase Locking但是读写都加锁效率非常低读写之间只能排队执行有时候为了效率原则是可以妥协的于是隔离性并不严格它被分为了多种级别从高到低分别为⬇️可串行化Serializable⬇️可重复读Read Repeatable⬇️已提交读Read Committed⬇️未提交读Read Uncommitted每一个级别都只是指导标准每个数据库对其的实现都有差异有的数据库在Read Committed级别时就已经实现了Read Repeatable的效果有的数据库干脆不提供Read Uncommitted级别。在隔离级别为Serializable时就会感觉到事务像一个完完全全的原子操作不被任何中断、并发所影响。很多开发者理解的事务可能就在Serializable级别大家误以为事务都是可串行化的其实并不是大多数的数据库默认隔离级别都不是可串行化大多数在Read Repeatable或者Read Committed要是按照可串行化的思维去编程却用着低于可串行化的隔离级别就很容易写出导致数据在业务层面不一致的代码所以开发者一定要理解各个隔离级别及其原理更好地支撑业务开发下面会仔细地讲隔离级别及其实现。4. 持久性Duration这是ACID中最好理解的即事务成功提交后对数据的修改永久的即使系统发生故障也不会丢失这里所说的故障也只是一般错误比如宕机、系统Bug、断电如果是硬盘损毁那就没办法数据一定会丢失。二、并发问题与隔离级别在讨论各个隔离级别的实现之前先看一下在事务并发执行时隔离不足会导致的问题。脏写Dirty Write还未提交的事务写了另一个未提交事务所写过的数据称为脏写比如两个并发执行的事务A、BA写了x在A还未提交前B也写了x然后A提交此时虽然B还没有提交但是A也会发现自己写的x不见了。很多地方用“覆盖”去形容脏写但是我觉得不太适合因为覆盖暗示了一种先后链条某个事务写了数据在昨天就提交了今天有事务来写同一个数据可以称之为覆盖昨天的数据成为历史但这不是脏写所以更适合的形容可能是“擦除”事务发现自己的提交被别人擦除好像不存在。脏写是事务一定不允许发生的所以不管是哪个隔离级别都一定不允许脏写。脏读Dirty Read由于事务的可回滚特性因此commit前的任何读写都有被撤销的可能假如某个事物读取了还未commit事务的写数据后来对方回滚了那么读到的就是脏数据因为它已经不存在了。避免脏读可以采用加锁或者快照读的解决方案。在已提交读Read Committed级别就可以避免脏读因为读到的一定是已经Commit的数据。在业务开发中虽然有未提交读Read Uncommitted但是几乎是没有人会用的读到脏数据一般对业务是很大的伤害所以有的数据库干脆都不支持未提交读比如PostgreSQL。不可重复读Non-Repeatable Read事务A读取一个值但是没有对它进行任何修改另一个并发事务B修改了这个值并且提交了事务A再去读发现已经不是自己第一次读到的值了是B修改后的值就是不可重复读。简单来说就是第一次读的值啥都没做下次读它也有可能发生变化。一般数据库使用MVCC在事务的第一条语句开始时生成Read View事务之后的所有读取都是基于同一个Read View以此避免不可重复读问题。幻读Phantom与不可重复读非常类似事务A查询一个范围的值另一个并发事务B往这个范围中插入了数据并提交然后事务A再查询相同范围发现多了一条记录或者某条记录被别的事务删除事务A发现少了一条记录。幻读容易与不可重复读混淆区别它们只需要记住不可重复读面向的是“同一条记录”而幻读面向的是“同一个范围”。MVCC虽然使用快照的方式解决了不可重复读但是还是不能避免幻读幻读需要通过范围锁解决可能大家会觉得很奇怪为什么快照读无法避免幻读这个会在下一篇文章中详细讲。SQL标准中有对于各个隔离级别所允许出现的问题作出规定除了以上4个问题外下面还有3个问题更偏向业务层面不过也是由于隔离不足引起的读偏斜Read SkewSkew可以理解为不一致因此读偏斜可以理解为读结果违反业务一致性比如X、Y两个账户余额都为50他们总和为100事务A读X余额为50然后事务B从X转账50到Y然后提交事务A在B提交后读Y发现余额为100那么它们总和变成了150此时违反业务一致性。写偏斜Write Skew写偏斜可以理解为事务commit之前写前提被破坏导致写入了违反业务一致性的数据网上有个很好的简称为写前提困境也就是读出某些数据作为另一些写入的前提条件但是在提交前读入的数据就已被别的事务修改并提交这个事务并不知道然后commit了自己的另一些写入写前提在commit前就被修改导致写入结果违反业务一致性。写偏斜发生在写前提与写入目标不相同的情境下。这是业务开发中最容易出错地方如果开发者不太理解隔离级别也不知道目前使用的是哪个隔离级别很可能写出有写偏斜的代码造成业务不一致。举个例子信用卡系统对不同等级的会员有积分加成3级会员则每次都3倍积分同时会有定时任务检查当积分不满足要求时就会降级。首先会员进行了刷卡消费此时要计算积分开启了事务A读到会员等级为3与此同时定时任务也开始了读到会员积分为2800已经不满足3000分应该降级为2级然后将会员等级降级为2并且commit由于事务A读到的等级为3它还是按照3倍积分为会员增加了积分会员赚了多亏那个程序员不理解他使用的事务隔离级别出现了业务不一致。丢失更新Lost Updates由于未提交事务之间看不到对方的修改因此都以一个旧前提去更新同一个数据导致最后的提交结果是错误值。假设有支付宝账户X余额100元事务A、B同时向X分别充值10元、20元最后结果应该为130元但是由于丢失更新最后是110元。丢失更新与写偏斜很相似都是由于写前提被改变他们区别是丢失更新是在同一个数据的最终不一致而写偏斜的冲突不在同一个数据在不同数据中的最终不一致。这一篇讲到的所有问题都会在下一篇讲隔离级别实现中得到解决理解隔离级别实现有助于选择合适的隔离级别或者在代码层面有意识地避免隔离级别不足所带来的问题。参考资料《MySQL 是怎样运行的从根儿上理解 MySQL》《数据库事务处理的艺术事务管理与并发控制》《数据库事务、隔离级别和锁》博客《SQL隔离级别》博客《开发者都应该了解的数据库隔离级别》博客《A beginner’s guide to read and write skew phenomena》博客