关于事务:事务原理了解一下

1、聊聊事务原理一句话来说,要么胜利,要么失败,不能有两头态 1.1、回顾一下ACIDAtomic:原子性,就是一堆SQL,要么一起胜利,要么都别执行,不容许某个SQL胜利了,某个SQL失败了,这就是扯淡么Consistency:一致性,这个是针对数据一致性来说的,就是一组SQL执行之前,数据必须是精确的,执行之后,数据也必须是精确的。别搞了半天,执行完了SQL,后果SQL对应的数据批改没给你执行,这不是坑爹么Isolation:隔离性,这个就是说多个事务在跑的时候不能相互烦扰,别事务A操作个数据,弄到一半儿还没弄好呢,后果事务B来改了这个数据,导致事务A的操作出错了,那不就搞笑了么Durability:持久性,事务胜利了,就必须永恒对数据的批改是无效的,别过了一会儿数据本人没了,不见了,那就好玩儿了1.2、陈词滥调的事务隔离级别读未提交(Read Uncommitted):这个很坑爹,就是说某个事务还没提交的时候,批改的数据,就让别的事务给读到了,这就恶心了,很容易导致出错的。这个也叫做脏读读已提交(Read Committed)又叫做不可反复读:这个比下面那个略微好一点,然而一样比拟难堪,就是说事务A在跑的时候, 先查问了一个数据是值1,而后过了段时间,事务B把那个数据给批改了一下还提交了,此时事务A再次查问这个数据就成了值2了,这是读了人家事务提交的数据啊,所以是读已提交。这个也叫做不可反复读,就是所谓的一个事务内对一个数据两次读,可能会读到不一样的值可反复读(Read Repeatable):这个就是比下面那个再好点儿,就是说事务A在执行过程中,对某个数据的值,无论读多少次都是值1;哪怕这个过程中事务B批改了数据的值还提交了,然而事务A读到的还是本人事务开始时这个数据的值串行化:幻读,不可反复读和可反复读都是针对两个事务同时对某条数据在批改,然而幻读针对的是插入,比方某个事务把所有行的某个字段都批改为了2,后果另外一个事务插入了一条数据,那个字段的值是1,而后就难堪了。第一个事务会忽然发现多进去一条数据,那个数据的字段是1。如果要解决幻读,就须要应用串行化级别的隔离级别,所有事务都串行起来,不容许多个事务并行操作2、MySQL的事务隔离级别分析一下2.1、InnoDB存储引擎架构设计 2.2、MySQL是怎么实现RR事务隔离的2.2.1、Redo Log日志理解一下 2.2.2、MVC Undo Log日志链条事务A,插入了一条数据接着有一个事务B跑来批改了一下这条数据接着假如事务C又来批改了一下这个值为C2.2.3、 MySQL RR readview + undo log日志链首先假如有一条数据是事务id=50的一个事务插入,之后有事务A和事务B同时在运行事务A发动一个查问,他就是第一次查问就会生成一个ReadView,此时ReadView里的creator_trx_id是60,min_trx_id是60,max_trx_id=71,m_ids=[60,70]这个时候事务A基于这个ReadView去查问这条数据,会发现这条数据的trx_id=50,是小于ReadView里的min_trx_id的,阐明他发动查问之前,早就有事务插入这条数据了还提交了所以此时能够查到这条原始数据接着事务B此时更新了这条数据的值为B,此时会批改trx_id=70,同时生成一个undo log,而且此时事务B还提交了,也就是说事务B曾经完结了这个时候有一个问题,ReadView中的m_ids此时还会是60和70吗?那是必然的,因为ReadView一旦生成就不会扭转了,这个时候尽管事务B曾经完结了,然而事务A的ReadView里,还是会有60和70两个事务id,意思就是说,你在事务A开启的时候,事务B过后是在运行的那好,接着此时事务A去查问这条数据的值,他会诧异的发现此时数据的trx_id是70了,70一方面是在ReadView的min_trx_id和max_trx_id的范畴区间的,同时还在m_ids列表中阐明起码是事务A开启查问的时候,id=70的这个事务B还是在运行的,而后由这个事务B更新了这条数据,此时事务A是不能查问到事务B的这个值的,因而这个时候只能顺着指针往历史版本链条下来找接着事务顺着指针找到上面一条数据,trx_id为50,是小于ReadView的min_trx_id的,阐明在他开启查找之前,就曾经提交这个事务了,所以事务A是能够查问到这个值的,此时事务A查到的是原始值你事务A屡次读同一个数据,每次读到的都是一样的值,除非是他本人批改了值,否则读到的都是一样的值,不论别的事务如何批改数据,事务A的ReadView始终是不变的,他基于这个ReadView始终看到的值是一样的接着此时事务A再次查问,此时发现符合条件有2条数据,一条是原始值的那个数据,一条是事务C插入的那条数据,然而事务C插入的那条数据的trx_id是80,这个80是大于本人的ReadView的max_trx_id的,阐明是本人发动查问之后,这个事务才启动的,所以此时这条数据是不能查问的,所以MySQL RR防止了幻读3、XA标准理解一下X/Open的组织定义了分布式事务的模型,这外面有几个角色,就是AP(Application,应用程序),TM(Transaction Manager,事务管理器),RM(Resource Manager,资源管理器),CRM(Communication Resource Manager,通信资源管理器)TM的话就是一个在零碎里嵌入的一个专门治理横跨多个数据库的事务的一个组件,RM的话说白了就是数据库(比方MySQL),CRM能够是消息中间件(然而也能够不必这个货色) 那么XA是啥呢?说白了,就是定义好的那个TM与RM之间的接口标准,就是治理分布式事务的那个组件跟各个数据库之间通信的一个接口 这个XA仅仅是个标准,具体的实现是数据库产商来提供的,比如说MySQL就会提供XA标准的接口函数和类库实现 JTA(Java Transaction API),是J2EE的编程接口标准,它是XA协定的JAVA实现,例如Atomikos, bitronix都提供了jar包形式的JTA实现框架。这样咱们就可能在Tomcat或者Jetty之类的服务器上运行应用JTA实现事务的利用零碎,个别是单机跨多库状况下,然而在微服务的明天,个别都是一个服务一个库,个别都不会这么干了 3.1、2PC是啥?X/Open组织定义的一套分布式事务的模型,还是比拟虚的,还没方法落地,而且XA接口标准也是一个比拟务实的一个货色,还是没法落地的2PC说白了就是基于XA标准搞的一套分布式事务的实践,也能够叫做一套标准,或者是协定 毛病:1、同步阻塞:在阶段一里执行prepare操作会占用资源,始终到整个分布式事务实现,才会开释资源,这个过程中,如果有其他人要拜访这个资源,就会被阻塞住2、单点故障:TM是个单点,一旦挂掉就完蛋了3、事务状态失落:即便把TM做成一个双机热备的,一个TM挂了主动选举其余的TM进去,然而如果TM挂掉的同时,接管到commit音讯的某个库也挂了,此时即便从新选举了其余的TM,压根儿不晓得这个分布式事务以后的状态,因为不晓得哪个库接管过commit音讯,那个接管过commit音讯的库也挂了4、脑裂问题:在阶段二中,如果产生了脑裂问题,那么就会导致某些数据库没有接管到commit音讯,那就完蛋了,有些库收到了commit音讯,后果有些库没有收到,这咋整呢,那必定完蛋了 3.1、3PC是啥?如果人家TM在DoCommit阶段发送了abort音讯给各个库,后果因为脑裂问题,某个库没接管到abort音讯,本人还执行了commit操作,不是也不对么 4、TCC是个什么原理(柔性事务)try、confirm和cancel 4.1、容许空回滚事务协调器在调用 TCC 服务的一阶段 Try 操作时,可能会呈现因为丢包而导致的网络超时,此时事务管理器会触发二阶段回滚,调用 TCC 服务的 Cancel 操作,而 Cancel 操作调用未呈现超时TCC 服务在未收到 Try 申请的状况下收到 Cancel 申请,这种场景被称为空回滚;空回滚在生产环境经常出现,用户在实现TCC服务时,应容许容许空回滚的执行,即收到空回滚时返回胜利 4.2、防悬挂管制事务协调器在调用 TCC 服务的一阶段 Try 操作时,可能会呈现因网络拥挤而导致的超时,此时事务管理器会触发二阶段回滚,调用 TCC 服务的 Cancel 操作,Cancel 调用未超时;在此之后,拥挤在网络上的一阶段 Try 数据包被 TCC 服务收到,呈现了二阶段 Cancel 申请比一阶段 Try 申请先执行的状况,此 TCC 服务在执行完到的 Try 之后,将永远不会再收到二阶段的 Confirm 或者 Cancel ,造成 TCC 服务悬挂。用户在实现 TCC 服务时,要容许空回滚,然而要拒绝执行空回滚之后 Try 申请,要避免出现悬挂 ...

April 2, 2023 · 1 min · jiezi

关于事务:mysql-事务隔离级别介绍

一、论断-MySQL5.6和MySQL 8 的默认隔离级别为 Repeatable Read(可反复读)在 Repeatable Read 隔离级别下,事务开始时会获取一致性快照,并在事务完结之前放弃这个快照。这意味着在事务执行期间,读取的所有数据都来自于这个快照,而不会受到其余事务的批改影响。因而,在这个隔离级别下,即便其余事务批改了数据,以后事务也不会看到这些批改,直到以后事务提交。 须要留神的是,MySQL 的隔离级别能够通过设置来更改,并且不同的存储引擎可能有不同的默认隔离级别。因而,在理论利用中,须要依据具体的需要来抉择适合的隔离级别。 二、MySQL的四种隔离级别存在的问题1、读未提交(Read Uncommitted):最低级别的隔离级别,容许一个事务读取另一个事务未提交的数据,可能导致脏读、不可反复读和幻读等问题。 2、读已提交(Read Committed):保障一个事务读取到的数据是另一个事务曾经提交的数据,解决了脏读的问题,但可能导致不可反复读和幻读等问题。 3、可反复读(Repeatable Read):保障一个事务读取到的数据不会被其余事务批改,解决了脏读和不可反复读的问题,但可能导致幻读等问题。 4、串行化(Serializable):最高级别的隔离级别,齐全解决了脏读、不可反复读和幻读等问题,然而会导致并发性能升高。 须要留神的是,不同的隔离级别解决的问题和带来的问题是绝对的,并且不同的利用场景须要不同的隔离级别来保证数据的一致性和并发性能。因而,在抉择隔离级别时须要依据具体的需要进行衡量和抉择。 三、脏读,幻读,不可反复读含意解释1、脏读(Dirty Read)指的是一个事务读取了另一个事务还未提交的数据。如果另一个事务最终回滚了,则第一个事务读取的数据是有效的。这种读取未提交的数据可能导致意外的后果,因为这些数据可能会随时被回滚或批改。 2、幻读(Phantom Read)是指在同一事务中执行雷同的查问,但返回了不同的后果行数。这通常产生在并发环境中,其中一个事务在读取数据时另一个事务插入了一些新的数据行或删除了一些已存在的数据行。 3、不可反复读(Non-repeatable Read)指的是在同一事务中屡次执行雷同的查问,但返回不同的后果。这可能是因为其余事务批改了数据或者在同一事务中本人批改了数据导致的。这种状况下,数据在事务执行期间产生了扭转,所以事务之间可能会发生冲突。 这些问题可能会影响数据库的一致性和可靠性,因而须要采取相应的措施来解决这些问题,如应用锁机制、事务隔离级别等。 四、隔离级别选型参考抉择适当的数据库隔离级别须要思考多个因素,例如应用程序的要求、并发性需求、性能需求、数据完整性需要等等。以下是一些通用的思考因素: 1、并发性需求:如果应用程序须要反对高并发性,那么能够思考抉择较低的隔离级别,例如读未提交或读已提交。这些隔离级别能够缩小锁定抵触,从而进步并发性。然而,这也会减少一些数据不统一的可能性。 2、数据完整性需要:如果应用程序须要保证数据的完整性,例如防止脏读(Dirty Read)和不可反复读(Non-repeatable Read)等问题,那么能够思考抉择较高的隔离级别,例如可反复读或可串行化。 3、应用程序要求:不同的应用程序对数据一致性的要求也不同。例如,某些应用程序可能更重视数据的实时性,而对数据的一致性要求较低,那么能够抉择较低的隔离级别。然而,对于一些须要保证数据一致性的应用程序,须要抉择更高的隔离级别。

March 5, 2023 · 1 min · jiezi

关于事务:事务的隔离级别

事务的隔离级别事务的四大个性别离是:原子性、一致性、隔离性、持久性 幻读和不可反复读都是在同一个事务中屡次读取了其余事务曾经提交的事务的数据导致每次读取的数据不统一,所不同的是不可反复读读取的是同一条数据,而幻读针对的是一批数据整体的统计(比方数据的个数) 以MYSQL数据库来剖析四种隔离级别 第一种隔离级别:Read uncommitted(读未提交)如果一个事务曾经开始写数据,则另外一个事务不容许同时进行写操作,但容许其余事务读此行数据,该隔离级别能够通过“排他写锁”,然而不排挤读线程实现。这样就防止了更新失落,却可能呈现脏读,也就是说事务B读取到了事务A未提交的数据 解决了更新失落,但还是可能会呈现脏读 第二种隔离级别:Read committed(读提交)如果是一个读事务(线程),则容许其余事务读写,如果是写事务将会禁止其余事务拜访该行数据,该隔离级别防止了脏读,然而可能呈现不可反复读。事务A当时读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据曾经产生了扭转。 解决了更新失落和脏读问题 第三种隔离级别:Repeatable read(可反复读取)可反复读取是指在一个事务内,屡次读同一个数据,在这个事务还没完结时,其余事务不能拜访该数据(包含了读写),这样就能够在同一个事务内两次读到的数据是一样的,因而称为是可反复读隔离级别,读取数据的事务将会禁止写事务(但容许读事务),写事务则禁止任何其余事务(包含了读写),这样防止了不可反复读和脏读,然而有时可能会呈现幻读。(读取数据的事务)能够通过“共享读镜”和“排他写锁”实现。 解决了更新失落、脏读、不可反复读、然而还会呈现幻读 第四种隔离级别:Serializable(可序化)提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过“行级锁”是无奈实现序列化的,必须通过其余机制保障新插入的数据不会被执行查问操作的事务拜访到。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,个别很少应用,在该级别下,事务程序执行,不仅能够防止脏读、不可反复读,还防止了幻读 解决了更新失落、脏读、不可反复读、幻读(虚读) 以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低,像Serializeble这样的级别,就是以锁表的形式(相似于Java多线程中的锁)使得其余线程只能在锁外期待,所以平时选用何种隔离级别应该依据理论状况来,在MYSQL数据库中默认的隔离级别是Repeatable read(可反复读)。 在MYSQL数据库中,反对下面四种隔离级别,默认的为Repeatable read(可反复读);而在Oracle数据库中,只反对Serializeble(串行化)级别和Read committed(读已提交)这两种级别,其中默认的为Read committed级别 在MYSQL数据库中查看以后事务的隔离级别 SELECT @@tx_isolation;在MYSQL数据库中设置事务的隔离级别: 记住:设置数据库的隔离级别肯定要是在开启事务之前: 如果是应用JDBC对数据库的事务设置隔离级别的话,也应该是在调用Connecton对象的setAutoCommit(false)办法之前,调用Connection对象的setTransactionIsolation(level)即可设置以后连贯的隔离级别,至于参数level,能够应用Connection对象的字段: 在JDBC中设置隔离级别的局部代码: 隔离级别的设置只对以后连贯无效,对于应用MYSQL命令窗口而言,一个窗口就相当于一个连贯,以后窗口设置的隔离级别只对以后窗口中的事务无效,对于JDBC操作数据库来说,一个Connection对象相当与一个连贯,而对于Connection对象设置的隔离级别只对该Connection对象无效,与其余连贯Connection对象无关

November 23, 2022 · 1 min · jiezi

关于事务:真正理解可重复读事务隔离级别

