🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 事务笔记 [TOC=3,8] ---- ### mysql锁笔记 开两个命令行 A:对行1更新 B:开启事务,开启事务后,对A的更新是看不到的 但是如果B此时查询带上for update变成行锁的话,那么此时数据查出来是最新的,并且发现A再进行更新操作就会被阻塞。 要不了一会儿,A报错: ``` ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction(超时) ``` 然后BCOMMIT 提交事务,A瞬间获得锁,做更新。 奇怪,不对啊, for update不是针对事务的吗,A没有使用事务,此时应该是“一致性非锁定读”才对啊,怎么也会被阻塞呢? (我有病啊,读是没问题的,A现在做的是更新好不好,更新还是需要得到排它锁的,这不是“一致性非锁定读”,查询时是没有问题的,A和PHP一样正常的) 我们用PHP再来做一下实验: ~~~ 还是B开启事务,锁定,PHP来做和A同样的事试试: PHP也还是会阻塞,A明明没有开启事务啊,正常的 ~~~ 上面是在Console上开多选项卡进行实验测试的流程描述和结果。 结论: for update语句只在事务内有效,它是数据库为了让select查询语句拥有加锁的能力而添加的特性,它使select语句可以加排他锁的,使用效果是select语句会锁住它所涉及到的数据行,并且拿到最新的数据(因为它要锁定的就是最新的数据啊),而不再是事务外的修改对事务内不可见(可重复读时),请注意这点。 ======== 好这个问题分析完了,看下一个问题: 如果不开启事物,A查询,B高频率的改,对A会有什么影响,A还会有“一致性非锁定读”的光环吗? B高频率的改,对A的查询和更新不会有什么明显的影响,至少没出现过阻塞 * * * * * A开启事务,修改zb_user的id为1的mobile=1 B客户端,修改zb_user的id为2的mobile=1 结果被阻塞 直到报错 ``` ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction ``` mobile为唯一索引,可见在事务类,锁住了mobile=1,所以即使B没有开启事务,修改mobile=1的也会被阻塞。 关闭客户端A,那么A的事务自动回滚了,此时B才能执行。 * * * * * 客户端A、B都开启事务, A修改修改zb_user的id为5的mobile=5 B修改修改zb_user的id为5的mobile=5 B会被阻塞; 提交A,B获得锁,执行成功。 * * * * * 客户端A、B都开启事务, A修改修改zb_user的id为7的mobile=7 B修改修改zb_user的id为6的mobile=7 B会被阻塞; 提交A, B执行完,但报错,ERROR 1062 (23000): Duplicate entry '7' for key 'mobile' * * * * * 客户端A事务 A修改修改zb_user的id为1的login=11 B修改修改zb_user的id为1的login=1111 B会被阻塞; 提交A, B执行完,login结果为1111 * * * * * 可见这次B被阻塞是因为,A更新id=1时锁定了这一行,需要直到提交才会释放锁,而上面那个是因为mobile是唯一索引,所以锁住了唯一索引。 得出结论,事务内更新操作会加锁,并且直到提交才会释放锁。事务外修改也要获得锁,不过sql执行完就自动释放锁了,可以理解为,每一条sql执行完就立即自动提交了。 并且要注意的是,在默认的“可重复读”隔离模式下,事务外的修改对事务内是不可见的。但是不管在哪里,事务外,还是事务内,还是有没有开启事务,锁都是唯一的。不开启事务修改数据也会先要获得排它锁,事务内修改也要获得排它锁。锁是独立于多版本控制MVCC之外的,不受局限,它是唯一的,对要上锁的数据来说是唯一的,即,一个数据同一时刻只能被一个排它锁上锁,这与数据所处的环境,事务无关。这就是锁的意义所在,不然锁就没有意义了。 补充:即使事务回滚,AUTO_INCREMENT也还是不能回滚的哦。所以可能出现ID断层。 * * * * * A开启事务 B 更新id为2的mobile=2 A查询发现id为2的mobile还是原来的,因为在事务内,可重复读,外面的修改对事务内是不可见的。 A更新id为3的mobile=2,报错 ``` ERROR 1062 (23000): Duplicate entry '2' for key 'mobile' ``` 可见,虽然事务是可重复度,但是为了保证正确性,更新时却不是这样的,不然就会出现可以更新成功,但是提交时报错,原因还是一样的。 * * * * * [MySQL 四种事务隔离级的说明 - jyzhou - 博客园](http://www.cnblogs.com/zhoujinyi/p/3437475.html) [MySQL事务隔离级别详解 - JAVA夜无眠 - ITeye博客](http://xm-king.iteye.com/blog/770721) > SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。 [MySQL数据库事务隔离级别(Transaction Isolation Level) - 生死看淡,不服就干! - CSDN博客](http://blog.csdn.net/jiangwei0910410003/article/details/24960785) >[danger] 要记住mysql有一个autocommit参数,默认是on,他的作用是每一条单独的查询都是一个事务,并且自动开始,自动提交(执行完以后就自动结束了,如果你要适用select for update,而不手动调用 start transaction,这个for update的行锁机制等于没用,因为行锁在自动提交后就释放了),所以事务隔离级别和锁机制即使你不显式调用start transaction,这种机制在单独的一条查询语句中也是适用的,分析锁的运作的时候一定要注意这一点。 **其实我们平常的每条sql都是作为事务执行的(在事务中执行的)** <p style="color:red;">也就是说,其实我们平常的每一条普通的sql,我们觉得没有开启事务,但其实都开启了事务,确实是作为事务执行的,单条sql作为一个事务自动开启和提交了,只不过我们没有感觉到而已。理解这点很重要。</p> 终于明白了 `select …… for update` 为什么要在事务内才有效果,原来是因为,默认是自动提交了,看似没有使用事物,但其实每一条sql都是自动开启并提交了事务的,所以锁也就在sql本身执行完就释放了啊,所以这样加锁当然没意义啊,锁一下,立马就松开释放了有什么意义呢。 > SERIALIZABLE事务隔离级别最严厉,在进行查询时就会对表或行加上共享锁,其他事务对该表将只能进行读操作,而不能进行写操作。 (可见更高级别的隔离就是要用更高级别的锁,这样会有更大的系统开销,并发能力也越低,但是为了数据的完整性,业务逻辑的需要,不得不做这样折中的选择,以更大的开销,牺牲性能来换我们对数据的业务要求。任何事情都是这样的,都是多面性的。) **MYSQL事务的隔离级别** | 隔离级别 | 为了解决 | 会产生的问题| 问题描述| | --- | --- |---|---| | 未提交读 Read Uncommitted | | 脏读 | A事务没有提交的数据能够被B事务读到 | | 已提交读 Read Committed | 脏读 | 不可重复度 | 事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。 | | 可重复度 Repeatable Read | 不可重复度 | 幻读 | A事务的提交对B事务是不可见的(默认),但是新增的数据对事务B是可见的,如果使用 事务B使用了 for update 则为当前读,此时对事务B也是可见的 | | 可序列化 Serializable | 幻读 | | 没有问题,唯一的问题在于这种严格的模式性能是最低的,因为不能并发了 | **问题描述** | 问题 | 描述 | 解决方式 | | --- | --- | --- | | 更新丢失 | A、B同时编辑一篇文章,后提交的一个人会把前面一个人的提交覆盖掉 | 程序业务逻辑层中去解决 | | 脏读 | 在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。 | 已提交读 | | 不可重复读 | 由于B事务提交了对数据行的修改,那么A事务中两次读到的的数据可能是不一样的 | 可重复读 | | 幻读 | 简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。 | 可序列化 | > 隔离级别,其实就是 **一致性** 与 **可用性** 的 相互折衷和妥协(即 CAP 理论中的C、A)。应用需要根据实际情况进行取舍。 **MYSQL锁** 排它锁,共享锁,意向排它锁,意向共享锁。 查询操作默认是 【一致性非锁定读】。更新,插入,删除都是需要对数据加排它锁的。这个加锁是自动的。如果在事务中,则需要等待事务提交后自动释放锁,如果不在事务中,获得锁执行完sql后立即释放锁。 [MySQL的四种事务隔离级别 - 带着梦逃亡 - 博客园](https://www.cnblogs.com/wyaokai/p/10921323.html) [锁住余额,为何还会更新异常?](https://mp.weixin.qq.com/s?__biz=MzIzMTgwODgyMw==&mid=2247483935&idx=1&sn=176c01be1a220071e78d60e6d295c34e&chksm=e89fc847dfe841515bf8901ec7ee7ca3102731cdb475275093f2d07ac9a6bb10d54ce37beed9&scene=178&cur_album_id=1337216649245655040#rd) >[tip] 可重复读要注意别使用**快照读**了,这个细节很重要,不然将会存在安全隐患。 * * * * * `FOR UPDATE` 是针对事务的,只会在事务内才有锁效果 而更新操作不论是在事务内还是在事务外都会对所影响到的数据加排他锁,如果在事务外,sql执行完立即释放锁,如果在事务内则等待事务提交后才释放锁。 * * * * * 参考:[MySQL学习之——锁(行锁、表锁、页锁、乐观锁、悲观锁等) - 一个手艺人 - 博客频道 - CSDN.NET](http://blog.csdn.net/mysteryhaohao/article/details/51669741) ** InnoDB行锁模式兼容性列表** |请求锁模式 是否兼容 当前锁模式 | X | IX | S | IS | | --- | --- |---|---|---| | X | 冲突 | 冲突 | 冲突 | 冲突 | | IX | 冲突 | 兼容 | 冲突 | 兼容 | | S | 冲突 | 冲突 | 兼容 | 兼容 | | IS | 冲突 | 兼容 | 兼容 | 兼容 | 如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。 意向锁是InnoDB自动加的,不需用户干预。**对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。** * 共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。 * 排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE。 用SELECT ... IN SHARE MODE获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT... FOR UPDATE方式获得排他锁。 * * * * * ### 参考 [关系型数据库为什么能活这么久?](https://mp.weixin.qq.com/s/tvUQPSITJ7mxNuxLUbNPOw) [mysql事务处理用法与实例详解 - 网名还没想好 - 博客园](http://www.cnblogs.com/ymy124/p/3718439.html) [幻读和不可重复读的区别 - 闭关修炼 - 博客频道 - CSDN.NET](http://blog.csdn.net/v123411739/article/details/39298127) [MySQL "replace into" 的坑 - 默念默 - 博客园](http://www.cnblogs.com/monian/archive/2014/10/09/4013784.html) [MySQL :: MySQL 5.7 Reference Manual :: 13.2.8 REPLACE Syntax](https://dev.mysql.com/doc/refman/5.7/en/replace.html) [数据库村的旺财和小强](https://mp.weixin.qq.com/s/tSF_w9xUOj3Q2hmOxJkwLg) > 有的操作必须要串行,才能保证最终结果正确,所以数据库的解决办法都是用锁。 可以再补充一个,【更新丢失】:更新丢失其实不是数据库层要解决的问题,而是业务逻辑层要解决的问题,比如A、B同时打开同一篇文章进行编辑,A先提交保存了,B再点击提交保存,就会覆盖掉A之前保存的内容。解决更新丢失的问题,可以给文章加一个版本号;或者A打开时就记录下A打开的状态,此时就不能允许其他人打开编辑了。 * * * * * [分布式系统的事务处理](http://coolshell.cn/articles/10910.html) > 分布式事务处理,更加的困难,也就是著名的**拜占庭将军问题 (Byzantine Generals Problem)** [解决分布式系统事务一致性的几种方案对比,你有更好的吗?](http://mp.weixin.qq.com/s/kzmTKKH-t6tpJ97fa6TYPg) ~~~ ……我们会尽量把可提供回滚接口的服务放在前面。 解决了通知的问题,又有新的问题了。万一这消息有重复被消费,往用户帐号上多加了钱,那岂不是后果很严重? 仔细思考,其实我们可以消息消费方,也通过一个“消费状态表”来记录消费状态。在执行“加款”操作之前,检测下该消息(提供标识)是否已经消费过,消费完成后,通过本地事务控制来更新这个“消费状态表”。这样子就避免重复消费的问题。 从上面分析的几种情况来看,貌似问题都不大的。那么我们来分析下消费者端面临的问题: 消息出列后,消费者对应的业务操作要执行成功。如果业务执行失败,消息不能失效或者丢失。需要保证消息与业务操作一致 尽量避免消息重复消费。如果重复消费,也不能因此影响业务结果 如何保证消息与业务操作一致,不丢失? 主流的 MQ 产品都具有持久化消息的功能。如果消费者宕机或者消费失败,都可以执行重试机制的(有些 MQ 可以自定义重试次数)。 如何避免消息被重复消费造成的问题? 保证消费者调用业务的服务接口的幂等性 通过消费日志或者类似状态表来记录消费状态,便于判断(建议在业务上自行实现,而不依赖 MQ 产品提供该特性) 其实为了交易系统更可靠,我们一般会在类似交易这种高级别的服务代码中,加入详细日志记录的,一旦系统内部引发类似致命异常,会有邮件通知。同时,后台会有定时任务扫描和分析此类日志,检查出这种特殊的情况,会尝试通过程序来补偿并邮件通知相关人员。 在某些特殊的情况下,还会有“人工补偿”的,这也是最后一道屏障。 ~~~ * * * * * [国之骄傲!他的课堂带领中国计算机叫板美国,高考状元们挤破头也想进](http://www.toutiao.com/i6448402201713836557/?tt_from=weixin&app=news_article&iid=12619555732&wxshare_count=1) [Raft 为什么是更易理解的分布式一致性算法 - mindwind - 博客园](http://www.cnblogs.com/mindwind/p/5231986.html) [raft算法与paxos算法相比有什么优势,使用场景有什么差异? - 知乎](https://www.zhihu.com/question/36648084) [春晚抢微信红包如何实现 - 腾讯云分布式消息队列CMQ架构](http://www.toutiao.com/a6333075051169399041/?tt_from=weixin&utm_campaign=client_share&app=news_article&utm_source=weixin&iid=12619555732&utm_medium=toutiao_android&wxshare_count=1) [分布式事务之TCC服务设计和实现](https://mp.weixin.qq.com/s/L7c1yKLZf8ZkMNiUerokyQ) * * * * * [MySQL锁问题 - Lucky_man - 博客园](http://www.cnblogs.com/lucky-man/p/6207776.html) ~~~ 间隙锁 当使用范围条件检索数据的时候,对于键值在条件范围内但并不存在的记录,InnoDB也会进行加锁,这个锁 就叫“间隙锁”。InnoDB使用间隙锁的目的,一方面是为了防止幻读,另一方面是为了满足恢复和复制的需要。但是 这种加锁机制会阻塞符合条件范围内键值的并发插入,造成严重的锁等待,所以应该尽量避免使用范围条件来检索数据。 除了通过范围条件加锁时使用间隙锁外,如果使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁! ~~~ [MySQL中锁详解(行锁、表锁、页锁、悲观锁、乐观锁等) - 坦GA的博客 - CSDN博客](http://blog.csdn.net/tanga842428/article/details/52748531) * * * * * ### 注意 InnoDB引擎下由超时(往往是等待锁)引发的异常错误,<span style="color:#F90B0B">**并不会自动回滚数据库哦,而是隐式的提交了,这是很危险的!**</span>所以程序中需要注意了,一定要捕获到这种异常,然后回滚,不然就会很危险了。 ```php try { sql执行 提交 } catch (Exception $e) { 回滚 } ``` * * * * * ### 扩展 [分布式事务XA](https://www.cnblogs.com/zengkefu/p/5742617.html) [MySQL 百万级数据量分页查询方法及其优化](https://mp.weixin.qq.com/s/le6ue-8q9T20WWWFw8fvuQ) [谈谈中间件开发,给想从事中间件开发的同学](https://mp.weixin.qq.com/s/hXnMmXWkFHebT5GddvwVZg) [SpringBoot多数据源中的分布式事务](https://mp.weixin.qq.com/s/lO0EF7-n-mNffzrUzXYmJA) [10分钟搞定ICO](https://zhuanlan.zhihu.com/p/32796998) [比特币入门教程 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2018/01/bitcoin-tutorial.html) [区块链入门教程 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2017/12/blockchain-tutorial.html) > 区块链是什么?一句话,它是一种特殊的分布式数据库。 [如何理解拜占庭将军问题? - 知乎](https://www.zhihu.com/question/23167269) [超级账本PBFT(拜占庭容错)算法详解 - CSDN博客](http://blog.csdn.net/fidelhl/article/details/52833118) [拜占庭将军问题深入探讨 | 巴比特](http://www.8btc.com/baizhantingjiangjun) > 拜占庭问题 和 两军问题 不同,不能混为一谈。 [漫画:什么是拜占庭将军问题?](https://mp.weixin.qq.com/s/-dTBkwVaRo6WxZ8uS9DwjQ) [一文看懂区块链 为何腾讯等巨头纷纷杀入?](https://www.toutiao.com/a6508633088778568206/?tt_from=weixin&utm_campaign=client_share&article_category=stock&timestamp=1515433507&app=news_article&utm_source=weixin&iid=22069500288&utm_medium=toutiao_android&wxshare_count=1) [Spring Cloud 分布式事务终极解决方案探讨](http://mp.weixin.qq.com/s/VMYsLKYV__Nf_UuVSR0HDw) [九分钟了解区块链](https://mp.weixin.qq.com/s/iHEFRZTcLDXQ5trzKbPt3g) [目前介绍比特币和区块链最好的视频](https://www.ixigua.com/a6514121541184127501/?utm_source=toutiao&utm_medium=feed_stream#mid=60418991004) [漫谈分布式系统、拜占庭将军问题与区块链](http://mp.weixin.qq.com/s/tngWdvoev8SQiyKt1gy5vw) [Java架构沉思录](https://mp.weixin.qq.com/s/BNiNXylqPwKXMW8rkS6dbQ) [数据库村的旺财和小强](http://mp.weixin.qq.com/s/bM_g6Z0K93DNFycvfJIbwQ) > 感觉数据库和分布式,是世界上最复杂的软件工程,不知道是不是,还有没有其它的。 [为什么不建议在MySQL中使用UTF-8?](https://mp.weixin.qq.com/s/LmpFIwoNULIqFuyTJqG1Vw)(此 `UTF-8` 非真的 `UTF-8`) [MySQL事务隔离级别和MVCC](https://mp.weixin.qq.com/s/Jeg8656gGtkPteYWrG5_Nw) [一文读懂数据库事务](http://mp.weixin.qq.com/s/uvardpe6Qy2cMahi8gFMlA) >[tip] **串性化**(原来可序列化,不是`serialize/serialized`“序列化”的意思啊,是可串行的意思啊,**序列不就是顺序串行吗**,看来**英文在翻译成中文时确实存在很大问题,很多本来就浅显易见的东西翻译过来就变得晦涩难懂了,看来学习过程严重依赖翻译是有很多问题的,容易误入歧途走很多弯路**),指的是强制事务串行执行,其可以避免“幻象读”的问题出现,这是最严格的隔离级别了。因为串行化需要在发生竞争的数据上加锁,所以并发性能不高,只有在对数据一致性要求非常高且并发度不高的情况下才会考虑使用这种隔离级别。(虽然我们不使用这种最高级别的事务隔离,但是其实重要数据操作,我们是会加锁的,比如涉及到转账时,会锁住相应的账户直至操作完成,其实也是一种强行变为串行的方式。) >[danger] 多个事务可以同时存在(受隔离级别的影响,会有不同的后果),但如果用了锁,存在竞争关系(有共同的竞态资源),那么事务就是串行的了。可以看到所有的并发问题最后唯一的解决办法其实都是强制变并行为串行的来解决的,毕竟竞态资源只有一个,是唯一的,要保证数据完整性,业务逻辑正确性,必须这样。**这是没有办法的事,计算机不是神,不能独立于现实生活而存在,所以也要遵守现实科学规则。** [分布式系统事务一致性](https://mp.weixin.qq.com/s/OIqRYvA3iliUG64nI48llA) [深入学习MySQL事务:ACID特性的实现原理](https://mp.weixin.qq.com/s/51vyvMdMnin6yD7MVXHG5w) [MySql 三大知识点——索引、锁、事务](https://mp.weixin.qq.com/s/btzwjg7uMQ1_H_m2Lnt_pQ) [数据库分库分表如何避免“过度设计”和“过早优化”](https://mp.weixin.qq.com/s/uTRwstOABOgZf2YCbgz5TQ) [面试官:你知道哪几种事务失效的场景?](https://mp.weixin.qq.com/s/AoifU5hk3HHcQxSF0hPIJw) [是谁拯救了数据库?](https://mp.weixin.qq.com/s/KTNM1AHfN0Gs9cjsJsPyxQ) [上亿数据怎么玩深度分页?兼容MySQL + ES + MongoDB](https://mp.weixin.qq.com/s/vrFU0lsURV5WQOgK7w9Mtw) [select for update引发的死锁分析,太惊险了](https://mp.weixin.qq.com/s/w6TXqllB8e1EgJqv47dnqg) * * * * * >[danger] **凡是有可能失败的地方,就一定会发生失败,所以检测、容错性要做好,不能偷懒!** * * * * * >[danger] **每条sql都有可能执行失败,所以要严格判断每一条sql的执行**,特别是在上下文中有很多sql的情况,这里首先要明确规定一下,对于sql操作失败是怎样定义的:更新操作受影响的条目为0,查询操作结果集为空,删除操作受影响的条目为0,这些都为操作成功,操作失败是指sql操作出现异常,比如插入失败,这个原因就有很多了,sql非法,字段格式错误,唯一索引导致的等等,这样的错误会直接抛异常的,不会继续往下执行的,需要我们捕获处理。好在tp5框架是这么对待sql错误的,所以任何时候我们的sql操作都应该在try{}中,这样就方便多了,不用一条一条的去判断执行失败了。回想起tp3真是噩梦,sql执行失败不会抛异常,而是返回失败,并把错误信息放到模型error中,让用户自己去判断,真的很麻烦。 >[danger] 你并不能保证你写的每一条语句一定会执行成功,程序绝对严谨吗,如果停电呢,打雷呢,临时工挖断网线呢,这些你考虑了没有,代码即科学,**并不能想当然的认为会怎么样**(计算机技术是经过严密的证明,有科学事实依据的,并不是你随便拍脑袋想出来的)。所以既然我们无法确定未知的错误,只能提前做好容错准备了,**所有SQL操作都必须放在事务内,这是没有任何商量余地的硬性规定!!!** * * * * * 看来数据库的 `Undo/Redo` 日志,其实就是撤销(后退)和重做(前进) ![](http://cdn.aipin100.cn/18-4-3/17100759.jpg) * * * * * >[danger] ### 实现可靠/安全的必备保证:事务+原子性+锁 系统不断向下游发送sql,这也是业务逻辑的体现,但对于下游数据库系统来说,它并不知道,**它并不关心你所谓的业务逻辑,它只知道接受sql执行**。 这些sql交错着,最终顺序被下游数据库执行(具体跟数据库的底层设计或存储引擎实现细节有关)。但是 `一个业务逻辑` 包含的多条sql要保证顺序执行,中间不能被其它sql插进来,不能受其它影响,才能保证业务逻辑的正确性,**否则就会有并发问题**,业务就不能按照预期执行。 要保证每个业务的sql操作为一个原子,一个独立的整体单元,不可分割,而不是分散的单条sql,这就要引入事务和锁了。 * * * * * [从单一架构到分布式交易架构,网易严选的成功实践](https://mp.weixin.qq.com/s/nv3Ht7OqTYQw31QFDX3gNg) >[danger] **没有完美的架构设计,世上也没有绝对的事情,没有谁能保证绝对可靠、安全和高可用,但我们有补偿和容错(类似还有重试,确认等机制),也是能做到万无一失的。** [MySQL事务隔离级别和MVCC](https://mp.weixin.qq.com/s/SCW_3AypO-rSolMcjCxVtA) [不知怎么优化MySQL?先搞懂原理再说吧!](https://mp.weixin.qq.com/s/NPeJPesMb6KbFjbWji5xdw) [分布式事务:深入理解什么是2PC、3PC及TCC协议](https://mp.weixin.qq.com/s/jQrPSmPhC_yNbIRcufR8KQ) [关于MySQL,你未必知道的!](https://mp.weixin.qq.com/s/pWHCieOwAdCrz8cauduWlQ) [【技术栈之Mysql】Mysql InnoDB常见死锁分析](https://mp.weixin.qq.com/s/LqvtEoozs4xWPNnMpLyF5w) [你真的懂Redis事务吗?](https://mp.weixin.qq.com/s/WOcw5B038P3qLU0c_Rs-Mw) [InnoDB,select为啥会阻塞insert?](https://mp.weixin.qq.com/s/y_f2qrZvZe_F4_HPnwVjOw) [揭秘JDBC超时机制](https://mp.weixin.qq.com/s/lxAHnuRO9rB5VlUTW4AsfQ) [MySQL在并发场景下的优化手段](https://mp.weixin.qq.com/s/BNiNXylqPwKXMW8rkS6dbQ) [分库分表的事务处理机制](https://mp.weixin.qq.com/s/Z2uqixTplk5CilQNamOR2A) [分析一个MySQL并发事务示例](https://mp.weixin.qq.com/s/hdDl95a6ayVtCoEc3RiLwQ) > 两种开启事务方法的区别:使用 ”start transaction with consistent snapshot;“ 语句的目的是马上启动事务,”begin/start transaction“命令不是一个事务的起点,而是在执行到他们之后的第一个操作 InnoDB 表的语句后事务才开始。 [找对“关系”后,办事果然轻松了!](https://mp.weixin.qq.com/s/mpi2tfvLKXAiPK1dxWSTQQ) [RDF 和 SPARQL 初探:以维基数据为例](http://www.ruanyifeng.com/blog/2020/02/sparql.html) > 维基数据 图数据 [隔离做得好,谁都没烦恼!](https://mp.weixin.qq.com/s/201mUftRWEFcxx9W_zsD2w) [分布式事务及其中间件介绍 - 掘金](https://juejin.im/post/5e9709d2e51d457918095eac) > 一、原理介绍: TXC模式命名来源于淘宝,实现原理是在执行SQL之前,先查询SQL的影响数据,然后保存执行的SQL快走信息和创建锁。当需要回滚的时候就采用这些记录数据回滚数据库,目前锁实现依赖redis分布式锁控制。 [数据库如何存储时间?你真的知道吗?](https://mp.weixin.qq.com/s/h5qysXyiOpXNvJT8pbTN3g) [死锁案例一](https://mp.weixin.qq.com/s/1Jd-8d-hZgGlAkvjWmO5kw) [这次终于懂了,InnoDB的七种锁(收藏)](https://mp.weixin.qq.com/s/f4_o-6-JEbIzPCH09mxHJg) * * * * * ### 嵌套事务的原理 https://github.com/top-think/framework/blob/5.1/library/think/db/Connection.php 关键点在: ```php protected function parseSavepointRollBack($name) { return 'ROLLBACK TO SAVEPOINT ' . $name; } ``` 嵌套事务的提交并没有真正提交,嵌套事务的开启也并不是真正的开启,只是计数,嵌套事务的回滚利用了回滚计数。 扩展:[XA 分布式事务原理 - 飞鹰之歌 - 博客园](https://www.cnblogs.com/luoyunfei99/articles/6803682.html) (tp也支持xa事务了) [mysql 事务之使用savepoint部分回滚 - 简书](https://www.jianshu.com/p/c93c1730e5dc) [PHP中实现MySQL嵌套事务的两种解决方案_hello_katty的专栏-CSDN博客](https://blog.csdn.net/hello_katty/article/details/45220825) [MySQL 到底支不支持事务嵌套?_weixin_34200628的博客-CSDN博客_mysql事务可以嵌套吗](https://blog.csdn.net/weixin_34200628/article/details/91725920) > **MYSQL 支持嵌套事务**,但不要直接多次执行 `START TRANSACTION` 以期望有嵌套事务的效果,这会带来隐式提交,嵌套事务需要你在系统层面去实现(使用:`SAVEPOINT` / `ROLLBACK TO SAVEPOINT` ),再次说明 **MYSQL 是支持嵌套事务的**。 >[danger] 连接断开后重连事务就不完整不能直接用了,而有的框架有断线重连的机制(tp db.break_reconnect),在使用事务时,考虑到我们使用事务的目的就是希望这些 sql 都在一个完整的事务当中不可分割以确保逻辑的正确性,所以此时不能使用框架提供的重连机制,因为重连后的连接不再是之前的那个事务的连接了!这是一个很严重的问题,如果重连后继续执行,后续的sql就不再是处于之前的事务之中了!!!那就与我们使用事务的目的背道而驰了!!! ~~~ // 再次开启事务,会隐式 COMMIT 此连接上的上一个 开启的事务 START TRANSACTION // 创建保存点 SAVEPOINT name // 回滚到保存点 ROLLBACK TO SAVEPOINT name COMMIT ROLLBACK ~~~ ~~~ // 再次开启事务,会隐式 COMMIT 此连接上的上一个 开启的事务 START TRANSACTION // 创建保存点 SAVEPOINT name // 回滚到保存点 ROLLBACK TO SAVEPOINT name COMMIT ROLLBACK ~~~ * * * * * ### 在事务内申请的锁,是属于整个事务的 ```php Db::startTrans(); if (Db::name('user')->field('balance,cumulative_earn,cumulative_team_reward')->lock(true)->find(10029)) { echo "1"; } // 锁不会对同一个事务内锁申请进行排斥,也就是说,在事务内申请的锁,是属于整个事务的 if (Db::name('user')->field('balance,cumulative_earn,cumulative_team_reward')->lock(true)->find(10029)) { echo "2"; } sleep(5); // 如果不提交,脚本执行完毕,连接关闭,事务也会结束的 Db::commit(); ``` 框架并不知道你写了嵌套的代码(机器没有人类直观可以直接看出来,而是要通过其他规则取理解事物的),只知道计数和顺序,这很重要。 #### 在循环中使用事物请注意! 由于框架实现嵌套事务的原理,所以再循环中使事务要特别小心,因为很可能出现嵌套事务哦,尽管你没有写嵌套事务的代码,上面我们已经知道了,“嵌套”只是我们理解的概念,实际上是事务计数。 如果,我们在 continue; 退出当前遍历直接进入下个遍历时,没有提交,下个遍历又再次开启事务,这就成了一个嵌套事务了。 这造成的后果就是,就第二个遍历的提交不会真的提交,原因你应该知道了,因为第二个遍历中的提交,做的是计数-1。 ---- ### 关于连接的思考 ~~~ 1. mysql 打开连接,执行sql,然后又长时间再没有执行sql,再执行sql时,可能执行失败,因为长时间没有查询,连接已经被自动关闭了 2. mysql 打开连接未变关闭会怎样(短连接,和长连接的情况) 3. mysql 打开事务未提交也未回滚会怎样: a. 程序关闭了,连接未断开,未提交 b. 断开连接,未提交 c. 连接丢失(网络等原因),未提交 4. mysql insert_id() 并发时的问题 5. mysql 并发能力,连接上限,web服务在请求并发量很高时,不能都打在mysql上去了,因为mysql并发能力有限 6. 连接断开后重连事务就不完整不能直接用了,而有的框架有断线重连的机制(tp db.break_reconnect),在使用事务时,不能使用重连机制啊,因为重连后的连接不再是之前的那个事务的连接了!这是很严重的问题,如果重连后继续执行,就不再是处于之前完整的事务之中了!!! 资料: https://www.cnblogs.com/gaogao67/p/10859651.html https://www.cnblogs.com/leijiangtao/p/11882107.html https://segmentfault.com/q/1010000012903190 https://www.laruence.com/2011/04/27/1995.html https://www.laruence.com/2010/04/12/1396.html http://guitoo.cc/wordpress/2018/11/04/pdo/ https://blog.csdn.net/u014229282/article/details/81071257 https://dbaplus.cn/news-11-974-1.html https://blog.csdn.net/xc_zhou/article/details/80879863 https://blog.csdn.net/qianwenzhi/article/details/94433279 https://www.cnblogs.com/A121/p/12028972.html ~~~ ---- ### 程序终止了,未提交/未回滚,会怎样? https://segmentfault.com/q/1010000006202863?_ea=1071166 https://tieba.baidu.com/p/4185856648?pn=0&red_tag=t3424511720&referer=tieba.baidu.com [有关mysql长连接涉及事务的问题? - 知乎](https://www.zhihu.com/question/62603122) 程序报错,终止运行,未提交和回滚事务。 musql 那边可能立马不知道连接断开了(连接没有心跳的话就无法得知对方是否掉线了),还会保持这个会话和事务,如果有锁,可能会一直生效。 所以这种情况还是很危险的,需要保证程序终止前能够回滚事务。 如果会话活动超时,mysql那边要主动关闭连接时发现有未提交的事务,可能会回滚,也可能会提交,这取决于默认配置。不过应该默认是回滚。 不要站在应用上像这个问题,站在mysql设计者的角度看这个问题就发现非常简单。 ---- ### 可重复读 可重复读: 先开启事务,再修改数据,再事务中查询,能查到最新的修改的。—— 说明并不是开启事务就固化了数据。 事务中查询了一次,再修改了数据,事务中再次查询,结果与前次查询一致,并不是最新已修改的数据。—— 说明可重复读 机制 是记录当当前事务中首次查询的数据,并在后续查询与第一次查询一致。 ---- ### 查询缓存 应用程序中 重复执行 同一条 sql 这个情况很常见,往往不同的逻辑类相互使用时会有这个问题,因为它们独立使用时确实需要执行查询,但当组合使用时就会都执行了查询,这在开发中很常见,数据库早就想到了这个问题,在同一个会话连接(一个连接上只能同时开启一个事务)短时间内执行相同的查询sql,会有缓存,当数据没有被修改时都会命中查询缓存的,所以不用担心多次将数据从磁盘的某个位置读取出来的性能问题了。 有关mysql长连接涉及事务的问题? https://www.zhihu.com/question/62603122 http://www.nowamagic.net/librarys/veda/detail/756 https://www.cnblogs.com/coshaho/p/7192343.html https://blog.csdn.net/yongqi_wang/article/details/86674088 > 如果查询缓存使用了很大量的内存,缓存失效操作就可能成为一个非常严重的问题瓶颈。如果缓存中存放了大量的查询结果,那么缓存失效操作时整个系统可能都会僵死一会。因为这个操作时靠一个全局锁操作保护的,所有需要做该操作的查询都需要等待这个锁,而且无论是检测是否命中缓存、还是缓存失效检测都需要等待这个全局锁。 > 并不是什么情况下查询缓存都会提高系统性能的。缓存和失效都会带来额外的消耗,**所以只有当缓存带来的资源节约大于其本身的资源消耗时才会给系统带来性能提升。这根具体的服务器压力模型有关。** > 不过查询缓存命中率是一个很难判断的数值。命中率多大才是好的命中率?**只要查询缓存带来的效率提升大于查询缓存带来的额外消耗,即使百分之三十的命中率对系统性能提升也有很大好处。** 另外,缓存了哪些查询也很重要,例如,被缓存的查询本身消耗非常巨大,那么即使缓存命中率非常低,也任然会对系统性能提升有好处。所以,没有一个简单的规则可以判断查询缓存是否对系统有好处。 ---- ### mysql 索引 [MySQL 索引概览_wallace_www的博客-CSDN博客](https://blog.csdn.net/wallace_www/article/details/117264149) > 可以扩展了解一下,理论上最左匹配原则中索引对 where 中子句的顺序也是敏感的,但是由于MySQL的查询优化器会自动调整 where 子句的条件顺序以使用适合的索引,所以实际上 where 子句顺序不影响索引的效果。 [MySQL 索引结构_wallace_www的博客-CSDN博客_mysql 默认索引结构](https://blog.csdn.net/wallace_www/article/details/117304921?spm=1001.2014.3001.5501) [mysql协议详解_耿小渣的进阶之路-CSDN博客_mysql protocol](https://blog.csdn.net/u014608280/article/details/80484861) ---- ### 隔离级别 1. READ-UNCOMMITTED 未提交读 脏读问题(读到了未提交的数据) 2. READ-COMMITTED 已提交读,解决脏读问题,新问题:不可重复读(事务内,两次相同的查询可能结果不一致) 3. REPEATABLE-READ 可重复读,解决不可重复读问题,新问题:幻读(事务内,两次查询可能出现查询出前一次未查出的新行) 4. SERIALIZABLE 串行化,解决幻读问题,但是事务变串行了,吞吐降低了 select @@transaction_isolation; READ-COMMITTED 阿里云 rds 默认 已提交读 通常 RC 已提交读 已经够了,大部分应用 能够接受 不可重复读 的问题,RC 在提供了较好的性能的同时也能满足应用需求,是一个不错的选择。只有情况特殊时,才需要 RR 、SR 级别。 ---- last update:2018-9-23 17:03:51