原创:打码日记(微信公众号ID:codelogs),欢送分享,转载请保留出处。事务简介SQL 规范定义了四种隔离级别,这四种隔离级别别离是: 读未提交(READ UNCOMMITTED):在这种隔离级别下,可能会呈现脏读、不可反复读、幻读问题。 读提交 (READ COMMITTED):解决脏读问题。 可反复读 (REPEATABLE READ):解决脏读、不可反复读问题。 串行化 (SERIALIZABLE):解决脏读、不可反复读、幻读问题。这里不具体解释脏读、不可反复读、幻读问题这些景象了,介绍事务的文章或书根本都会说得很分明,但请留神,这些都是事务并发运行时可能产生的景象,而不能了解为数据库的bug! 但我在工作多年后再想到这些常识时,对可反复读行为产生十分大的疑难,如下: 程序员为什么不把第一次读的数据保留在内存中,第二次重复使用不行吗?为啥要数据库保障两次读出的数据是雷同的(即可反复读),并且读两次数据库会节约更多数据库资源而升高性能。另外,每次读出最新的数据有啥不好?读个历史数据有啥用?既然如此,可反复读到底有什么用? 举个例子咱们能够考查上面这样的场景,有个金融产品有一个性能,须要查找那些账号余额与账号交易流水对不上的用户,咱们叫到账工作吧,而且要在对账工作运行时,用户交易失常不中断。 比方某账号余额100元,该账号有两笔交易记录(+200, -100),这样这个账号就对账失常,但如果程序查问出账号余额100元后,这时用户又转出100元,咱们再去查问交易记录时,在不同事务隔离级别下会查到不同的后果,如下: 提交读可反复读备注 开始时余额100,交易记录(+200, -100)查问到余额100元查问到余额100元 另一事务收入100元,余额缩小为0,并提交查问到交易记录(+200, -100, -100)查问到交易记录(+200, -100) 对账失败对账胜利 可见,在提交读场景下,对账失败了,而可反复读场景下对账胜利了,而实际上这个账号的余额与交易记录始终是对齐的。我在MySQL5.7亲自验证,后果的确如此。 所以可反复读具体作用是什么呢? 所以可反复读具体作用是什么呢? 所以可反复读具体作用是什么呢?它实质作用是保障在开启事务后,对数据库所有表数据的查问,查问到的都是雷同的版本,就是开启事务那一刻的版本(在mysql中为第一次查问那一刻的版本),而不论它是查问的一个表,还是不同的表,所以可反复读事务级别解决的并不是外表上的不可反复读景象。 可反复读也常常用在数据库备份过程中,因为数据库备份时数据还有可能在一直批改,咱们必定心愿备份整个数据库开始时的那个版本,而不心愿备份的数据有些是之前那个时刻版本的,有些则是之后那个工夫版本的。 这个例子也阐明了另一个问题,即什么时候须要应用事务,刚写代码时咱们常常被告知所有写操作要放到一个事务中,实际上,一些非凡场景,多个读操作也要放到一个事务中。 换角度了解事务咱们能够不从赃读,不可反复读,幻读这些景象看事务隔离级别,而是从读一致性上来了解,如下: 未提交读,不解决任何读一致性问题,只保障了事务的写一致性(又称原子性),事务提交后,要么都批改胜利,要么都不胜利。提交读,保障其它并发事务的批改要么全可见,要么全不可见,能够了解为"写一致性读",留神断句!"写一致性"、"读",这是最罕用的事务隔离级别,能够保障业务数据含意的一致性。 比方用户下单场景,开事务先后写了order主表订单数据与order_item子表订单中商品数据,如果在两个写两头,有一个未提交读的事务,去读取order与order_item,就会发现只读到了order而没有读到order_item,这给用户看到了,那肯定会吓一跳的,我交钱了后果买了一个空单!尽管用户刷新一下又能够看到残缺数据。 但如果应用提交读事务隔离级别就不会有这个问题,用户要么查不到任何数据,要么查到残缺数据,这也从侧面阐明了逻辑上有关联的数据批改,肯定要开事务来操作。可反复读,保障事务开启或第一次查问那一刻,前面所有对整个数据库所有表的读都是读那一刻的版本,当然包含反复读同一张表,也能够了解为"统一版本读"。串行化,一般来说解决的是并发上的逻辑谬误,因为此级别逻辑上能够认为所有事务都是串行执行的(尽管数据库实际上可能会并发执行)。 比方两个事务先判断数据有没有,没有则插入数据的场景,并发状况下两个事务同时查问,发现没有数据后插入数据,后果插入了两条数据,而应用串行化隔离级别就没有这个问题,这在并发编程中叫竞态条件,所以串行化解决了读写的竞态条件问题。 当然,这个问题也能够通过增加惟一索引,或应用内部显示加锁的办法来解决。mysql可反复读是否解决幻读在网上,咱们常常会看到两种说法的文章,有的说mysql可反复读解决了幻读问题的,也有说没解决的。 这么说也对也不对,具体差别在于以后读取操作是快照读还是以后读,如下: 快照读以后读备注 开始时订单1下有两个order_item,别离A和Bselect * from order_item where oid=1(读到A和B)select * from order_item where oid=1(读到A和B)第一次读 另一事务在订单1下插入C并提交select * from order_item where oid=1(读到A和B)select * from order_item where oid=1 for update(读到A、B和C)第二次读下面后果同样在mysql5.7下验证通过,咱们称其中的select xxx for update为以后读,即读取最新的数据,一般的select则是快照读,在mysql中insert、update、delete、select xxx for update都是以后读。 ...

April 10, 2022 · 1 min · jiezi

关于事务:事务回滚后回调

对了加了 @Transactional的办法,当办法抛出异样时会主动回滚。如果咱们此时想在回滚前执行一些办法,如开释锁。能够这么写:/** * 实用于事务办法:事务实现后再开释锁 * * @param key * @param requestId */private void unlockAfterTransaction(String key, String requestId) { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCompletion(int status) { super.afterCompletion(status); distributeLocker.unlock(key, requestId); log.info("unlock success"); } });}

February 12, 2022 · 1 min · jiezi

关于事务:事务方法执行中断导致锁表

报错信息:数据库插入超时,然而查问失常。 起因剖析:本地debug带事务注解的办法时候,执行到一半之后间接停掉了服务。此时的事务没有失常开释,导致mysql锁住了表。 解决办法: 能够先执行命名查看是否有被锁住的表: show OPEN TABLES where In_use > 0;确认起因后,执行 SELECT * FROM information_schema.innodb_trx 找到锁表的过程id(try_mysql_thread_id字段),而后间接 kill xxx

January 11, 2022 · 1 min · jiezi

关于事务:MySQL事务隔离级别与MVCC

前言本篇文章首先会对数据库事务的几个根底概念进行阐明,次要是事务ACID模型,并发事务带来的问题和事务隔离级别。而后在此基础上,会对MySQL的InnoDB引擎中的一致性非锁定读取(Consistent Nonlocking Reads)进行较为深刻的演示和解析,次要波及MVCC机制,undo log和快照。 参考 《深入浅出MySQL》MySQL官网文档注释一. 事务和事务ACID模型1. 事务概念事务概念如下。 事务是由一组SQL语句组成的逻辑处理单元。即能够将事务了解为一系列的对数据库的操作汇合,这些操作要么全副失效,要么全副不失效。 2. ACID模型事务的ACID模型如下表所示。 ACID属性项解释原子性(Atomicity)事务是一个原子操作单元,其对数据的批改,要么全都执行,要么全都不执行。一致性(Consistent)在事务开始和实现时,数据都必须保持一致状态。即所有相干的数据规定都必须利用于事务的批改,以保持数据的完整性;事务完结时,所有的外部数据结构(如B树索引或双向链表)也都必须是正确的。隔离性(Isolation)数据库系统提供肯定的隔离机制,保障事务在不受内部并发操作影响的“独立”环境执行。即事务处理过程中的中间状态对其它事务是不可见的。持久性(Durable)事务实现之后,事务对于数据的批改是永久性的,即便呈现系统故障也可能放弃。二. 并发事务带来的问题如果事务之间严格依照串行的形式执行,不会呈现并发问题,然而会极大升高对数据库资源的利用率。因而为了减少数据库资源的利用率,进步数据库系统的事务吞吐量,通常事务之间是并发执行的,由此也引入了并发事务带来的问题,如下表所示。 并发事务带来的问题解释脏读(Dirty Reads)一个事务正在对一条记录做批改,在这个事务实现并提交前,这条记录的数据就处于不统一状态,这时,第二个事务来读取同一条记录,如果不加管制,第二个事务会读取这条处于不统一状态的记录,即读取到脏数据,称为脏读。不可反复读(Non-Repeatable Reads)一个事务在读取某些数据后的某个工夫,再次读取以前读过的数据,却发现其读出的数据曾经产生了扭转或某些记录曾经被删除,这种景象称为不可反复读。幻读(Phantom Reads)一个事务按雷同的查问条件从新读取以前检索过的数据,却发现其它事务插入了满足其查问条件的新数据,这种景象称为幻读。三. 事务隔离级别为了解决因为并发事务带来的脏读,不可反复读和幻读的问题,须要借助数据库提供肯定的事务隔离机制,通常有基于乐观锁的加锁机制和基于无锁的多版本并发管制(MultiVersion Concurrency Control, MVCC)。事务隔离的本质就是使事务在肯定水平上串行化执行,事务隔离得越严格,串行化的水平就越高,然而相应的数据库的并发能力就越低,为了解决事务的隔离与并发的矛盾,引入了事务隔离级别这一概念,不同的隔离级别会导致不同的事务并发问题,每种隔离级别的形容如下表所示。 事务隔离级别形容读未提交(Read uncommitted,RU)事务能够感知到其它未提交事务对数据库所做的变更。读已提交(Read committed,RC)事务无奈感知到其它未提交事务对数据库所做的变更。可反复度(Repeatable read,RR)事务在执行过程中,只能看到事务启动时刻数据库的状态,事务无奈感知到其它事务对数据库所做的变更。可序列化(Serializable)事务对数据的操作会加锁,通过加锁使事务串行化执行,是最高事务隔离级别,但数据库的并发能力最低。特地阐明:在MySQL的InnoDB引擎中,以读未提交隔离级别为例,某个事务如果隔离级别是读未提交,并不阐明该事务在提交事务前对数据库所做的变更对其它事务可见,而是该事务查问数据时抉择将其它未提交事务对数据库所做的变更置为可见。即事务隔离级别是事务在查问数据时抉择所有被查数据的一种规定,局部文章中对事务隔离级别的概念进行了混同,故特此说明。 不同的隔离级别下,事务读数据一致性和并发事务带来的问题能够用下表示意。 上面将针对不同的事务隔离级别,基于MySQL的InnoDB引擎进行简略的示例演示。首先创立表,SQL语句如下所示。 CREATE TABLE info( id INT(11) PRIMARY KEY AUTO_INCREMENT, num INT(11) NOT NULL)插入一条数据以供查问,SQL语句如下所示。 INSERT INTO info (num) VALUES (20);第一个示例是读未提交,先将事务2隔离级别设置为READ UNCOMMITTED,事务执行流程如下图所示。 事务2读取到了事务1未提交的数据,在事务1回滚之后,数据库中id为1的数据的num为20,此时事务2读取到的数据成为了脏数据,产生了脏读。 第二个示例是读已提交,先将事务2隔离级别设置为READ COMMITTED,事务执行流程如下图所示。 步骤3中事务1更新了id为1的数据的num为25,步骤4中事务2查问了id为1的数据且num为20,阐明在读已提交事务隔离级别下,事务无奈感知其它未提交事务对数据库的更改。步骤5中事务1提交了事务,步骤6中事务2又查问了id为1的数据且num为25,那么事务2在同一次事务中对同一条数据的两次读取后果不雷同,产生了不可反复读。 进行第三个示例前,先将id为1的数据的num更改回20。 第三个示例是可反复读,先将事务2隔离级别设置为REPEATABLE READ,事务执行流程如下图所示。 事务1更改了id为1的数据的num为25,并提交了事务,事务2在事务1提交事务前后别离执行了一次查问,并且第二次查问后果与第一次查问后果雷同,所以在可反复读隔离级别下解决了不可反复读问题。 进行第四个示例前,先将id为1的数据的num更改回20。 第四个示例仍旧是可反复读,事务执行流程如下图所示。 步骤3中事务1插入了一条num为25的数据,步骤4中事务2执行了一次范畴查问,查问后果不蕴含num为25的数据,步骤5中事务1提交了事务,步骤6中事务2又执行了一次范畴查问,查问后果还是不蕴含num为25的数据,所以在MySQL的InnoDB引擎下将事务隔离级别设置为可反复读时,还能够解决幻读的问题。通常状况下,可反复读隔离级别是无奈解决幻读的,然而MySQL的InnoDB引擎应用了MVCC技术,能够在可反复读隔离级别下,躲避了幻读的问题,对于MVCC技术,将在下一大节进行阐明。 四. 一致性非锁定读取对于一致性非锁定读取的官网概念,能够参见MySQL的官网文档:Consistent Nonlocking Reads。定义如下所示。 A consistent read means that InnoDB uses multi-versioning to present to a query a snapshot of the database at a point in time. The query sees the changes made by transactions that committed before that point in time, and no changes made by later or uncommitted transactions.The exception to this rule is that the query sees the changes made by earlier statements within the same transaction. ...

January 6, 2022 · 2 min · jiezi

关于事务:事务

事务是数据库操作最根本单元,逻辑上一组操作,要么都胜利,如果有一个失败所有操作都失败典型场景:银行转账

July 4, 2021 · 1 min · jiezi

关于事务:电商千万级交易的金手指分布式事务管理

摘要:从古至今,咱们的交易与生产过程 产生着天翻地覆的变动。明天,带大家一起 解密继续千年那些买买买背地的故事。本文分享自华为云社区《揭秘买买买千万级交易背地的那些事》,原文作者:华为云头条 。 上云总动员干货进行时,带你一起揭秘买买买背地的那些事! 从古至今,咱们的交易与生产过程 产生着天翻地覆的变动 明天,云宝想带大家一起 解密继续千年那些买买买背地的故事 遐想远古期间 人们通过以物易物的形式实现生产需要 但常因物品价值不对等而不欢而散 起初呈现货币,从贝壳到铜钱 “买买买”是实现了但钱币难以随身携带 交易变成沉甸甸的“累赘” 宋朝呈现纸币“交子”,大大晋升了交易效率 但随之而来的“伪钞”问题也困扰人们千年 当初,互联网技术的倒退 让网络领取浸透生存的每一个角落 各类交易通过扫码、碰一碰就能领取 但审慎的小伙伴肯定关注过 最早的线上购物,已经呈现买家下单胜利 付了钱却没收到货 卖家接到投诉却没找到订单记录 生生吃了个差评却无处说理 这到底是怎么肥事? 云宝这就带你走近微服务之 为领取操碎了心特别篇 不过,在理解古代交易产生的问题之前 咱们先来看看上面3个内容 Q1:什么是事务?事务是由一组SQL语句组成的逻辑处理单元,可看做是一次大流动由不同小流动组成;它们具备4个属性,即事务ACID属性: 原子性(Atomicity)一致性(Consistency)隔离性(Isolation)持久性(Durability)大家只需重点记住 TA们要么全副胜利要么全副失败 不存在“薛定谔的事务” Q2:什么是分布式事务?单体利用拆分成多个利用后,造成了SOA架构,或者是微服务架构。这时候就变成了一个分布式系统,而依赖分布式系统所产生的事务,就是分布式事务。 划个重点 单体架构下的一般事务因为利用未拆分 所有性能混在一起,牵一动员全身 而微服务架构下的分布式事务则因为利用拆分后 各零碎分工合作,权责明显 Q3:为什么须要分布式事务?给大家看个分布式事务典型场景~ 整个电商购物波及到了4个零碎 从订单零碎开始发动事务 如果在下单这个过程中 仓储零碎最终并没有生成出库记录 那库存零碎应该要勾销扣库存减扣 积分零碎应该也要勾销加积分 TA们能力放弃数据一致性 防止买卖双方“喜剧”的产生 数据不同步 ▼ 分布式事务让数据统一 ▼ 怎么样,也来一款试试吗? 给大家举荐下 华为云分布式事务管理DTM能有机整合整体购物流程 岂但不便买家、卖家、平台治理 而且反对电子商务平安凋敝地倒退 从此和鸡飞狗跳的购物问题say拜拜啦 华为云DTM是华为云分布式事务管理中间件,提供了高牢靠的分布式事务处理能力。反对跨微服务事务、跨库事务、多数据源、非侵入式事务、TCC事务、事务监控、高TPS事务处理能力及数据分析等性能场景,帮忙企业满足外围业务数据(如交易数据)一致性需要。 亮点太多几乎说不完 ...

March 17, 2021 · 1 min · jiezi

关于事务:技术分享-大数据量更新回滚效率提升方法

作者:周启超爱可生北分团队 DBA,次要负责项目前期建设及前期疑难问题反对。做事认真,对事负责。本文起源:原创投稿*爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。咱们常常会遇到操作一张大表,发现操作工夫过长或影响在线业务了,想要回退大表操作的场景。在咱们进行大表操作之后,期待回滚是一个很漫长的过程,只管你可能对晓得一些缩短工夫的办法,处于对生产环境数据完整性的敬畏,也会抉择不做染指。最终抉择不作为的起因大多源于对操作影响的不确定性。实际出真知,上面针对两种次要晋升事务回滚速度的形式进行验证,一种是晋升操作可用内存空间,一种是通过停实例,禁用 redo 回滚形式进行进行验证。 仔细阅读过官网手册的同学,肯定留意到了对于晋升大事务回滚效率,官网提供了两种办法:一是减少 innodb_buffer_pool_size 参数大小,二是正当利用 innodb_force_recovery=3 参数,跳过事务回滚过程。第一种形式比拟温和,innodb_buffer_pool_size 参数是能够动静调整的,可行性也较高。第二种形式相较之下较暴力,但成果较好。 上面咱们看下第一种形式的成果如何:mysql>set global innodb_buffer_pool_size = 1073741824;Query OK, 0 rows affected (0.00 sec)mysql> begin;Query OK, 0 rows affected (0.00 sec)mysql> use sbtest;Database changedmysql> update sbtest1 set k=k+1; Query OK, 16023947 rows affected (7 min 23.23 sec)Rows matched: 16023947 Changed: 16023947 Warnings: 0mysql>mysql> set global innodb_buffer_pool_size = 5368709120;Query OK, 0 rows affected (0.00 sec)mysql> show variables like '%uffer_pool%';+-------------------------------------+----------------+| Variable_name | Value |+-------------------------------------+----------------+| innodb_buffer_pool_chunk_size | 134217728 || innodb_buffer_pool_dump_at_shutdown | ON || innodb_buffer_pool_dump_now | OFF || innodb_buffer_pool_dump_pct | 25 || innodb_buffer_pool_filename | ib_buffer_pool || innodb_buffer_pool_instances | 8 || innodb_buffer_pool_load_abort | OFF || innodb_buffer_pool_load_at_startup | ON || innodb_buffer_pool_load_now | OFF || innodb_buffer_pool_size | 5368709120 |+-------------------------------------+----------------+10 rows in set (0.02 sec)mysql> rollback;Query OK, 0 rows affected (6 min 39.41 sec)最后更新操作用时 7 min 23.23 sec 回滚操作用时 6 min 39.41 sec 相较于更新操作回滚操作耗时缩短了将近一分钟,成果仿佛并不显著。 当然回滚工夫和更新操作工夫进行比照不太谨严,上面对不同大小 innodb_buffer_pool_size 条件状况下更新和回滚操作工夫进行一个汇总。 ...

October 22, 2020 · 2 min · jiezi

关于事务:数据库事务的实现原理

1. 前言都晓得数据库事务有ACID个性(原子性、一致性、隔离型、持久性),本文简略聊一下它们的实现原理。 2. 日志文件2.1. redo logredo log叫做重做日志,是用来实现事务的持久性。该日志文件由两局部组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。 当事务提交之后会把所有批改信息都会存到该日志中。假如有个表叫做tb1(id,username) 当初要插入数据(3,ceshi) start transaction; select balance from bank where name="zhangsan"; // 生成 重做日志 balance=600 update bank set balance = balance - 400; // 生成 重做日志 amount=400 update finance set amount = amount + 400; commit; redo log 有什么作用?mysql 为了晋升性能不会把每次的批改都实时同步到磁盘,而是会先存到Boffer Pool(缓冲池)外头,把这个当作缓存来用。而后应用后盾线程去做缓冲池和磁盘之间的同步。 那么问题来了,如果还没来的同步的时候宕机或断电了怎么办?还没来得及执行下面图中红色的操作。这样会导致丢局部已提交事务的批改信息! 所以引入了redo log来记录已胜利提交事务的批改信息,并且会把redo log长久化到磁盘,零碎重启之后在读取redo log复原最新数据。 总结:redo log是用来复原数据的 用于保障,已提交事务的长久化个性。 2.2. undo logundo log 叫做回滚日志,用于记录数据被批改前的信息。他正好跟后面所说的重做日志所记录的相同,重做日志记录数据被批改后的信息。undo log次要记录的是数据的逻辑变动,为了在产生谬误时回滚之前的操作,须要将之前的操作都记录下来,而后在产生谬误时才能够回滚。 还用下面那两张表 ...

September 27, 2020 · 1 min · jiezi

关于事务:回滚

1.mySQL中的事务提交,mySQL中的事务提条默认是主动提交.2.回滚就是将数据恢复到原来的样子3.事务提交用commit.事务一旦提交,就不能够批改.4.若执行过程中出错,则不提交,事务须要回滚5.事务回滚和提交只会执行一个,提交就是失常执行,回滚就是不失常执行.

September 17, 2020 · 1 min · jiezi

关于事务:事务不好意思你被隔离了

事务的隔离级别数据库个别有四种个性:原子性、一致性、隔离性和持久性。而数据库的隔离级别就是针对其中的隔离性而言。 隔离级别也有四种:未提交读、提交读、可反复读、串行化。也不是所有数据库都反对事务的,甚至同一数据库不同存储引擎事务都不是一样的,例如MySQL数据库,外面InnoDB 引擎反对事务,而MyISAM 引擎不反对事务。 而在咱们利用层面,例如spring框架基于数据库事务的隔离级别也提供了本人的隔离级别,它有五种,其中四种和数据库一一对应,还有一种是DEFAULT,它示意应用数据库默认的隔离级别。对于不同数据库,默认的隔离级别可能是不一样的,例如MySQL数据库默认隔离级别是可反复读,而Oracle数据库是提交读。 上面来具体看看四种隔离级别是什么样的,代码是基于spring事务注解来管制事务的隔离级别。 未提交读(READ_UNCOMMITTED)此级别属于最低隔离级别,能够读取其余事务未提交的数据,相当于没有隔离,个别不必此种隔离级别。 例如上面这个例子: @Test@Transactional(isolation = Isolation.READ_UNCOMMITTED)public void select() throws Exception { List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from book where id = 1 "); System.out.println(maps);}@Test@Transactionalpublic void update() throws Exception { jdbcTemplate.update("update book set name = '三体2' where id = 1"); Thread.sleep(30000); throw new Exception("异样回滚");}首先执行update办法,外面尽管执行了批改操作,然而期待30s后抛出了异样,事务回滚,而在回滚之前执行select办法,发现读取到了 name='三体2'的后果。阐明:READ_UNCOMMITTED隔离级别能够读取到其余事务还没提交的数据,造成脏读。 读已提交(READ_COMMITTED)此级别只能读取曾经提交的数据,然而读取一行数据时,其余事务能够批改此行数据,再次读取时和上次不一样(不可反复读)。这是Oracle等数据库默认隔离级别。 示例: @Test@Transactional(isolation = Isolation.READ_COMMITTED)public void select() throws Exception { List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from book where id = 1 "); System.out.println("第一次:"+maps); Thread.sleep(30000); maps = jdbcTemplate.queryForList("select * from book where id = 1"); System.out.println("第二次:"+maps);}@Test@Transactionalpublic void update() throws Exception { jdbcTemplate.update("update book set name = '三体3' where id = 1");}首先执行select办法,输入第一次后果,此时name='三体2',而后进入睡眠;接着执行update办法,批改这条数据当前,select办法里第二次输入,此时name='三体3',造成了同一个事务中,两次读取后果不一样。阐明:READ_COMMITTED隔离级别尽管只能读取已提交的数据,然而读取时,其余事务还能够批改此数据(不可反复读)。 ...

August 11, 2020 · 1 min · jiezi

关于事务:spring事务咋和新冠病毒一样还会传染

什么是事务?数据库事务是指一系列紧密操作,要么全副胜利,要么全副失败。它有四种个性:原子性、一致性、隔离性和持久性。 而spring事务是封装在数据库事务之上的一种事务处理机制,它有两种治理形式:编程式事务和申明式事务。在平时应用中,咱们大多应用@Transactional申明式事务来治理,这也是spring举荐的形式,上面例子也对立采纳此种形式。 上面咱们次要来看看spring事务的流传机制 spring事务的流传机制spring事务的流传机制有七种:REQUIRED、REQUIRES_NEW、NESTED、SUPPORTS、NOT_SUPPORTED、MANDATORY和NEVER。 首先要晓得事务的流传指的是一个办法调用另外一个办法并将事务传递给它,而流传机制是针对被调用者,管制它是否被流传或者被怎么流传。注:前面屡次提到 此办法在/不在事务中,指的是调用者是否开启了事务。 上面咱们来仔细分析一下这几种传播方式。 REQUIRED(有事务则退出,没有则创立)REQUIRED是spring事务的默认形式,如果以后办法不在事务中,就创立一个新的事务;如果以后办法在事务中,就退出到这个事务中。 举个例子,咱们操作book书籍表和title章节表,先插入book表,而后再插入title表(上面几种传播方式都以这两张表为例)。BookServiceImpl中办法(调用者)调用TitleServiceImpl中办法(被调用者)。上面分两种状况: 1、调用者有事务 @Slf4j@Service@AllArgsConstructorpublic class BookServiceImpl implements BookService { private final BookMapper bookMapper; private final TitleService titleService; @Override @Transactional public void bookTransaction() { String transactionName = TransactionSynchronizationManager.getCurrentTransactionName(); log.info("调用者中事务:{}", transactionName); bookMapper.insert(new Book().setAuthor("zpg")); //流传事务 titleService.titleTransaction(); }}@Slf4j@Service@AllArgsConstructorpublic class TitleServiceImpl implements TitleService { private final TitleMapper titleMapper; @Override @Transactional(propagation = Propagation.REQUIRED) public void titleTransaction() { String transactionName = TransactionSynchronizationManager.getCurrentTransactionName(); log.info("被调用者中事务:{}", transactionName); titleMapper.insert(new Title().setName("第一章")); }}// 输入,被调用者应用的是调用者的事务调用者中事务:com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest被调用者中事务:com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest2、调用者无事务 ...

August 11, 2020 · 2 min · jiezi

为什么要避免大事务以及大事务如何解决

什么是大事务运行工夫比拟长,长时间未提交的事务就能够称为大事务 大事务产生的起因操作的数据比拟多大量的锁竞争事务中有其余非DB的耗时操作。。。大事务造成的影响并发状况下,数据库连接池容易被撑爆锁定太多的数据,造成大量的阻塞和锁超时执行工夫长,容易造成主从提早回滚所须要的工夫比拟长undo log收缩。。。如何查问大事务注:本文的sql的操作都是基于mysql5.7版本 以查问执行工夫超过10秒的事务为例: select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>10如何防止大事务通用解法在一个事务外面, 防止一次解决太多数据在一个事务外面,尽量避免不必要的查问在一个事务外面, 防止耗时太多的操作,造成事务超时。一些非DB的操作,比方rpc调用,音讯队列的操作尽量放到事务之外操作基于mysql5.7的解法在InnoDB事务中,行锁是在须要的时候才加上的,但并不是不须要了就立即开释,而是要等到事务完结时才开释。如果你的事务中须要锁多个行,要把最可能造成锁抵触、最可能影响并发度的锁尽量往后放通过SETMAX_EXECUTION_TIME命令, 来管制每个语句查问的最长工夫,防止单个语句意外查问太长时间监控 information_schema.Innodb_trx表,设置长事务阈值,超过就报警/或者kill在业务功能测试阶段要求输入所有的general_log,剖析日志行为提前发现问题设置innodb_undo_tablespaces值,将undo log拆散到独立的表空间。如果真的呈现大事务导致回滚段过大,这样设置后清理起来更不便附录查问事务相干语句注:sql语句都是基于mysql5.7版本 # 查问所有正在运行的事务及运行工夫select t.*,to_seconds(now())-to_seconds(t.trx_started) idle_time from INFORMATION_SCHEMA.INNODB_TRX t# 查问事务详细信息及执行的SQLselect now(),(UNIX_TIMESTAMP(now()) - UNIX_TIMESTAMP(a.trx_started)) diff_sec,b.id,b.user,b.host,b.db,d.SQL_TEXT from information_schema.innodb_trx a inner join information_schema.PROCESSLIST bon a.TRX_MYSQL_THREAD_ID=b.id and b.command = 'Sleep'inner join performance_schema.threads c ON b.id = c.PROCESSLIST_IDinner join performance_schema.events_statements_current d ON d.THREAD_ID = c.THREAD_ID;# 查问事务执行过的所有历史SQL记录SELECT ps.id 'PROCESS ID', ps.USER, ps.HOST, esh.EVENT_ID, trx.trx_started, esh.event_name 'EVENT NAME', esh.sql_text 'SQL', ps.time FROM PERFORMANCE_SCHEMA.events_statements_history esh JOIN PERFORMANCE_SCHEMA.threads th ON esh.thread_id = th.thread_id JOIN information_schema.PROCESSLIST ps ON ps.id = th.processlist_id LEFT JOIN information_schema.innodb_trx trx ON trx.trx_mysql_thread_id = ps.id WHERE trx.trx_id IS NOT NULL AND ps.USER != 'SYSTEM_USER' ORDER BY esh.EVENT_ID; # 简略查问事务锁 select * from sys.innodb_lock_waits # 查问事务锁详细信息 SELECT tmp.*, c.SQL_Text blocking_sql_text, p.HOST blocking_host FROM ( SELECT r.trx_state wating_trx_state, r.trx_id waiting_trx_id, r.trx_mysql_thread_Id waiting_thread, r.trx_query waiting_query, b.trx_state blocking_trx_state, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread, b.trx_query blocking_query FROM information_schema.innodb_lock_waits w INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id ) tmp, information_schema.PROCESSLIST p, PERFORMANCE_SCHEMA.events_statements_current c, PERFORMANCE_SCHEMA.threads t WHERE tmp.blocking_thread = p.id AND t.thread_id = c.THREAD_ID AND t.PROCESSLIST_ID = p.id 参考MySQL-长事务详解 ...

July 17, 2020 · 1 min · jiezi

事务与锁完整版

事务初学的时候,感觉事务的四大特性就那么回事,不就是一堆事要么完成,要么全部失败吗。还有经常说的脏读,幻读,不可重复读根本无法理解,就是那个存款取款的例子,我修改了数据,对方看到我修改的数据,这不很正常吗。现在看来,当时根本就不知道并发是什么鬼,更何谈并发事物了。 然后给你来一堆名词,共享锁,排它锁,悲观锁,乐观锁...... 想想就觉得那时候能记下来已经是奇迹了。 Spring 还给事务弄了一个传播机制的家伙,Spring 事务传播机制可以看这篇文章 。 本文应该来说是对初学者的福音,有一定经验的人看的话应该也会有收获。 事务的四大特性ACID这个是刚入门面试的时候必问一个面试题,刚入行的时候我是硬生生背下来的。 原子性(Atomicity) 一件事情的所有步骤要么全部成功,要么全部失败,不存在中间状态。一致性(Consistency) 事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。隔离性(Isolation) 两个事务之间是隔离程度,具体的隔离程度由隔离级别决定,隔离级别有 读未提交的 (read-uncommitted)读提交的 (read-committed)可重复读 (repeatable-read)串行 (serializable)持久性 (Durability) 一个事务提交后,数据库状态就永远的发生改变,不会因为数据库宕机而让提交不生效。一个事务和并发事务事务指的是从开始事务->执行操作->提交/回滚 整个过程,在程序中使用一个连接对应一个事务 -- sql 中的事务START TRANSACTION;select * from question;commit ;// 最原始的 jdbc 事务Connection connection = 获取数据库连接;try{ connection.setAutoCommit(false); // todo something connection.commit();}catch(Exception e){log(e); connection.rollback();}finally{ try{connection.close()}catch(Exception e){log(e);};}并发事务是指两个事务一同开始执行,如果两个事务操作的数据之间有交集,则很有可能产生冲突。这时怎么办呢,其实这也是 临界资源 的一种,在应用程序中,我们解决这类问题的关键是加锁,在数据库的实现也是一样,但在数据库中需要考虑更多。常见的需要考虑的问题有(下面说的我和人都是指一个会话) 对整张表数据加锁还是对当前操作的数据行加锁,这时有表锁和行锁,MyISAM 引擎只支持表锁,而 innodb 支持行锁和表锁如果数据量庞大,比如选到了百万数据,千万数据,不可能一次性全部加锁, 会很影响性能,innodb 是逐条加锁的数据库的操作其实有很大一部分是查询操作,如果锁住数据,任何人都不让进的话,性能也会很低下,所以会有读锁和写锁,也叫共享锁和排它锁根据检测冲突的时间不同,可以在一开始就把数据锁住,直到我使用完,还有就是在真正操作数据的时候才去锁住,就是悲观锁和乐观锁就算是让别人可以读数据,在两个事务也可能互相影响,比如脏读。事务的隔离级别及会带来的问题看过网上的大部分文章,基本都是一个表格来演示两个事务的并发,有的根本就是直接抄的,不知道那作者真的懂了没,其实我们是可以用客户端来模拟两个事务并发的情况的,打开两个 session ,让两个事务互相穿插。 下面的演示都是基于 mysql5.7 版本,查询事务隔离级别和修改隔离级别语句 -- 查看事务隔离级别select @@tx_isolation;-- 修改当前 session 事务隔离级别set session transaction isolation level read uncommitted;set session transaction isolation level read committed ;set session transaction isolation level repeatable read ;set session transaction isolation level serializable;-- 开启事务提交和回滚START TRANSACTION;select * from question;commit ;rollback;准备数据表,暂时先使用 InnoDB 引擎 ...

November 2, 2019 · 3 min · jiezi

一文带你理解脏读幻读不可重复读与mysql的锁事务隔离机制

首先说一下数据库事务的四大特性 1 ACID事务的四大特性是ACID(不是"酸"....) (1) A:原子性(Atomicity)原子性指的是事务要么完全执行,要么完全不执行. (2) C:一致性(Consistency)事务完成时,数据必须处于一致的状态.若事务执行途中出错,会回滚到之前的事务没有执行前的状态,这样数据就处于一致的状态.若事务出错后没有回滚,部分修改的内容写入到了数据库中,这时数据就是不一致的状态. (3) I:隔离性(Isolation)同时处理多个事务时,一个事务的执行不能被另一个事务所干扰,事务的内部操作与其他并发事务隔离. (4) D:持久性(Durability)事务提交后,对数据的修改是永久性的. 2 Mysql的锁Mysql的锁其实可以按很多种形式分类: 按加锁机制分,可分为乐观锁与悲观锁.按兼容性来分,可分为X锁与S锁.按锁粒度分,可分为表锁,行锁,页锁.按锁模式分,可分为记录锁,gap锁,next-key锁,意向锁,插入意向锁.这里主要讨论S锁,X锁,乐观锁与悲观锁. (1) S锁与X锁S锁与X锁是InnoDB引擎实现的两种标准行锁机制.查看默认引擎可使用 show variables like '%storage_engine%';作者的mysql版本为8.0.17,结果如下: 先建好测试库与测试表,很简单,表就两个字段. create database test;use test;create table a(id int primary key auto_increment,money int); Ⅰ.S锁S锁也叫共享锁,读锁,数据只能被读取不能被修改.玩一下,上锁! lock table a read;然后..... 只能读不能改,删,也不能增. Ⅱ.X锁X锁也叫排他锁,写锁,一个事务对表加锁后,其他事务就不能对其进行加锁与增删查改操作. 设置手动提交,开启事务,上X锁. set autocmmmit=0;start transaction;lock table a write; 在开启另一个事务,使用select语句. set autocommit=0;start transaction;select * from a; 这里是阻塞select操作,因为一直都没释放X锁. 同样也不能再加锁,也是阻塞中. 回到原来那个加锁的事务,嗯,什么事也没有,正常读写. 释放锁后: unlock table; 在另一个事务中可以看到中断时间. ...

October 17, 2019 · 1 min · jiezi

一文读懂Spring事务管理器

为什么需要事务管理器如果没有事务管理器的话,我们的程序可能是这样: Connection connection = acquireConnection();try{ int updated = connection.prepareStatement().executeUpdate(); connection.commit();}catch (Exception e){ rollback(connection);}finally { releaseConnection(connection);}也有可能是这样"优雅的事务": execute(new TxCallback() { @Override public Object doInTx(Connection var1) { //do something... return null; }});public void execute(TxCallback txCallback){ Connection connection = acquireConnection(); try{ txCallback.doInTx(connection); connection.commit(); }catch (Exception e){ rollback(connection); }finally { releaseConnection(connection); }}# lambda版execute(connection -> { //do something... return null;});但是以上两种方式,针对一些复杂的场景是很不方便的。在实际的业务场景中,往往有比较复杂的业务逻辑,代码冗长,逻辑关联复杂,如果一个大操作中有全是这种代码的话我想开发人员可能会疯把。更不用提定制化的隔离级别,以及嵌套/独立事务的处理了。 Spring 事务管理器简介Spring作为Java最强框架,事务管理也是其核心功能之一。Spring为事务管理提供了统一的抽象,有以下优点: 跨不同事务API(例如Java事务API(JTA),JDBC,Hibernate,Java持久性API(JPA)和Java数据对象(JDO))的一致编程模型。支持声明式事务管理(注解形式)与JTA之类的复杂事务API相比, 用于程序化事务管理的API更简单和Spring的Data层抽象集成方便(比如Spring - Hibernate/Jdbc/Mybatis/Jpa...)使用方式事务,自然是控制业务的,在一个业务流程内,往往希望保证原子性,要么全成功要么全失败。 所以事务一般是加载@Service层,一个Service内调用了多个操作数据库的操作(比如Dao),在Service结束后事务自动提交,如有异常抛出则事务回滚。 这也是Spring事务管理的基本使用原则。 下面贴出具体的使用代码: 注解在被Spring管理的类头上增加@Transactional注解,即可对该类下的所有方法开启事务管理。事务开启后,方法内的操作无需手动开启/提交/回滚事务,一切交给Spring管理即可。 @Service@Transactionalpublic class TxTestService{ @Autowired private OrderRepo orderRepo; public void submit(Order order){ orderRepo.save(order); }}也可以只在方法上配置,方法配置的优先级是大于类的 ...

October 17, 2019 · 2 min · jiezi

Spring事务传播属性有那么难吗看这一篇就够了

Spring事务传播属性有那么难吗?看这一篇就够了笔者文笔功力尚浅,如有不妥,请慷慨指出,必定感激不尽学习东西要知行合一,如果只是知道理论而没实践过,那么掌握的也不会特别扎实,估计过几天就会忘记,接下来我们一起实践来学习Spring事务的传播属性。 传播属性传播属性定义的是当一个事务方法碰到另一个事务方法时的处理行为,一共有七种行为,定义如下 传播性值描述PROPAGATION_REQUIRED0支持当前事务,如果没有就新建事务PROPAGATION_SUPPORTS1支持当前事务,如果没有就不以事务的方式运行PROPAGATION_MANDATORY2支持当前事务,如果当前没事务就抛异常PROPAGATION_REQUIRES_NEW3无论当前是否有事务,都会新起一个事务PROPAGATION_NOT_SUPPORTED4不支持事务,如果当前存在事务,就将此事务挂起不以事务方式运行PROPAGATION_NEVER5不支持事务,如果有事务就抛异常PROPAGATION_NESTED6如果当前存在事务,在当前事务中再新起一个事务其实只看概念的话已经很直截了当了说明了每个传播性的作用,此时我们再用具体的例子演示一下每个传播性属性下的行为。 此次演示我们使用的是H2数据库,这个数据库是作用在内存里面的,所以对于我们演示事务效果来说正好,无需我们在进行其他的配置了,我们新建一个表。将下面语句放在schema.sql文件里面即可,SpringBoot程序在启动的时候就会自动为我们在内存里面建立这样的一个表。 CREATE TABLE FOO (ID INT IDENTITY, BAR VARCHAR(64));演示之前我们会定义两个类FooService和BarService。我们使用BarService 里面的方法进行调用FooService 中的方法。 环境准备在进行事务演示之前,其实可以分为以下几种情况,根据排列组合,我们可以得出以下八种情况 调用者:有无事务调用者:是否有异常被调用者:有无事务(这个是通过传播属性进行控制的)所以并不在排列组合中被调用者:是否有异常调用者是否有事务调用者是否有异常被调用者是否有异常有有有有有无有无有有无无无有有无有无无无有无无无异常类其中的RollbackException是我们自己定义的一个异常类 @Servicepublic class BarServiceImpl implements BarService{ @Autowired private FooService fooService; // PROPAGATION_REQUIRED演示 无事务 @Override public void testRequiredNoTransactional() throws RollbackException { fooService.testRequiredTransactional(); }}调用者在BarService中定义两个方法,一个是带着事务的,一个是不带事务的 // 有事务@Override@Transactional(rollbackFor = Exception.class)public void hasTransactional() throws RollbackException {}// 无事务@Overridepublic void noTransactional() throws RollbackException { }接下来我们就根据俄上面定义的八种情况进行事务传播属性的学习。 PROPAGATION_REQUIRED在此传播属性下,被调用方是否新建事务取决去调用者是否带着事务。想要了解这个传播属性的特性,其实我们演示上面八种情况的两个例子就够了 调用者是否有事务调用者是否有异常被调用者是否有异常无无有有有无第一种情况我们在被调用者抛出异常的情况下,如果查询不到插入的数据,那么就说明被调用者在调用者没有事务的情况下自己新建了事务。第二种情况我们在调用者抛出异常的情况下,如果查询不到插入的数据,那么就说明被调用者在调用者有事务的情况下就加入当前事务了。我们先来看一下被调用者的类的方法例子。 @Servicepublic class FooServiceImpl implements FooService { @Autowired private JdbcTemplate jdbcTemplate; // REQUIRED传播属性-被调用者有异常抛出 @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) public void testRequiredHasException() throws RollbackException { jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ("+Global.REQUIRED_HAS_EXCEPTION+")"); throw new RollbackException(); } // REQUIRED传播属性-被调用者无异常抛出 @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) public void testRequiredNoException() throws RollbackException { jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ("+Global.REQUIRED_NO_EXCEPTION+")"); }}接下来我们看一下调用者方法的例子 ...

October 16, 2019 · 3 min · jiezi

阿里开源分布式事务组件-seata-demo-环境搭建以及运行流程简析

案例设计seata 官方给出了一系列 demo 样例,不过我在用的过程中发现总有这个那个的问题,所以自己维护了一份基于 dubbo 的 demo 在 github 上,适配的 seata 版本是 0.8.0。案例的设计直接参考官方 quick start给出的案例: 整个案例分为三个服务,分别是存储服务、订单服务和账户服务,这些服务通过 dubbo 进行发布和调用,内部调用逻辑如上面图所示。整个 demo 的工程样例如下所示: undo_log 表这个案例除了在数据库需要建立业务表以外,还要额外建立一张 undo_log 表,这个表的主要作用是记录事务的前置镜像和后置镜像。全局事务进行到提交阶段,则删除该表对应的记录,全局事务如果需要回滚,则会利用这个表里记录的镜像数据,恢复数据。undo_log 表里的数据实际上是“朝生夕死”的,数据不需要在表里存活太久。表结构如下所示: CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;服务逻辑每个服务都对应了一个 starter 类,这个类主要用来在 spring 环境下,将该服务启动,并通过 dubbo 发布出去,以账户服务为例: ...

October 9, 2019 · 4 min · jiezi

Redis的事务

一、是什么可以一次执行多个命令, 本质是一组命令的集合,一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其他命令插入,不许加塞 二、能干啥一个队列中,一次性,顺序性的执行一系列命令 三、示例演示正常执行 127.0.0.1:6379> MULTI // 开启事务OK127.0.0.1:6379> set k1 v1 //入队QUEUED127.0.0.1:6379> set k2 v2QUEUED127.0.0.1:6379> get k2QUEUED127.0.0.1:6379> set k3 v3QUEUED127.0.0.1:6379> EXEC //执行事务1) OK // 执行结果2) OK3) "v2"4) OK放弃事务 127.0.0.1:6379> MULTIOK127.0.0.1:6379> set k1 v1QUEUED127.0.0.1:6379> set k2 22QUEUED127.0.0.1:6379> set k3 33QUEUED127.0.0.1:6379> DISCARD //放弃事务OK127.0.0.1:6379> get k2 // 获取到的还是先前设置的v2"v2"一损俱损 127.0.0.1:6379> MULTIOK127.0.0.1:6379> set k4 v4QUEUED127.0.0.1:6379> setget k5 55 //命令错误,全都执行不成功(error) ERR unknown command 'setget'127.0.0.1:6379> set k6 v6QUEUED127.0.0.1:6379> EXEC(error) EXECABORT Transaction discarded because of previous errors.127.0.0.1:6379> get k4 // 获取的为nil(nil)冤头债主 ...

October 7, 2019 · 1 min · jiezi

mysql的四种事务隔离级别

什么是事务事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。事务的结束有两种,当事务中的所以步骤全部成功执行时,事务提交。如果其中一个步骤失败,将发生回滚操作,撤消撤消之前到事务开始时的所以操作。 事务的ACID1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。 2、一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。 3、隔离性(Isolation):同一时间,只允许一个事务请求同一数据,并发执行不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。 4、持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,其它操作或故障不应该对其执行结果有任何影响,不能回滚。 事务的并发问题1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。 小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表 事务隔离级别1、Read Uncommitted(读取未提交内容)在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。2、Read Committed(读取提交内容)这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。3、Repeatable Read(可重读)这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。4、Serializable(可串行化)这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。mysql默认的事务隔离级别为repeatable-read 各个隔离级别的情况CREATE TABLE `account` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(11) DEFAULT '', `balance` decimal(10,2) DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; 1、读未提交(1)打开一个客户端A,并设置当前事务模式为read uncommitted(未提交读),查询表account的初始值:(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:(3)这时,虽然客户端B的事务还没提交,但是客户端A就可以查询到B已经更新的数据:(4)一旦客户端B的事务因为某种原因回滚,所有的操作都将会被撤销,那客户端A查询到的数据其实就是脏数据:(5)在客户端A执行更新语句update account set balance = balance - 50 where id =4,Lucy的balance没有变成200,居然是250,是不是很奇怪,数据不一致啊,如果你这么想就太天真了,在应用程序中,我们会用250-50=200,并不知道其他会话回滚了,要想解决这个问题可以采用读已提交的隔离级别 2、读已提交(1)打开一个客户端A,并设置当前事务模式为read committed(已提交读),查询表account的所有记录:(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:(3)这时,客户端B的事务还没提交,客户端A不能查询到B已经更新的数据,解决了脏读问题:(4)客户端B的事务提交(5)客户端A执行与上一步相同的查询,结果 与上一步不一致,即产生了不可重复读的问题 3、可重复读(1)打开一个客户端A,并设置当前事务模式为repeatable read,查询表account的所有记录(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account并提交(3)在客户端A查询表account的所有记录,与步骤(1)查询结果一致,没有出现不可重复读的问题(4)在客户端A,接着执行update balance = balance - 50 where id = 1,balance没有变成350-50=300,lucy的balance值用的是步骤(2)中的300来算的,所以是250,数据的一致性倒是没有被破坏。可重复读的隔离级别下使用了MVCC机制,select操作不会更新版本号,是快照读(历史版本);insert、update和delete会更新版本号,是当前读(当前版本)。(5)重新打开客户端B,插入一条新数据后提交(6)在客户端A查询表account的所有记录,没有 查出 新增数据,所以没有出现幻读 ...

September 10, 2019 · 1 min · jiezi

ShardingSphere-x-Seata一致性更强的分布式数据库中间件

日前,分布式数据库中间件 ShardingSphere 将 Seata 分布式事务能力进行整合,旨在打造一致性更强的分布式数据库中间件。 背景数据库领域,分布式事务的实现主要包含:两阶段的 XA 和 BASE 柔性事务。XA 事务底层,依赖于具体的数据库厂商对 XA 两阶段提交协议的支持。通常,XA 协议通过在 Prepare 和 Commit 阶段进行 2PL(2 阶段锁),保证了分布式事务的 ACID,适用于短事务及非云化环境(云化环境下一次 IO 操作大概需要 20ms,两阶段锁会锁住资源长达 40ms,因此热点行上的事务的 TPS 会降到 25/s 左右,非云化环境通常一次 IO 只需几毫秒,因此锁热点数据的时间相对较低)。 但在 BASE 柔性事务方面,ShardingSphere 提供的接入分布式事务的 SPI,只适用于对性能要求较高,对一致性要求比较低的业务。 Seata 核心的 AT 模式适用于构建于支持本地 ACID 事务的关系型数据库。通过整合 Seata,其 AT 模式在一阶段提交+补偿的基础上,通过 TC 的全局锁实现了 RC 隔离级别的支持,可提高 ShardingSphere 的分布式事务的一致性。 整合方案整合 Seata AT 事务时,需要把 TM,RM,TC 的模型融入到 ShardingSphere 分布式事务的 SPI 的生态中。在数据库资源上,Seata 通过对接 DataSource 接口,让 JDBC 操作可以同 TC 进行 RPC 通信。同样,ShardingSphere 也是面向 DataSource 接口对用户配置的物理 DataSource 进行了聚合,因此把物理  DataSource 二次包装为 Seata 的 DataSource 后,就可以把 Seata AT 事务融入到 ShardingSphere 的分片中。 ...

July 4, 2019 · 1 min · jiezi

『浅入深出』MySQL-中事务的实现

在关系型数据库中,事务的重要性不言而喻,只要对数据库稍有了解的人都知道事务具有 ACID 四个基本属性,而我们不知道的可能就是数据库是如何实现这四个属性的;在这篇文章中,我们将对事务的实现进行分析,尝试理解数据库是如何实现事务的,当然我们也会在文章中简单对 MySQL 中对 ACID 的实现进行简单的介绍。 事务其实就是并发控制的基本单位;相信我们都知道,事务是一个序列操作,其中的操作要么都执行,要么都不执行,它是一个不可分割的工作单位;数据库事务的 ACID 四大特性是事务的基础,了解了 ACID 是如何实现的,我们也就清楚了事务的实现,接下来我们将依次介绍数据库是如何实现这四个特性的。 原子性 在学习事务时,经常有人会告诉你,事务就是一系列的操作,要么全部都执行,要都不执行,这其实就是对事务原子性的刻画;虽然事务具有原子性,但是原子性并不是只与事务有关系,它的身影在很多地方都会出现。 由于操作并不具有原子性,并且可以再分为多个操作,当这些操作出现错误或抛出异常时,整个操作就可能不会继续执行下去,而已经进行的操作造成的副作用就可能造成数据更新的丢失或者错误。 事务其实和一个操作没有什么太大的区别,它是一系列的数据库操作(可以理解为 SQL)的集合,如果事务不具备原子性,那么就没办法保证同一个事务中的所有操作都被执行或者未被执行了,整个数据库系统就既不可用也不可信。 回滚日志 想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,而在 MySQL 中,恢复机制是通过回滚日志(undo log)实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后在对数据库中的对应行进行写入。 这个过程其实非常好理解,为了能够在发生错误时撤销之前的全部操作,肯定是需要将之前的操作都记录下来的,这样在发生错误时才可以回滚。 回滚日志除了能够在发生错误或者用户执行 ROLLBACK 时提供回滚相关的信息,它还能够在整个系统发生崩溃、数据库进程直接被杀死后,当用户再次启动数据库进程时,还能够立刻通过查询回滚日志将之前未完成的事务进行回滚,这也就需要回滚日志必须先于数据持久化到磁盘上,是我们需要先写日志后写数据库的主要原因。 回滚日志并不能将数据库物理地恢复到执行语句或者事务之前的样子;它是逻辑日志,当回滚日志被使用时,它只会按照日志逻辑地将数据库中的修改撤销掉看,可以理解为,我们在事务中使用的每一条 INSERT 都对应了一条 DELETE,每一条 UPDATE 也都对应一条相反的 UPDATE 语句。 在这里,我们并不会介绍回滚日志的格式以及它是如何被管理的,本文重点关注在它到底是一个什么样的东西,究竟解决了、如何解决了什么样的问题,如果想要了解具体实现细节的读者,相信网络上关于回滚日志的文章一定不少。 事务的状态 因为事务具有原子性,所以从远处看的话,事务就是密不可分的一个整体,事务的状态也只有三种:Active、Commited 和 Failed,事务要不就在执行中,要不然就是成功或者失败的状态: 但是如果放大来看,我们会发现事务不再是原子的,其中包括了很多中间状态,比如部分提交,事务的状态图也变得越来越复杂。 Active:事务的初始状态,表示事务正在执行;Partially Commited:在最后一条语句执行之后;Failed:发现事务无法正常执行之后;Aborted:事务被回滚并且数据库恢复到了事务进行之前的状态之后;Commited:成功执行整个事务;虽然在发生错误时,整个数据库的状态可以恢复,但是如果我们在事务中执行了诸如:向标准输出打印日志、向外界发出邮件、没有通过数据库修改了磁盘上的内容甚至在事务执行期间发生了转账汇款,那么这些操作作为可见的外部输出都是没有办法回滚的;这些问题都是由应用开发者解决和负责的,在绝大多数情况下,我们都需要在整个事务提交后,再触发类似的无法回滚的操作。 以订票为例,哪怕我们在整个事务结束之后,才向第三方发起请求,由于向第三方请求并获取结果是一个需要较长时间的操作,如果在事务刚刚提交时,数据库或者服务器发生了崩溃,那么我们就非常有可能丢失发起请求这一过程,这就造成了非常严重的问题;而这一点就不是数据库所能保证的,开发者需要在适当的时候查看请求是否被发起、结果是成功还是失败。 并行事务的原子性 到目前为止,所有的事务都只是串行执行的,一直都没有考虑过并行执行的问题;然而在实际工作中,并行执行的事务才是常态,然而并行任务下,却可能出现非常复杂的问题: 当 Transaction1 在执行的过程中对 id = 1 的用户进行了读写,但是没有将修改的内容进行提交或者回滚,在这时 Transaction2 对同样的数据进行了读操作并提交了事务;也就是说 Transaction2 是依赖于 Transaction1 的,当 Transaction1 由于一些错误需要回滚时,因为要保证事务的原子性,需要对 Transaction2 进行回滚,但是由于我们已经提交了 Transaction2,所以我们已经没有办法进行回滚操作,在这种问题下我们就发生了问题,Database System Concepts 一书中将这种现象称为不可恢复安排(Nonrecoverable Schedule),那什么情况下是可以恢复的呢? ...

June 13, 2019 · 2 min · jiezi

3分钟干货之数据库事务特性

数据库的特性是面试时考察频率非常高的题目,共4个特性:△原子性:是指事务由原子的操作序列组成,所有操作要么全部成功,要么全部失败回滚。 △一致性:是指事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处以一致性状态。比如在做多表操作时,多个表要么都是事务后新的值,要么都是事务前的旧值。 △隔离性:是指多个用户并发访问数据库时,数据库为每个用户执行的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。事务的隔离级别我们稍后介绍。 △持久性:是指一个事务一旦提交并执行成功,那么对数据库中数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

May 23, 2019 · 1 min · jiezi

03MySQL数据库事务的隔离级别

事务 就是要保证一组数据库操作,要么全部成功,要么全部失败。 在MySQL中,事务支持是在引擎层实现的 MySQL是一个支持多引擎的系统,但并不是所有的引擎都支持事务。比如MySQL原生的MyISAM引擎就不支持事务,这也是MyISAM被InnoDB取代的重要原因之一。事务的四大特性:ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性),今天我们就来说说其中I,也就是“隔离性”。 当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。 在谈隔离级别之前,你首先要知道,你隔离得越严实,效率就会越低。因此很多时候,我们都要在二者之间寻找一个平衡点。 SQL标准的事务隔离级别包括:(从上到下越来越严实) 读未提交(read uncommitted)读提交(read committed)可重复读(repeatable read)串行化(serializable )逐一解释: 读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。这会带来脏读、幻读、不可重复读问题。(基本没用)读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。避免了脏读,但仍然存在不可重复读和幻读问题。可重复读是指,一个事务执行过程中看到的数据,这个事务自己看到的数据始终不变(当然他可能已经被其他事务改变了)在可重复读隔离级别下,未提交变更对其他事务也是不可见的。 避免了脏读和不可重复读问题,但幻读依然存在。串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行,避免了以上所有问题。直接看文字描述可能不太好理解,那我们来看图吧 我们来看看在不同的隔离级别下,事务A会有哪些不同的返回结果,也就是图里面V1、V2、V3的返回值分别是什么。 若隔离级别是“读未提交”, 则V1的值就是2。这时候事务B虽然还没有提交,但是结果已经被A看到了。因此,V2、V3也都是2。若隔离级别是“读提交”,则V1是1,V2的值是2。事务B的更新在提交后才能被A看到。所以, V3的值也是2。若隔离级别是“可重复读”,则V1、V2是1,V3是2。之所以V2还是1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。若隔离级别是“串行化”,则在事务B执行“将1改成2”的时候,会被锁住。直到事务A提交后,事务B才可以继续执行。所以从A的角度看, V1、V2值是1,V3的值是2。在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在“读提交”隔离级别下,这个视图是在每个SQL语句开始执行的时候创建的。这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。 我们可以看到在不同的隔离级别下,数据库行为是有所不同的。Oracle数据库的默认隔离级别其实就是“读提交”,因此对于一些从Oracle迁移到MySQL的应用,为保证数据库隔离级别的一致,你一定要记得将MySQL的隔离级别设置为“读提交”。 总结来说,存在即合理,哪个隔离级别都有它自己的使用场景,你要根据自己的业务情况来定。我想你可能会问那什么时候需要“可重复读”的场景呢?我们来看一个数据校对逻辑的案例。 假设你在管理一个个人银行账户表。一个表存了每个月月底的余额,一个表存了账单明细。这时候你要做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。你一定希望在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。 这时候使用“可重复读”隔离级别就很方便。事务启动时的视图可以认为是静态的,不受其他事务更新的影响。 悲观锁与乐观锁 悲观锁,正如它的名字那样,数据库总是认为别人会去修改它所要操作的数据,因此在数据库处理过程中将数据加锁。其实现依靠数据库底层。 乐观锁,如它的名字那样,总是认为别人不会去修改,只有在提交更新的时候去检查数据的状态。通常是给数据增加一个字段来标识数据的版本。 MySQL的MVCC(多版本并发控制) 我们知道,MySQL的innodb采用的是行锁,而且采用了多版本并发控制来提高读操作的性能。 什么是多版本并发控制呢 ?其实就是在每一行记录的后面增加两个隐藏列,记录创建版本号和删除版本号, 而每一个事务在启动的时候,都有一个唯一的递增的版本号。 1、在插入操作时 : 记录的创建版本号就是事务版本号。 比如我插入一条记录, 事务id 假设是1 ,那么记录如下:也就是说,创建版本号就是事务版本号。 2、在更新操作的时候,采用的是先标记旧的那行记录为已删除,并且删除版本号是事务版本号,然后插入一行新的记录的方式。 比如,针对上面那行记录,事务Id为2 要把name字段更新 update table set name= 'new_value' where id=1; 3、删除操作的时候,就把事务版本号作为删除版本号。比如 delete from table where id=1; 4、查询操作: 从上面的描述可以看到,在查询时要符合以下两个条件的记录才能被事务查询出来: 1) 删除版本号 大于 当前事务版本号,就是说删除操作是在当前事务启动之后做的。 2) 创建版本号 小于或者等于 当前事务版本号 ,就是说记录创建是在事务中(等于的情况)或者事务启动之前。 ...

May 5, 2019 · 1 min · jiezi

事务4种隔离级别

哪四种?数据库事务的隔离级别有4种,由低到高分别为: Read uncommitted 读未提交Read committed 读提交Repeatable read 重复读(从事务操作的角度阐述表示这个事务需要多次读取)Serializable 序列化,串行化顺序执行,相当于锁表

April 24, 2019 · 1 min · jiezi

一次事务相关的踩坑之旅@Modifying(clearAutomatically = true)

在处理多线程save po引起的数据覆盖时,将save改为了update,如下:遇到第一个诡异问题,修改完这个,我之前的两张表InputPO和ProgramPO的关联出现了问题(一对多),数据库中显示,ProgramPO中的外键inputId为null了,百撕不得骑姐啊,一个操作影响到了完全不相关的两张表。于是开启了hibernate的语句执行日志,对比了一下修改前后的sql执行,发现了问题。之前外键正常时,日志能看到这句:Hibernate: /* create one-to-many row com.suma.xianrd.sirius.pojo.task.InputPO.programPOs */ update program set inputId=? where id=?明显是建外键的,而用了update方法后,这句执行就没了。 没道理啊,表之间又没关系。忽然想到了一问题,这些执行的最外层,我加了一个事务,会不会有关系呢?做了如下实验操作一:一个外层带事务的方法,先分别存储了一对关联的input和programpo,然后等10s,方法结束。现象一:日志看到顺序如下,插入输入1->插入节目1->插入输入2->插入节目2,10s后,依次看到两条update外键的日志执行。操作二:将上述方法事务取消现象二:日志顺序如下,插入输入1->插入节目1->update外键1->插入输入2->插入节目2->update外键2。操作三:还是上述方法,方法带上事务,等待10s后抛异常。现象三:只能看到插入的日志,看不到update外键的日志。没有任何输入存入数据库。结论一:事务提交时,才会执行缓存的关联外键操作。操作四:引入我们的主角,update修改DeviceAuthPO的方法,在上述方法插入数据后调用。现象四:在没有异常的情况下,update外键的日志凭空不见了。操作到这里,进行相关搜索后,把注意力放在了@Modifying(clearAutomatically = true)这个注释上,有注释:会清理缓存,导致同事务内的其它表外键操作丢失; 无注释:前面的数据,后面仍然查不到。。源码中这个注释是这样描述的:Defines whether we should clear the underlying persistence context after executing the modifying query.我们在update的方法中都默认将这个配置为true了,为的是能让update及时生效到数据库。但看这里的描述,这样配置会清掉持久层的上下文,我们看到的现象就是外键操作丢失。总结下这次跟jpa以及事务相关的收获1、@Modifying(clearAutomatically = true)注释的update方法,会导致本事务内的生成关联表外键的操作丢失2、同一个事务内多次查询同一条数据,实际语句只会执行一次(事务内出现了带@Modifying(clearAutomatically = true)的语句,下一次会重新查)所以,尽量避免一个事务所包含的动作过多,不利于事务控制,如果此时再有多线程进行处理,出问题概率很高。后续更新:在stackoverflow上看到一篇帖子,描述一样的问题(clearAutomatically = true 导致部分数据库动作丢失),原文这样描述的:This approach clears the persistence context not to have outdated values, but it drops all non-flushed changes still pending in the EntityManager. 清缓存的同时会把未提交的修改给扔掉? 和自己测试的结果是一致的。这个动作类似于,在没有提交的情况下,执行了一次entityManager.clear()。新版版spring data jpa引入了另外一个flushAutomatically = true,可确保动作提交,暂未实际验证,后续补充。望交流指正。

April 20, 2019 · 1 min · jiezi

springboot 集成 shiro 导致事务无效

问题描述前两天测试一个写事务,发现这个事务出现异常不会回滚了,一直在事务上找问题,一直没有找到,结果发现是shiro的bean先于Spring事务将userService实例化了,结果导致spring事务初始化时好无法扫描到该bean,导致这个bean上没有绑定事务,导致事务无效寻找问题在哪一、在事务本身找问题通过百度发现,大家都有以下几个原因导致事务失效数据库的引擎是否是innoDB 启动类上是否加入@EnableTransactionManagement注解 方法是否为public是否是因为抛出了Exception等checked异常经过排查,发现以上原因都通过了,那么应该不是写的问题。二、在运行中找问题在上面4个原因检查时,发现将已有的service 类 copy下现在有两个除了名字其他都一模一样的类,这时运行下发现,在原来的类中@Transational失效,在新copy中的类中@Transational就起效了,这个问题好莫名奇妙,什么都没改就一个有效一个无效,现在的思路就是比较下这两个类在运行时有什么不同通过log发现打出了一下信息,说是jdbc的connection 不是Spring管的而正常回归的service类则是,调用了 JtaTransactionManager 类,而且 spring是管理jdbc的connection的通过这个分析,可以知道这spring对于这两个类的处理是不一样的,应该是spring代理或者初始化的问题,翻了下log 发现service 在ProxyTransactionManagementConfiguration 配置之前就被创建了,那应该是这里的问题了,这里就要分析下service为啥提前被创建了,发现在开始启动的是shiro ,而shiro中有个realm中引用了这些服务,所以这些服务在Transaction创建扫描之前创建了引发问题原因总结导致问题的真正原因是bean创建顺序问题,解决问题方法就是,在Transaction之后创建service。ps:呵呵,但是我还是不知道咋样才能解决创建顺序问题,继续百度之,关键词shiro 导致 事务不生效果然有解决方案解决方案经过百度找到了以下的解决方法,和以下解释shiro导致springboot事务不起效解决办法BeanPostProcessor加载次序及其对Bean造成的影响分析spring boot shiro 事务无效Shrio 多realms集成:No realms have been configured! One or more realms must be presentspring + shiro 配置中部分事务失效分析及解决方案(这个方案不管用)解决方法一:在realm引用的service服务上加@lazy注解,但是这个方法我测试了下并没有起效!!!解决方法二:把在 ShiroConfig里面初始化的Realm的bean和securityManager的bean方法移动到一个新建的ShiroComponent中,利用监听器中去初始化,主要配置如下,其中ShiroComponent中UserNamePassWordRealm和WeiXinRealm是我自定义的两个Realm,换成自己的就好,ShiroConfig.javaimport java.util.LinkedHashMap;import java.util.Map;import javax.servlet.DispatcherType;import javax.servlet.Filter;import org.apache.shiro.cache.ehcache.EhCacheManager;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.spring.LifecycleBeanPostProcessor;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.filter.DelegatingFilterProxy;/** * 自定义继承shiro 没有使用shiro-spring-boot-web-starter 的shiro 套件 * * @author gaoxiuya * /@Configurationpublic class ShiroConfig { /* * FilterRegistrationBean * * @return / @Bean public FilterRegistrationBean filterRegistrationBean() { FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); filterRegistration.setFilter(new DelegatingFilterProxy(“shiroFilter”)); filterRegistration.setEnabled(true); filterRegistration.addUrlPatterns("/"); filterRegistration.setDispatcherTypes(DispatcherType.REQUEST); return filterRegistration; } /** * @param securityManager * @see org.apache.shiro.spring.web.ShiroFilterFactorupload.visit.pathyBean * @return / @Bean(name = “shiroFilter”) public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(securityManager); bean.setLoginUrl("/"); bean.setSuccessUrl("/index"); bean.setUnauthorizedUrl("/403"); Map<String, Filter> filters = new LinkedHashMap<>(); filters.put(“permsc”, new CustomPermissionsAuthorizationFilter()); bean.setFilters(filters); Map<String, String> chains = new LinkedHashMap<>(); chains.put("/favicon.ico", “anon”); bean.setFilterChainDefinitionMap(chains); return bean; } @Bean public EhCacheManager cacheManager() { EhCacheManager cacheManager = new EhCacheManager(); cacheManager.setCacheManagerConfigFile(“classpath:ehcache.xml”); return cacheManager; } /* * @see DefaultWebSessionManager * @return */ @Bean(name = “sessionManager”) public DefaultWebSessionManager defaultWebSessionManager() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setCacheManager(cacheManager()); sessionManager.setGlobalSessionTimeout(1800000); sessionManager.setDeleteInvalidSessions(true); sessionManager.setSessionValidationSchedulerEnabled(true); sessionManager.setDeleteInvalidSessions(true); return sessionManager; } @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); }}ShiroComponent.javaimport java.util.ArrayList;import java.util.List;import org.apache.shiro.authc.Authenticator;import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;import org.apache.shiro.authc.pam.ModularRealmAuthenticator;import org.apache.shiro.cache.CacheManager;import org.apache.shiro.realm.Realm;import org.apache.shiro.session.mgt.SessionManager;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.Bean;import org.springframework.context.event.ContextRefreshedEvent;import org.springframework.context.event.EventListener;import org.springframework.stereotype.Component;@Componentpublic class ShiroComponent { @Bean public Realm userNamePassWordRealm(CacheManager cacheManager) { UserNamePassWordRealm userNamePassWordRealm = new UserNamePassWordRealm(); userNamePassWordRealm.setCacheManager(cacheManager); return userNamePassWordRealm; } @Bean public Realm myWeiXinRealm(CacheManager cacheManager) { WeiXinRealm weiXinRealm = new WeiXinRealm(); weiXinRealm.setCacheManager(cacheManager); return weiXinRealm; } @Bean(name = “securityManager”) public DefaultWebSecurityManager securityManager(Authenticator modularRealmAuthenticator, CacheManager cacheManager, SessionManager defaultWebSessionManager) { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setAuthenticator(modularRealmAuthenticator); manager.setCacheManager(cacheManager); manager.setSessionManager(defaultWebSessionManager); return manager; } @Bean public Authenticator modularRealmAuthenticator() { ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator(); modularRealmAuthenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy()); return modularRealmAuthenticator; } @EventListener public void handleContextRefresh(ContextRefreshedEvent event) { ApplicationContext context = event.getApplicationContext(); DefaultWebSecurityManager manager = (DefaultWebSecurityManager) context.getBean(“securityManager”); Realm userNamePassWordRealm = (Realm) context.getBean(“userNamePassWordRealm”); Realm myWeiXinRealm = (Realm) context.getBean(“myWeiXinRealm”); ModularRealmAuthenticator modularRealmAuthenticator = (ModularRealmAuthenticator) context .getBean(“modularRealmAuthenticator”); List<Realm> realms = new ArrayList<>(); realms.add(userNamePassWordRealm); realms.add(myWeiXinRealm); modularRealmAuthenticator.setRealms(realms); manager.setAuthenticator(modularRealmAuthenticator); manager.setRealms(realms); }}总结以后需要补课的地方spring bean 初始化顺序spring 事务原理spring bean 预加载 BeanPostProces 原理@Lazy 原理和为啥不起效 ...

April 7, 2019 · 2 min · jiezi

@Transactional事务生效问题

平时我们使用spring框架,不论是springmvcv还是springboot,springCloud,绝大多数情况我们都是在方法,或者直接在类上面加一个@Transactional,将事务交给spring替我们去管理,然后并没有具体分析一些情况,今天结合几个例子,结合源代码,使用伪代码解释一波。 1.情况一 service(){ //方法A methodA(){ insertA(); } //方法B @Transactional methodB(){ insertB(); throw new RunTimeException(“强制抛一个异常”); } public void static main(String[] args){ methodA(); methodB(); } }情况一就是这样,main方法里面顺序调用AB两个方法,A方法不加事务注解,B方法加了事务注解。如果不了解@Transactional 事务的传播性,可能会回答:A成功插入,B插入失败,但是实际情况却是A,B均插入成功了。到底是什么原因呢?这里先简单介绍一下事务的6个传播属性:PROPAGATION_REQUIRED : 支持当前事务,如果当前没有事务,就新建一个事务,这也是最常见的PROPAGATION_SUPPORTS : 支持当前事务,如果当前没有事务,就以非事务的方式执行PROPAGATION_MANDATORY: 支持当前事务,如果当前没有事务,就抛异常PROPAGATION_REQUIRES_NEW:新建事务,如果当前事务存在,就把当前事务挂起PROPAGATION_NOT_SUPPORTED:以非事务的方式执行,如果存在当前事务,就把当前事务挂起PROPAGATION_NEVER: 以非事务的方式执行,如果当前存在事务,就抛异常PROPAGATION_NESTED:如果存在当前事务,则在嵌套事务内执行,如果当前没有事务,则新建一个事务前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。研究源码,调试程序可以看到:A没有事务管理,则线程内的connection 有个autoCommit = trueB得到事务的时候,由于事务的传播性依然生效, 得到的还是A方法的commit,其autoCommit = true,故而逐条sql进行提交,即A,B都会插入下面我们来分析情况二:serviceA(){ methodA(){ insertA(); }} serviceB(){ @Transactional methodB(){ insertB(); throw new RuntimeExcption(“强制抛出的异常”); }}serviceC(){ @Autowired private ServiceA serviceA; @Autowired private ServiceB serviceB; public void staic main(String[] args){ serviceA.methodA(); serviceB.methodB(); }}情况二的主要代码和情况一一样,都是要调用methodA和methodB,但是结果却不同,情况二的正确结果是指挥插入A,而B会回滚,这是为什么呢?同样是在B方法上面加了事务注解….其实大家都知道,spring的事务是交由cglib动态代理的,而动态代理对象产生的时机就非常重要了。再回到本例子:A:在同一个service内部,事务之间嵌套调用,普通方法和事务方法之间的嵌套调用,都不会开启新的事务(因为shpring使用的是动态代理的方式来控制的事务,而动态代理最终都是要调用原始对象的,而原始对象在调用方法时,已存在代理对象,是不会再触发代理了!)B:两个方法在不同的service里(即不同的对象,即代理对象也不是同一个),在ServiceC中,使用注入的方式将serviceA和serviceB注入,这样即使A没有使用事务,B也有自己的代理,会根据PROPAGATION_REQUIRED 而生成新的事务.

March 8, 2019 · 1 min · jiezi

mysql-事务管理(进阶)--待续

请口述以下问题:什么是事务到特性,具体说说你到理解请举一个案例描述为什么要用到事务(转账)事务的隔离级别待续。。。1、事务到特性(ACID)Atomicty 原子性事务不可再分。现实中的一个需求,由多条SQL实现时,现实中的一件事只有两种结果,要么成功要么失败。事务机制就实现了将多条sql“当成”一条sql来执行,通过这种机制加上人为的判断,实现多条sql要么都成功,要第都失败。Consistence 一致性事务的执行过程中,对数据表的影响是没有的。Isolation 隔离性当一个事件的执行,不会影响其他客户端的数据表中查询到的结果。Duration 永久性当一个事务执行交,其影响就是永久的。A->B(A给B转账100) 正常mysql流程:A账户B账户A-100B+100可能情况:情况A账户B账户第一种A-100B+100第二种A-100B第三种AB+100第四种AB2、隔离性与隔离级别当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。SQL标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。事务没提交,做等变更别等事务能看到读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。事务提交了,它做的变更别的事务才能看到可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。读也加锁啊写也加锁,读写锁冲突,后面的事务等前面事务执行完成再执行,一串隔离得越严实,效率就会越低。需要找一个平衡点。

February 27, 2019 · 1 min · jiezi

MySQL-事务管理(基础)

事务处理用来维护数据库等完整性,保证mysql操作要么成功,要么失败(myisam不支持事务)1、关键词事务(transaction)指一组SQL语句;回退(rollback)指撤销指定SQL语句的过程;提交(commit)指将未存储的SQL语句结果写入数据库表;保留点(savepoint)指事务处理中设置的临时占位符(place-holder),你可以对它发布回退(与回退整个事务处理不同)。2、使用rollbackselect * from orderitems;START TRANSACTION;DELETE FROM orderitems;select * from orderitems;ROLLBACK;select * from orderitems;3、使用commitSTART TRANSACTION;DELETE FROM orderitems where order_num = 20010;DELETE FROM orders WHERE order_num = 20010;COMMIT假设第二条删除失败,回滚,撤销事务处理块内的语句4、使用保留点复杂的事务处理可能需要部分提交或回退。 为了支持回退部分事务处理,必须能在事务处理块中合适的位置放 置占位符。这样,如果需要回退,可以回退到某个占位符。这些占位符称为保留点。为了创建占位符,可如下使用SAVEPOINT创建保留点SAVEPOINT delete1回退到保留点ROLLBACK TO delete1tips保留点越多越好,方便灵活使用,but没必要到就算来哈!凡事适可而止 释放保留点保留点在事务处理完成(执行一条ROLLBACK或 COMMIT)后自动释放release savepoint delete1明确释放保留点5、更改默认到提交行为mysql是自动提交所有更改。不自动提交更改set autocommit = 0;

February 25, 2019 · 1 min · jiezi

Mysql事务隔离

数据库事务隔离事务的介绍事务就是一组原子性的sql查询,或者说是一个独立的工作单元。简而言之,事务内的语句要么全部执行成功,要么全部执行失败。在Mysql中,事务支持是在引擎层实现的,但并不是所有的Mysql引擎都支持事务,比如MyISAM引擎就不支持事务,这也是MyISAM被InnoDB取代的重要原因之一。提到事务,我们肯定会想到ACID:原子性(Atomicity)一致性(Consistency)隔离性(Isolation)持久性(Durability)隔离级别当数据库中有多个事务同时执行时,就可能会出现脏读、不可重复读、幻读等问题,因为就有了事务隔离级别的概念。SQL标准正定义了四种隔离级别:READ UNCOMMITTED (未提交读)事务中的修改,即使还没有提交,对其他事务都是可见的。事务可以读取未提交的数据,也被称为脏读(Dirty Read)。READ COMMITTED(提交读)一个事务提交后,所做的变更才能被其他事务看到。这个级别也叫不可重复读,因为事务中执行2次相同的查询,可能得到的结果是不一样的。REPEATABLE READ(可重复读)一个事务执行的过程中,总是和这个事务在启动时看到的数据是一致的。当然在这个级别下,未提交的数据变更对其他事务也是不可见的。SERIALIZABLE(可串行化)对同一行记录,写和读都会加锁,当出现读写锁冲突时,后访问的事务必须等前一个事务执行完成才能继续执行,就会导致大量的超时和锁争用的问题。在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑为准。在可重复读这个隔离级别下,这个视图是事务开启的时候创建的,整个事务期间都用这个视图。在读提交的隔离级别下,这个视图是在sql语句开始执行的时候创建的。在读未提交的隔离级别下,直接返回记录上的最新值,没有视图概念。在串行化的隔离级别下,直接用加锁的方式避免并行访问。配置的方式是将启动参数transaction-isolation设置成想要的隔离级别。查看当前设置:mysql> show variables like ’transaction_isolation’;+———————–+—————–+| Variable_name | Value |+———————–+—————–+| transaction_isolation | REPEATABLE-READ |+———————–+—————–+1 row in set (0.00 sec)总之,存在即合理,不同的隔离级别适用于不同的场景,具体我们应该根据业务场景来决定。事务隔离的实现在Mysql中,实际上每条记录的更新同时也会记录一条回滚操作,记录上的最新值通过回滚操作,都可以得到前一个状态的值。系统会自动判断,当没有事务再需要回滚日志时,会删除回滚日志。为什么不建议使用长事务:长事务意味着系统里面会存在很老的事务视图,由于这些事务随时可以访问数据库里面的任何数据,所以这个事务提交之前,数据库里可能用到的回滚记录必须保留着,这就会占用大量的存储空间。同时长事务还占用锁资源,也可能拖垮整个库。事务启动的方式显式启动事务语句,begin或者start transaction,提交就是commit,回滚用rollback。set autocommit = 0,这个命令会将线程的自动提交关掉,意味着如果执行一个select 语句,这个事务就启动了,并且不会自动提交,直到你主动执行commit或者rollback,或者断开连接。个人建议还是通过第一种方式显式启动事务,避免长事务的发生。在 set autocommit = 1 的情况下,用 begin 显式启动的事务,如果执行 commit 则提交事务。如果执行 commit work and chain,则是提交事务并自动启动下一个事务,这样也省去了再次执行 begin 语句的开销。查询长事务:下面语句是查询持续时间超过60s的事务mysql> select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60;Empty set (0.00 sec)总结下来,我们在开发过程中,尽量少用长事务,如果无法避免,保证逻辑日志空间足够大,并且支持动态日志空间增长。监控Innodb_trx表,发现长事务报警。欢迎交流。参考资料《高性能Mysql》极客时间-Mysql实战45讲

January 26, 2019 · 1 min · jiezi

Mysql事务隔离级别之读提交

Mysql事务隔离级别之读提交查看mysql 事务隔离级别mysql> show variables like ‘%isolation%’;+—————+—————-+| Variable_name | Value |+—————+—————-+| tx_isolation | READ-COMMITTED |+—————+—————-+1 row in set (0.00 sec)可以看到当前的事务隔离级别为 READ-COMMITTED 读提交下面看看当前隔离级别下的事务隔离详情,开启两个查询终端A、B。下面有一个order表,初始数据如下mysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 1 |+—-+——–+1 row in set (0.00 sec)第一步,在A,B中都开启事务mysql> start transaction;Query OK, 0 rows affected (0.00 sec)第二步查询两个终端中的number值A mysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 1 |+—-+——–+1 row in set (0.00 sec)B mysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 1 |+—-+——–+1 row in set (0.00 sec)第三步将B中的number修改为2,但不提交事务mysql> update order set number=2;Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0第四步查询A中的值mysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 1 |+—-+——–+1 row in set (0.00 sec)发现A中的值并没有修改。第五步,提交事务B,再次查询A中的值Bmysql> commit;Query OK, 0 rows affected (0.01 sec)Amysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 2 |+—-+——–+1 row in set (0.00 sec)发现A中的值已经更改第六步,提交A中的事务,再次查询A,B的值。Amysql> commit;Query OK, 0 rows affected (0.00 sec)mysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 2 |+—-+——–+1 row in set (0.00 sec)Bmysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 2 |+—-+——–+1 row in set (0.00 sec)发现A,B中的值都更改为2了。下面给一个简单的示意图我们可以看到,在事务隔离级别为读已提交 的情况下,当B中事务提交了之后,即使A未提交也可以读到B事务提交的结果。这样解决了脏读的问题。原文地址 ...

January 9, 2019 · 1 min · jiezi

Mysql事务隔离级别之可重复读

Mysql事务隔离级别之。可重复读(REPEATABLE-READ)查看mysql 事务隔离级别mysql> SELECT @@tx_isolation;+—————–+| @@tx_isolation |+—————–+| REPEATABLE-READ |+—————–+1 row in set (0.00 sec)可以看到默认的事务隔离级别为 REPEATABLE-READ 可重复读下面看看当前隔离级别下的事务隔离详情,开启两个查询终端A、B。下面有一个order表,初始数据如下mysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 1 |+—-+——–+1 row in set (0.00 sec)第一步,在A,B中都开启事务mysql> start transaction;Query OK, 0 rows affected (0.00 sec)第二步查询两个终端中的number值A mysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 1 |+—-+——–+1 row in set (0.00 sec)B mysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 1 |+—-+——–+1 row in set (0.00 sec)第三步将B中的number修改为2,但不提交事务mysql> update order set number=2;Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0第四步查询A中的值mysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 1 |+—-+——–+1 row in set (0.00 sec)发现A中的值并没有修改。第五步,提交事务B,再次查询A中的值Bmysql> commit;Query OK, 0 rows affected (0.01 sec)Amysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 1 |+—-+——–+1 row in set (0.00 sec)发现A中的值还是未更改第六步,提交A中的事务,再次查询A,B的值。Amysql> commit;Query OK, 0 rows affected (0.00 sec)mysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 2 |+—-+——–+1 row in set (0.00 sec)Bmysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 2 |+—-+——–+1 row in set (0.00 sec)发现A,B中的值都更改为2了。下面给一个简单的示意图我们可以看到,在事务隔离级别为可重复读 的情况下,A,B事务在执行期间前后看到的数据是一致的。这也就是可重复读的隔离特性。·遵循事务在执行期间看到的数据前后必须是一致的`原文地址 ...

January 4, 2019 · 1 min · jiezi

MySQL探秘(八):InnoDB的事务

事务是数据库最为重要的机制之一,凡是使用过数据库的人,都了解数据库的事务机制,也对ACID四个基本特性如数家珍。但是聊起事务或者ACID的底层实现原理,往往言之不详,不明所以。所以,今天我们就一起来分析和探讨InnoDB的事务机制,希望能建立起对事务底层实现原理的具体了解。 数据库事务具有ACID四大特性。ACID是以下4个词的缩写:原子性(atomicity) :事务最小工作单元,要么全成功,要么全失败 。一致性(consistency): 事务开始和结束后,数据库的完整性不会被破坏 。隔离性(isolation) :不同事务之间互不影响,四种隔离级别为RU(读未提交)、RC(读已提交)、RR(可重复读)、SERIALIZABLE (串行化)。持久性(durability) :事务提交后,对数据的修改是永久性的,即使系统故障也不会丢失 。 下面,我们就以一个具体实例来介绍数据库事务的原理,并介绍InnoDB是如何实现ACID四大特性的。示例介绍 我们首先来看一下具体的示例。大家可以自己亲自试验一下,这样理解和记忆都会更加深刻。 首先,使用如下的SQL语句创建两张表,分别是goods和trade,代表货物和交易。并向goods表中插入一条记录,id为1的货物数量为10。CREATE TABLE goods (id INT, num INT, PRIMARY KEY(id));CREATE TABLE trade (id INT, goods_id INT, user_id INT, PRIMARY KEY(id));INSERT INTO goods VALUES(1, 10); 然后打开终端,连接数据库,开启会话一,先用BEGIN显示开启一个事务。会话一先将goods表中id为1的货物的数量减一,然后向trade表中添加一笔交易的记录,最后使用COMMIT显示提交事务。 而会话二则先查询goods表中id为1的货物数量,然后向trade表中添加一笔交易记录,接着更新goods表中id为1的货物的数量,最后使用ROLLBACK进行事务的回滚。其中,两个会话中执行的具体语句和先后顺序如下图所示。 这个示例可以体现数据库事务的很多特性,我们一一来介绍。首先会话一的操作2更新了id为1的货物的数量,但是会话二的操作5读出来的数量仍然是10,这体现了事务的隔离性,使用InnoDB的多版本控制机制实现。 会话二的操作7也要更新同种货物的数量,此时因为会话一的操作2已经更新了该货物的数量,InnoDB已经锁住了该记录的行锁,所以操作7会被阻塞,直到会话一COMMIT。但是会话一的操作4和会话二的操作7都是向trade表中插入记录,后者却不会因为前者而阻塞,因为二者插入的不是同一行记录。锁机制是一种常见的并发控制机制,它和多版本控制机制一起实现了InnoDB事务的隔离性,关于InnoDB锁相关的具体内容可以参考InnoDB锁的类型和状态查询和InnoDB行锁算法。 会话一事务最终使用COMMIT提交了事务而会话二事务则使用ROLLBACK回滚了整个事务,这体现了事务的原子性。即事务的一系列操作要么全部执行(COMMIT),要么就全部不执行(ROLLBACK),不存在只执行一部分的情况。InnoDB使用事务日志系统来实现事务的原子性。这里有的同学就会问了,如果中途连接断开或者Server Crash会怎么样。能怎么样,直接自动回滚呗。 一旦会话一使用COMMIT操作提交事务成功后,那么数据一定会被写入到数据库中并持久的存储起来,这体现了事务的持久性。InnoDB使用redo log机制来实现事务的持久性。 而事务的一致性比较难以理解,简单的讲在事务开始时,此时数据库有一种状态,这个状态是所有的MySQL对象处于一致的状态,例如数据库完整性约束正确,日志状态一致等。当事务提交后,这时数据库又有了一个新的状态,不同的数据,不同的索引,不同的日志等。但此时,约束,数据,索引,日志等MySQL各种状态还是要保持一致性。 也就是说数据库从一个一致性的状态,变到另一个一致性的状态。事务执行后,并没有破坏数据库的完整性约束。 下面我们就来详细讲解一下上述示例涉及的事务的ACID特性的具体实现原理。总结来说,事务的隔离性由多版本控制机制和锁实现,而原子性、一致性和持久性通过InnoDB的redo log、undo log和Force Log at Commit机制来实现。原子性,持久性和一致性 原子性,持久性和一致性主要是通过redo log、undo log和Force Log at Commit机制机制来完成的。redo log用于在崩溃时恢复数据,undo log用于对事务的影响进行撤销,也可以用于多版本控制。而Force Log at Commit机制保证事务提交后redo log日志都已经持久化。 开启一个事务后,用户可以使用COMMIT来提交,也可以用ROLLBACK来回滚。其中COMMIT或者ROLLBACK执行成功之后,数据一定是会被全部保存或者全部回滚到最初状态的,这也体现了事务的原子性。但是也会有很多的异常情况,比如说事务执行中途连接断开,或者是执行COMMIT或者ROLLBACK时发生错误,Server Crash等,此时数据库会自动进行回滚或者重启之后进行恢复。 我们先来看一下redo log的原理,redo log顾名思义,就是重做日志,每次数据库的SQL操作导致的数据变化它都会记录一下,具体来说,redo log是物理日志,记录的是数据库页的物理修改操作。如果数据发生了丢失,数据库可以根据redo log进行数据恢复。 InnoDB通过Force Log at Commit机制实现事务的持久性,即当事务COMMIT时,必须先将该事务的所有日志都写入到redo log文件进行持久化之后,COMMIT操作才算完成。 当事务的各种SQL操作执行时,即会在缓冲区中修改数据,也会将对应的redo log写入它所属的缓存。当事务执行COMMIT时,与该事务相关的redo log缓冲必须都全部刷新到磁盘中之后COMMIT才算执行成功。 redo log写入磁盘时,必须进行一次操作系统的fsync操作,防止redo log只是写入了操作系统的磁盘缓存中。参数innodb_flush_log_at_trx_commit可以控制redo log日志刷新到磁盘的策略,它的具体作用可以查阅InnoDB的磁盘文件及落盘机制 redo log全部写入磁盘后事务就算COMMIT成功了,但是此时事务修改的数据还在内存的缓冲区中,称其为脏页,这些数据会依据检查点(CheckPoint)机制择时刷新到磁盘中,然后删除相应的redo log,但是如果在这个过程中数据库Crash了,那么数据库重启时,会依据redo log file将那些还在内存中未更新到磁盘上的数据进行恢复。 数据库为了提高性能,数据页在内存修改后并不是每次都会刷到磁盘上。而是引入checkpoint机制,择时将数据页落盘,checkpoint记录之前的数据页保证一定落盘了,这样相关的redo log就没有用了(由于InnoDB redo log file循环使用,这时这部分日志就可以被覆盖),checkpoint之后的数据页有可能落盘,也有可能没有落盘,所以checkpoint之后的redo log file在崩溃恢复的时候还是需要被使用的。InnoDB会依据脏页的刷新情况,定期推进checkpoint,从而减少数据库崩溃恢复的时间。检查点的信息在第一个日志文件的头部。 数据库崩溃重启后需要从redo log中把未落盘的脏页数据恢复出来,重新写入磁盘,保证用户的数据不丢失。当然,在崩溃恢复中还需要回滚没有提交的事务。由于回滚操作需要undo日志的支持,undo日志的完整性和可靠性需要redo日志来保证,所以崩溃恢复先做redo恢复数据,然后做undo回滚。 在事务执行的过程中,除了记录redo log,还会记录一定量的undo log。undo log记录了数据在每个操作前的状态,如果事务执行过程中需要回滚,就可以根据undo log进行回滚操作。 undo log的存储不同于redo log,它存放在数据库内部的一个特殊的段(segment)中,这个段称为回滚段。回滚段位于共享表空间中。undo段中的以undo page为更小的组织单位。undo page和存储数据库数据和索引的页类似。因为redo log是物理日志,记录的是数据库页的物理修改操作。所以undo log的写入也会产生redo log,也就是undo log的产生会伴随着redo log的产生,这是因为undo log也需要持久性的保护。如上图所示,表空间中有回滚段和叶节点段和非叶节点段,而三者都有对应的页结构。 我们再来总结一下数据库事务的整个流程,如下图所示。 事务进行过程中,每次sql语句执行,都会记录undo log和redo log,然后更新数据形成脏页,然后redo log按照时间或者空间等条件进行落盘,undo log和脏页按照checkpoint进行落盘,落盘后相应的redo log就可以删除了。此时,事务还未COMMIT,如果发生崩溃,则首先检查checkpoint记录,使用相应的redo log进行数据和undo log的恢复,然后查看undo log的状态发现事务尚未提交,然后就使用undo log进行事务回滚。事务执行COMMIT操作时,会将本事务相关的所有redo log都进行落盘,只有所有redo log落盘成功,才算COMMIT成功。然后内存中的数据脏页继续按照checkpoint进行落盘。如果此时发生了崩溃,则只使用redo log恢复数据。隔离性 InnoDB事务的隔离性主要通过多版本控制机制和锁机制实现,具体可以参考多版本控制,InnoDB锁的类型和状态查询和InnoDB行锁算法三篇文章。后记 本来想一篇文章将MySQL的事务机制讲明白,写完自己读了一遍,还是发现内容有些晦涩难懂,复杂的知识本来就是很难讲明白的,夫夷以近,则游者众;险以远,则至者少,希望读者以本文作为一篇指引性的文章,自己再去更加深入的地方去探秘。不过,能将复杂知识讲解的通俗简单也是一项很大的本领,文字和讲解能力还是需要提示的。Mysql探索(一):B-Tree索引数据库内部存储结构探索MySQL探秘(二):SQL语句执行过程详解MySQL探秘(三):InnoDB的内存结构和特性MySQL探秘(四):InnoDB的磁盘文件及落盘机制MySQL探秘(五):InnoDB锁的类型和状态查询MySQL探秘(六):InnoDB一致性非锁定读参考MySQL · 引擎特性 · InnoDB 事务系统MySQL · 引擎特性 · InnoDB 崩溃恢复过程 ...

December 24, 2018 · 1 min · jiezi

聊聊分布式事务

这次使用分布式事务框架过程中了学习了一些分布式事务知识,所以本文我们就来聊聊分布式事务那些事。首先我们先回顾下什么是事务。事务什么是事务?这个作为后端开发,日常开发中只要与数据库有交互,肯定就会使用过事务。现在摘抄一段wiki的解释,解释下什么是事务。是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成数据库系统具有事务特性,这是其有别与文件系统重要特性。传统的文件系统,如果正在写文件,操作系统突然崩溃,此时文件可能被破坏。数据库系统引入事务特性,可以保证数据库从一种状态转换为另一种状态。在提交工作时,可以确保要么所有修改都被保存,要么所有都不保存。通常一个事务会有多个读写操作构成。事务具有四个基本特性,俗称ACID。A(Atomicity):原子性。事务会被当做一个整体,要么所有语句都成功,要么都失败,不能存在部分语句成功,部分失败的情况。C(Consistenc):一致性。数据库的状态从一种状态转变为另外一种状态,事务开始之前和是事务结束之后,数据库完整性约束不变。什么叫数据库完整性约束不变?举个例子,若一个表姓名字段为唯一约束,若在事务提交或回滚后,姓名字段变成非唯一了,这就破坏数据库的完整性约束。I(Isolation):隔离性。多个并发事务执行,互不影响。D(Durability):持久性。事务提交之后,其对数据库相关修改能永久保存在数据库。所以该特性需要数据库系统可以在崩溃时需要恢复时也能提交的数据都不丢失。因此早期我们的系统只在存在一个数据源情况下,这个时候可以依靠数据库系统事务来保证业务的正确性。但是随着业务的不断扩展,我们业务的一个单表可能就存在千万数据,在使用再使用一个数据库实例,就会可能存在性相关能问题。这个时候我们就会考虑分库分表。但是这样就有可能导致,单个应用连接多个数据源的情况。如下图示例。上图一次购买过程,商家余额表与用户余额表处于两个单独的数据库实例中,这样单独的事务能保证扣减商家余额或用户余额要么扣减成功,要么扣减失败。但是我们却无法保证两个事务同时成功或同时失败。还有一种情况,随着系统越来越庞大,我们会选择将系统应用拆分多个微服务,让单个应用只操作一个数据源。这个时候我们就会碰到,一次业务调用,将会调用多个应用,每个应用单独操作数据源的情况,如下图。这种情况下我们更加不能保证所有调用都成功。由上面的例子下我们可以看出,随着业务发展,传统的单机事务已经无法满足我们的业务的需求,这个时候我们就需要分布式事务来保证。分布式事务摘抄一段 wiki 上解释。A distributed transaction is a database transaction in which two or more network hosts are involved.我们先来讲下实现分布式事务一些理论基础。分布式事务技术理论CAP 定理。在一个分布式系统(指互相连接并共享数据的节点的集合)中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。摘录极客时间从0开始学架构第22章解释虽然 CAP 理论定义是三个要素中只能取两个,但放到分布式环境下来思考,我们会发现必须选择 P(分区容忍)要素,因为网络本身无法做到 100% 可靠,有可能出故障,所以分区是一个必然的现象。如果我们选择了 CA 而放弃了 P,那么当发生分区现象时,为了保证 C,系统需要禁止写入,当有写入请求时,系统返回 error(例如,当前系统不允许写入),这又和 A 冲突了,因为 A 要求返回 no error 和 no timeout。因此,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构BASE 理论,分别是以下三个单词的缩写。Basically Available(基本可用):分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。Soft state(软状态):允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是CAP中的不一致。Eventually consistent(最终一致性):最终一致是指经过一段时间后,所有节点数据都将会达到一致。BASE 是对CAP 中 AP 方案的一种补充。在 BASE 中用软状态和最终一致,保证了延迟后的一致性。BASE 和 ACID 是相反的,ACID 是一种强一致性模型,而 BASE 却是牺牲这种强一致性,允许数据短时间内不一致,最终一致性。接下来我们看看分布式事务有哪几种实现方案。分布式事务实现方案基于数据库资源层面2PC 两阶段提交协议3PC 三阶段提交协议基于业务层面TCC基于数据库资源层面实现方案,由于存在多个事务,我们需要存在一个角色管理各个事务的状态。我们将这个角色称为协调者,事务参与者称为参与者。参与者与协调者一般会基于某种特定协议,目前比较有名的为 XA 接口协议。基于协调者与参与者的思想设定,分别提出了 2PC 与 3PC 实现XA 分布式事务。2PC 两阶段提交协议如名字所知,这个过程主要分为两步。第一阶段,协调者(事务管理器)将涉及到事务的进行预提交,这个时候数据库资源开始被锁定。参与者将 undo 与 redo 写入事务日志。第二阶段,参与者(资源管理器)行提交事务,或者利用 undo 日志回滚事务,释放资源。整个过程如下图。分布式事务提交成功场景:分布式事务回滚场景:该方案的优点为:实现比较简单,主流数据库都支持,强一致性。MySQL 5.5 以后基于 XA 协议实现.相应该方案也存在缺点:协调者的单点问题。若协调者在提交阶段宕机,参与者一直在等待,就一直锁定资源,一直阻塞。虽然可以重新选举协调者,但是无法解决该问题。同步阻塞时间过长,整个执行过程事务是阻塞的,直到提交完成,释放资源,若在提交过程/回滚过程,因为网络延时,参与者一直未收到指令,则参与者一直被阻塞。数据不一致。第二阶段,协调者发出第一个提交信号后后宕机,则第一个参与者提交事务,第二个参与者因为未收到协调者信号,无法进行事务提交。于是针对 2PC 存在的缺点,提出改进方案,3PC。3PC 三阶段提交协议三阶段提交,在两阶段提交的基础下,改进两阶段。三阶段步骤如下。CanCommit,协调者询问参与者是否可以进行事务提交。PreCommit ,若所有参与者可以进行事务提交,协调者下达 PreCommit 命令,参与者锁定资源,并等待最终命令。所有参与者返回确认信息,协调者向各个事务下发事务执行通知,锁定资源,并将执行情况返回。部分参与者返回否认信息或协调者等待超时。这种情况,协调者认为事务无法正常执行,下发中断指令,各个参与者退出预备状态Do Commit,若第二阶段全部回应 ack,则下达 Do Commit ,进行事务最终提交,否则下达中断事务命令,所有参与者进行事务回滚。所有参与者正常执行执行事务,协调者下发最终提交指令,释放锁定资源。部分参与者执行事务失败,协调者等待超时,协调者下发回滚指令,释放锁定资源。具体见下图。三阶段提交对比两阶段,引入超时机制减少事务阻塞,解决单点故障。在第三阶段,一旦参与者无法接受到协调者信号时,等待超时之后,参与者默认执行 commit,释放资源。三阶段任然不能解决数据一致性问题。若协调者发出回滚命令,但是由于网络问题,参与者在等待时间内都无法接收到,这时参与者默认提交事务,而其他事务进行了回滚,造成事务不一致。TCCTCC 事务为了解决在事务运行过程中大颗粒度资源锁定的问题,业界提出一种新的事务模型,它是基于业务层面的事务定义。锁粒度完全由业务自己控制。它本质是一种补偿的思路。它把事务运行过程分成 Try、Confirm / Cancel 两个阶段。在每个阶段的逻辑由业务代码控制。这样就事务的锁粒度可以完全自由控制。业务可以在牺牲隔离性的情况下,获取更高的性能。TCC 分别为 Trying,Confirm,Cancel 三个单词缩写。不同于 2PC 与 3PC 基于数据库层面,TCC 基于应用层面。TCC 三个动作分别为:Trying:完成所有业务检查(一致性)预留必须业务资源(准隔离性)Confirm:真正执行业务Confirm操作要满足幂等性Cancel:释放Try阶段预留的业务资源Cancel操作要满足幂等性上面说法,一听起来有点生涩难懂,没关系我们使用实际案例解释。下面我们模拟商城一次支付过程。用户下单使用组合支付,即余额加红包支付。一次正常流程为:创建订单下单调用余额系统,扣减余额调用红包系统,扣减红包余额修改订单状态为已支付完后支付。实际过程如下图。但是这么一个支付过程调用多个子服务,我们不能保证所有服务都能成功,比如我们在调用红包系统扣减红包系统失败。这个时候我们就碰到尴尬的场景,由于红包服务失败,导致方法异常退出,这个时候订单状态为初始状态,但是用户余额已经扣减。这对用户体验非常不友好。所以这次支付过程,我们必须存在机制将这次过程当成一次整体的行为,必须保证这其中服务调用,要么都成功,要么都失败,成为一个整体的事务。这时我们可以引入 TCC 事务,将整个下单过程作为一个整体。引入后,由于余额系统扣减是失败,这个时候我们回滚订单系统与红包系统。整个过程如下图。由于余额系统的失败,我们需要撤销这次过程中所有更改,所以我们向订单系统发送撤销通知,向红包系统发出撤销通知。因此系统引入 TCC 事务后,我们需要改造我们的调用过程。系统如何引入 TCC 事务根据 TCC 事务三步,这个时候我们必须将各个服务改造成 Try Confirm Cancle 三步、TCC TRY:根据上面的业务,订单系统增加 try 方法将订单状态修改成 PAYING。余额系统增加一个 try 方法,先检查用于余额是否充足,然后先将余额扣减,然后将扣减的余额增加到冻结金额。红包系统同余额系统。从改造过程可以看出,TCC try 方法需检查各业务资源,且这过程需要引入中间状态。我们根据下图来看整个过程。TCC Confirm:TCC 第一步 TRY 如果所有子服务调用都成功,这个时候我们就需要确认各服务。各个服务增加 confirm 方法。如余额系统 confirm 方法用来将冻结金额置为0,红包系统如上。订单系统将订单状态修改为 SUCCESS。confirm 方法需要注意实现幂等。如订单系统更新前,一定要先判断该笔订单状态处于 PAYING,才能更新订单。整个过程如下图。讲到这里,必须用到 TCC 事务框架推动各服务。TCC 事务管理器感知到 TRY 方法结束后,自动调用各服务提供的 confirm 方法,将各服务状态修改为终态。TCC Cancle:如若 TCC Try 过程中,冻结红包方法失败,这时我们就需要将之前修改都撤销,修改成其初始状态。cancle 方法也需要实现幂等如 confirm 方法 如下图:看到这,我们我们可以看出 TCC Try 成功,confirm 必定要成功,try 失败,cancle 必定要成功。因为 confirm 是系统更新为终态的关键。但是现实这么无情,生产系统 confirm 或 cancle 肯定会有几率失败,这个时候就需要 TCC 框架记录调用 confirm 结果。如果 confirm 调用失败,TCC 框架需要记录下来,然后间隔一定时间再次去调用。总结与思考看完全文,基本上对分布式事务又一定了解了吧。我们基于此对此总结下。使用分布式事务,我们需要结合我们实际场景应用。如果业务还处于开始阶段,我们其实可以选择数据库事务来保证快速上线迭代。等到业务一定阶段,系统开始拆分,数据库也拆分,这时如果业务需要保证一致性,这时必须使用分布式事务。这时候使用分布式事务,我们需要基于业务考虑使用哪种。使用 2PC 或 3PC 实现的分布式框架,业务应用层无需改动,接入较简单。但是相对应能较低,数据资源锁定较长。不太适合互联网等高并发业务场景。而使用基于 TCC 实现分布式框架,相对 2PC 性能较高,可以保证数据最终一致性。但是对于应用层来说,一个方法必须改造成三个方法,且业务中需引入一些中间状态,相对而言应用改造程度较大。参考资料分布式事务:两阶段提交与三阶段提交关于分布式事务、两阶段提交协议、三阶提交协议拜托,面试请不要再问我TCC分布式事务的实现原理!2PC和3PC一点理解如果觉得好的话,请帮作者点个赞呗~ 谢谢 ...

December 14, 2018 · 1 min · jiezi

php 多进程模拟并发事务产生的一些问题

表drop table if exists test;create table if not exists test ( id int not null auto_increment , count int default 0 , primary key id (id)) engine=innodb character set utf8mb4 collate = utf8mb4_bin comment ‘测试表’;insert into test (count) values (100);php 代码// 进程数量$pro_count = 100;$pids = [];for ($i = 0; $i < $pro_count; ++$i){ $pid = pcntl_fork(); if ($pid < 0) { // 主进程 throw new Exception(‘创建子进程失败: ’ . $i); } else if ($pid > 0) { // 主进程 $pids[] = $pid; } else { // 子进程 try { $pdo = new PDO(…); $pdo->beginTransaction(); $stmt = $pdo->query(‘select count from test’); $count = $stmt->fetch(PDO::FETCH_ASSOC)[‘count’]; $count = intval($count); if ($count > 0) { $count–; $pdo->query(‘update test set count = ’ . $count . ’ where id = 2’); } $pdo->commit(); } catch(Exception $e) { $pdo->rollBack(); throw $e; } // 退出子进程 exit; }}期望的结果期望 count 字段减少的量超过 100,变成负数!也就是多减!实际结果并发 200 的情况下,运行多次后的结果分别如下:1. count = 652. count = 753. count = 554. count = 84…与期望结果相差甚远!为什么会出现这样的现象呢?解释首先清楚下目前的程序运行环境,并发场景。何为并发,几乎同时执行,称之为并发。具体解释如下:进程 过程 获取 更新1-40 同时创建并运行 100 9941-80 同时创建并运行 99 9881 - 100 同时创建并运行 98 97对上述第一行做解释,第 1-40 个子进程的创建几乎同时,运行也几乎同时:进程 1 获取 count = 100,更新 99进程 2 获取 count = 100,更新 99…进程 40 获取 count = 100,更新 99所以,实际上这些进程都做了一致的操作,并没有按照预期的那样:进程1 获取 count=100,更新 99;进程 2 获取进程1更新后的结果 count=99,更新98;…;进程 99 获取进程 98更新后的结果count=1,更新0,产生的现象就是少减了!!结论采用上述做法实现的程序,库存总是 >= 0。疑问那要模拟超库存的场景该如何设计程序呢?仍然采用上述代码,将以下代码:if ($count > 0) { $count–; $pdo->query(‘update test set count = ’ . $count . ’ where id = 2’);}修改成下面这样:if ($count > 0) { $pdo->query(‘update test set count = count - 1 where id = 2’);}结果就会出现超库存!!库存 100,并发 200,最终库存减少为 -63。为什么会出现这样的情况呢?以下描述了程序运行的具体过程进程 1 获取库存 100,更新 99进程 2 获取库存 100,更新 98(99 - 1)进程 3 获取库存 100,更新 97(98 - 1)…. 进程 168 获取库存 1 ,更新 0(1-1)进程 169 获取库存 1 ,更新 -1(0 - 1)进程 170 获取库存 1 ,更新 -2(-1 - 1)….进程 200 获取库存 1,更新 -63(-62 - 1)现在看来很懵逼,实际就是下面这条语句导致的:$pdo->query(‘update test set count = count - 1 where id = 2’);这边详细阐述 进程 1,简称 a;进程 2,简称 b 他们具体的执行顺序:1. a 查询到库存 1002. b 查询到库存 1003. a 更新库存为 99(100 - 1),这个应该秒懂4. b 更新库存为 98(99 - 1) - b 在执行更新操作的时候拿到的是 a 更新后的库存! - 为什么会这样?因为更新语句是 update test set count = count - 1 where id = 2 ...

December 5, 2018 · 2 min · jiezi

认识 MongoDB 4.0 的新特性——事务(Transactions)

前言相信使用过主流的关系型数据库的朋友对“事务(Transactions)”不会太陌生,它可以让我们把对多张表的多次数据库操作整合为一次原子操作,这在高并发场景下可以保证多个数据操作之间的互不干扰;并且一旦在这些操作过程任一环节中出现了错误,事务会中止并且让数据回滚,这使得同时在多张表中修改数据的时候保证了数据的一致性。以前 MongoDB 是不支持事务的,因此开发者在需要用到事务的时候,不得不借用其他工具,在业务代码层面去弥补数据库的不足。随着 4.0 版本的发布,MongoDB 也为我们带来了原生的事务操作,下面就让我们一起来认识它,并通过简单的例子了解如何去使用。介绍事务和副本集(Replica Sets)副本集是 MongoDB 的一种主副节点架构,它使数据得到最大的可用性,避免单点故障引起的整个服务不能访问的情况的发生。目前 MongoDB 的多表事务操作仅支持在副本集上运行,想要在本地环境安装运行副本集可以借助一个工具包——run-rs,以下的文章中有详细的使用说明:https://thecodebarbarian.com/…事务和会话(Sessions)事务和会话(Sessions)关联,一个会话同一时刻只能开启一个事务操作,当一个会话断开,这个会话中的事务也会结束。事务中的函数Session.startTransaction()在当前会话中开始一次事务,事务开启后就可以开始进行数据操作。在事务中执行的数据操作是对外隔离的,也就是说事务中的操作是原子性的。Session.commitTransaction()提交事务,将事务中对数据的修改进行保存,然后结束当前事务,一次事务在提交之前的数据操作对外都是不可见的。Session.abortTransaction()中止当前的事务,并将事务中执行过的数据修改回滚。重试当事务运行中报错,catch 到的错误对象中会包含一个属性名为 errorLabels 的数组,当这个数组中包含以下2个元素的时候,代表我们可以重新发起相应的事务操作。TransientTransactionError:出现在事务开启以及随后的数据操作阶段UnknownTransactionCommitResult:出现在提交事务阶段示例经过上面的铺垫,你是不是已经迫不及待想知道究竟应该怎么写代码去完成一次完整的事务操作?下面我们就简单写一个例子:场景描述: 假设一个交易系统中有2张表——记录商品的名称、库存数量等信息的表 commodities,和记录订单的表 orders。当用户下单的时候,首先要找到 commodities 表中对应的商品,判断库存数量是否满足该笔订单的需求,是的话则减去相应的值,然后在 orders 表中插入一条订单数据。在高并发场景下,可能在查询库存数量和减少库存的过程中,又收到了一次新的创建订单请求,这个时候可能就会出问题,因为新的请求在查询库存的时候,上一次操作还未完成减少库存的操作,这个时候查询到的库存数量可能是充足的,于是开始执行后续的操作,实际上可能上一次操作减少了库存后,库存的数量就已经不足了,于是新的下单请求可能就会导致实际创建的订单数量超过库存数量。以往要解决这个问题,我们可以用给商品数据“加锁”的方式,比如基于 Redis 的各种锁,同一时刻只允许一个订单操作一个商品数据,这种方案能解决问题,缺点就是代码更复杂了,并且性能会比较低。如果用数据库事务的方式就可以简洁很多:commodities 表数据(stock 为库存):{ “_id” : ObjectId(“5af0776263426f87dd69319a”), “name” : “灭霸原味手套”, “stock” : 5 }{ “_id” : ObjectId(“5af0776263426f87dd693198”), “name” : “雷神专用铁锤”, “stock” : 2 }orders 表数据:{ “_id” : ObjectId(“5af07daa051d92f02462644c”), “commodity”: ObjectId(“5af0776263426f87dd69319a”), “amount”: 2 }{ “_id” : ObjectId(“5af07daa051d92f02462644b”), “commodity”: ObjectId(“5af0776263426f87dd693198”), “amount”: 3 }通过一次事务完成创建订单操作(mongo Shell):// 执行 txnFunc 并且在遇到 TransientTransactionError 的时候重试function runTransactionWithRetry(txnFunc, session) { while (true) { try { txnFunc(session); // 执行事务 break; } catch (error) { if ( error.hasOwnProperty(’errorLabels’) && error.errorLabels.includes(‘TransientTransactionError’) ) { print(‘TransientTransactionError, retrying transaction …’); continue; } else { throw error; } } }}// 提交事务并且在遇到 UnknownTransactionCommitResult 的时候重试function commitWithRetry(session) { while (true) { try { session.commitTransaction(); print(‘Transaction committed.’); break; } catch (error) { if ( error.hasOwnProperty(’errorLabels’) && error.errorLabels.includes(‘UnknownTransactionCommitResult’) ) { print(‘UnknownTransactionCommitResult, retrying commit operation …’); continue; } else { print(‘Error during commit …’); throw error; } } }}// 在一次事务中完成创建订单操作function createOrder(session) { var commoditiesCollection = session.getDatabase(‘mall’).commodities; var ordersCollection = session.getDatabase(‘mall’).orders; // 假设该笔订单中商品的数量 var orderAmount = 3; // 假设商品的ID var commodityID = ObjectId(‘5af0776263426f87dd69319a’); session.startTransaction({ readConcern: { level: ‘snapshot’ }, writeConcern: { w: ‘majority’ }, }); try { var { stock } = commoditiesCollection.findOne({ _id: commodityID }); if (stock < orderAmount) { print(‘Stock is not enough’); session.abortTransaction(); throw new Error(‘Stock is not enough’); } commoditiesCollection.updateOne( { _id: commodityID }, { $inc: { stock: -orderAmount } } ); ordersCollection.insertOne({ commodity: commodityID, amount: orderAmount, }); } catch (error) { print(‘Caught exception during transaction, aborting.’); session.abortTransaction(); throw error; } commitWithRetry(session);}// 发起一次会话var session = db.getMongo().startSession({ readPreference: { mode: ‘primary’ } });try { runTransactionWithRetry(createOrder, session);} catch (error) { // 错误处理} finally { session.endSession();}上面的代码看着感觉很多,其实 runTransactionWithRetry 和 commitWithRetry 这两个函数都是可以抽离出来成为公共函数的,不需要每次操作都重复书写。用上了事务之后,因为事务中的数据操作都是一次原子操作,所以我们就不需要考虑分布并发导致的数据一致性的问题,是不是感觉简单了许多?你可能注意到了,代码中在执行 startTransaction 的时候设置了两个参数——readConcern 和 writeConcern,这是 MongoDB 读写操作的确认级别,在这里用于在副本集中平衡数据读写操作的可靠性和性能,如果在这里展开就太多了,所以感兴趣的朋友建议去阅读官方文档了解一下:readConcern:https://docs.mongodb.com/mast…writeConcern:https://docs.mongodb.com/mast…我们正在进行限时有奖读者调查,欢迎参加:创宇前端期待听到你的声音文 / Xss本文已由作者授权发布,版权属于创宇前端。欢迎注明出处转载本文。本文链接:https://knownsec-fed.com/2018…想要订阅更多来自知道创宇开发一线的分享,请搜索关注我们的微信公众号:乐趣区。欢迎留言讨论,我们会尽可能回复。感谢您的阅读。 ...

November 13, 2018 · 2 min · jiezi

Spring 事务管理

事务因Github自动化测试的原因,(最后找到的原因是getOneSavedDepartment时,这个Department没存上,所以ToMessage引用了一个未持久化的Department,就报错了),特此学习了一下事务。事务,基础概念就不说了。Spring为我们提供了对事务的支持,我们只需要很简单的注解或者XML配置即可实现。去网上找了好多篇关于Spring事务的博客,全是字,根本没有心情去看,更谈不上深入理解了,作者还在标题中自认为自己讲的比较好。如果你也不喜欢大段的文字,请继续向下看,我保证我画的图不会让你失望。org.springframework.transaction.annotation.Transactional@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface Transactional { @AliasFor(“transactionManager”) String value() default “”; @AliasFor(“value”) String transactionManager() default “”; Propagation propagation() default Propagation.REQUIRED; Isolation isolation() default Isolation.DEFAULT; int timeout() default -1; boolean readOnly() default false; Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {};}事务注解中有好多的属性,如果是简单场景的话,那我们只需要默认地配置就好了。但是如果应用发嵌套事务的复杂场景下,我们就需要研究研究这几个配置项了。这里我们深入学习一下事务的传播属性propagation。PropagationREQUIRED因为我们的事务传播级别就是REQUIRED,所以我们不配置propagation来测试REQUIRED。如果当前存在事务,则使用当前事务。如果不存在任何事务,则创建一个新的事务。内部方法开启默认REQUIRED级别的事务一个????,一个????,省略set、get方法。@Entitypublic class Cat { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name;}@Entitypublic class Dog { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name;}一个猫实现类,一个狗实现类,都有save方法,并且DogServiceImpl中还有一个保存并抛出异常的方法,用于测试回滚。省略依赖注入的代码。@Servicepublic class CatServiceImpl implements CatService { @Override @Transactional public void save(Cat cat) { catRepository.save(cat); }}@Servicepublic class DogServiceImpl implements DogService { @Override @Transactional public void save(Dog dog) { dogRepository.save(dog); } @Override @Transactional public void saveThrowException(Dog dog) { dogRepository.save(dog); throw new RuntimeException(); }}外部方法不开启事务public void test() { Cat cat = new Cat(); cat.setName(“Hello Kitty!”); catService.save(cat); Dog dog = new Dog(); dog.setName(“史努比”); dogService.save(dog);}没有任何异常抛出,猫存上了,狗也存上了。将保存狗的方法由save修改为saveThrowException,抛个异常,测试一下回滚。public void test() { Cat cat = new Cat(); cat.setName(“Hello Kitty!”); catService.save(cat); Dog dog = new Dog(); dog.setName(“史努比”); dogService.saveThrowException(dog);}猫存上了,狗没存上。因为同一事务的所有操作是同时成功,同时失败的。所以我们断定,猫和狗用的是两个事务。图解如果不存在任何事务,则创建一个新的事务。save猫和save狗都是默认的REQUIRED级别,test方法未开启事务,所以当前不存在任何事务。所以save猫创建了一个新的事务,save狗也创建了一个新的事务。外部方法开启事务为外部test方法添加事务。@Transactionalpublic void test() { Cat cat = new Cat(); cat.setName(“Hello Kitty!”); catService.save(cat); Dog dog = new Dog(); dog.setName(“史努比”); dogService.save(dog);}显然,未抛出异常,都存上了。方法修改为保存并抛出异常。@Transactionalpublic void test() { Cat cat = new Cat(); cat.setName(“Hello Kitty!”); catService.save(cat); Dog dog = new Dog(); dog.setName(“史努比”); dogService.saveThrowException(dog);}两者都没存上,所以断定save狗的方法抛出的异常对save猫是有影响的,猜测二者用的是同一个事务。如果当前存在事务,则使用当前事务。如果捕获抛出的RuntimeException,该方法仍然不能保存。Dog dog = new Dog();dog.setName(“史努比”);try { dogService.saveThrowException(dog);} catch (RuntimeException e) { System.out.println(“error”);}会抛出异常,该事务不能被提交。总结:REQUIRED修饰的内部方法会加入外部方法的事务中,其中有任一一个方法抛异常,事务都会回滚。SUPPORTSSUPPORTS与REQUIRED类似:如果当前存在事务,则使用当前事务。如果不存在任何事务,则不使用事务。MANDATORYMANDATORY与REQUIRED类似:如果当前存在事务,则使用当前事务。如果不存在任何事务,则抛出异常。REQUIRES_NEW如果当前存在事务,则挂起当前事务。创建一个新的事务。应该适合该操作相对独立的情况。就比如下单加支付,如果支付失败,但是这个订单应该还存在。如果将事务传播级别修改为这个的话,那save狗如果抛出异常就不影响save猫了。@Override@Transactionalpublic void test() { Cat cat = new Cat(); cat.setName(“Hello Kitty!”); catService.save(cat); Dog dog = new Dog(); dog.setName(“史努比”); dogService.saveThrowException(dog);}@Override@Transactional(propagation = Propagation.REQUIRES_NEW)public void save(Cat cat) { catRepository.save(cat);}@Override@Transactional(propagation = Propagation.REQUIRES_NEW)public void saveThrowException(Dog dog) { dogRepository.save(dog); throw new RuntimeException();}NOT_SUPPORTED如果当前存在事务,则挂起当前事务。同时以非事务方式运行。NEVER永远不要存在事务。如果当前存在事务,则抛出异常。NESTED如果当前存在事务,则在当前事务的一个嵌套事务中运行。该级别只对DataSourceTransactionManager事务管理器生效。本来想测试一下的,可惜。org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider’s capabilitiesNestedTransactionNotSupportedException,不支持嵌套事务。StackOverflow上的回答,Hibernate不支持嵌套事务。总结哥仨REQUIRED、SUPPORTS、MANDATORY,如果当前存在事务,则使用当前事务。哥仨REQUIRES_NEW、NOT_SUPPORTED、NEVER都不支持当前存在的事务。NESTED,嵌套事务,觉得这个应该是事务中最好用且最合理的,可惜Hibernate不支持。 ...

September 2, 2018 · 2 min · jiezi