精华内容
下载资源
问答
  • Spring事务实现原理

    万次阅读 2020-09-10 09:39:51
    对于一个应用而言,事务的使用基本是...但是由于不了解底层的原理,这样的问题可能在今后的工作中往复出现。 本文就为大家揭开@Transactional下的秘密。 原生的事务管理 在没有Spring存在的时候,事务就已经诞生了...

    对于一个应用而言,事务的使用基本是不可避免的。虽然Spring给我们提供了开箱即用的事务功能—— @Transactional 。
    但是,自带的事务功能却也存在控制粒度不够的缺点。更糟糕的是, @Transactional 在某些情况下就失效了。可能一些读者baidu/google一下解决办法后,失效的问题确实解决了。但是由于不了解底层的原理,这样的问题可能在今后的工作中往复出现。
    本文就为大家揭开 @Transactional 下的秘密。

    原生的事务管理

    在没有Spring存在的时候,事务就已经诞生了。其实框架依赖的还是底层提供的能力,只不过它对这一过程的抽象和复用。
    这里我们用底层的API来了解下事务管理的过程(JDBC为例):

    	// 获取mysql数据库连接
    	Connection conn = DriverManager.getConnection("xxxx");
        conn.setAutoCommit(false);
    	statement = conn.createStatement();
    	// 执行sql,返回结果集
    	resultSet = statement.executeQuery("xxxx");
    	conn.commit(); //提交
    	//conn.rollback();//回滚
    

    上面是一个原生操作事务的一个例子,这些过程也是Spring事务逃不开的,只不过在为了编程的效率让这一过程自动化或是透明化的你无法感知罢了。
    而我们之后做的就是逐步还原这一自动化的过程。

    Spring提供的事务API

    Spring提供了很多关于事务的API。但是最为基本的就是 PlatformTransactionManager 、 TransactionDefintion 和 TransactionStatus 。

    事务管理器——PlatformTransactionManager

    PlatformTransactionManager 是事务管理器的顶层接口。事务的管理是受限于具体的数据源的(例如,JDBC对应的事务管理器就是 DatasourceTransactionManager ),因此 PlatformTransactionManager 只规定了事务的基本操作:创建事务,提交事物和回滚事务。

    public interface PlatformTransactionManager extends TransactionManager {
    
        /**
         * 打开事务
         */
    	TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
    			throws TransactionException;
    
    	/**
    	 * 提交事务
    	 */
    	void commit(TransactionStatus status) throws TransactionException;
    
    	/**
    	 * 回滚事务
    	 */
    	void rollback(TransactionStatus status) throws TransactionException;
    }
    

    同时为了简化事务管理器的实现,Spring提供了一个抽象类 AbstractPlatformTransactionManager ,规定了事务管理器的基本框架,仅将依赖于具体平台的特性作为抽象方法留给子类实现。

    事务状态——TransactionStatus

    事务状态是我对 TransactionStatus 这个类的直译。其实我觉得这个类可以直接当作事务的超集来看(包含了事务对象,并且存储了事务的状态)。 PlatformTransactionManager.getTransaction() 时创建的也正是这个对象。
    这个对象的方法都和事务状态相关:

    public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
    
    	/**
    	 * 是否有Savepoint Savepoint是当事务回滚时需要恢复的状态
    	 */
    	boolean hasSavepoint();
    
    	/**
    	 * flush()操作和底层数据源有关,并非强制所有数据源都要支持
    	 */
    	@Override
    	void flush();
    
    }
    

    此外, TransactionStatus 还从父接口中继承了其他方法,都归总在下方:

    	/**
    	 * 是否是新事务(或是其他事务的一部分)
    	 */
    	boolean isNewTransaction();
    
    	/**
    	 * 设置rollback-only 表示之后需要回滚
    	 */
    	void setRollbackOnly();
    
    	/**
    	 * 是否rollback-only
    	 */
    	boolean isRollbackOnly();
    
    	/**
    	 * 判断该事务已经完成
    	 */
    	boolean isCompleted();
    	
    	
    	/**
    	 * 创建一个Savepoint
    	 */
    	Object createSavepoint() throws TransactionException;
    
    	/**
    	 * 回滚到指定Savepoint
    	 */
    	void rollbackToSavepoint(Object savepoint) throws TransactionException;
    
    	/**
    	 * 释放Savepoint 当事务完成后,事务管理器基本上自动释放该事务所有的savepoint
    	 */
    	void releaseSavepoint(Object savepoint) throws TransactionException;
    
    

    事务属性的定义——TransactionDefinition

    TransactionDefinition 表示一个事务的定义,将根据它规定的特性去开启事务。
    事务的传播等级和隔离级别的常量同样定义在这个接口中。

    	/**
    	 * 返回事务的传播级别
    	 */
    	default int getPropagationBehavior() {
    		return PROPAGATION_REQUIRED;
    	}
    
    	/**
    	 * 返回事务的隔离级别
    	 */
    	default int getIsolationLevel() {
    		return ISOLATION_DEFAULT;
    	}
    
    	/**
    	 * 事务超时时间
    	 */
    	default int getTimeout() {
    		return TIMEOUT_DEFAULT;
    	}
    
    	/**
    	 * 是否为只读事务(只读事务在处理上能有一些优化)
    	 */
    	default boolean isReadOnly() {
    		return false;
    	}
    
    	/**
    	 * 返回事务的名称
    	 */
    	@Nullable
    	default String getName() {
    		return null;
    	}
    
    
    	/**
    	 * 默认的事务配置
    	 */
    	static TransactionDefinition withDefaults() {
    		return StaticTransactionDefinition.INSTANCE;
    	}
    

    编程式使用Spring事务

    有了上述这些API,就已经可以通过编程的方式实现Spring的事务控制了。
    但是Spring官方建议不要直接使用 PlatformTransactionManager 这一偏低层的API来编程,而是使用 TransactionTemplate 和 TransactionCallback 这两个偏向用户层的接口。
    示例代码如下:

            //设置事务的各种属性;可以猜测TransactionTemplate应该是实现了TransactionDefinition
            transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
            transactionTemplate.setTimeout(30000);
            
            //执行事务 将业务逻辑封装在TransactionCallback中
            transactionTemplate.execute(new TransactionCallback<Object>() {
                @Override
                public Object doInTransaction(TransactionStatus transactionStatus) {
                        //....   业务代码
                }
            });
    

    以上就是Spring事务最基本的原理。但是为什么这些过程对我们似乎都不可见呢?那是因为这些过程都 通过AOP的方式被织入 了我们的业务逻辑中。
    所以,像要深入了解Spring事务原理,还需要了解AOP的原理。

    AOP的原理

    AOP的实现机制有两种:Proxy-based和Weaving-based。
    前者是依赖动态代理的方式达到对代理类增强的目的。后者应该是通过字节码增强的方式达到增强的目的。
    在Spring中,一般默认使用前者。之后也仅是针对前者进行分析。

    而Spring声明AOP的方式也有两种,一种是通过声明Aspect,另一种是通过声明Advisor。
    无论是哪种方式,都需要表达清楚你要进行增强的逻辑 (what)和你要增强的地方(where)。即,需要告诉Spring你要增强什么逻辑,并且对哪些Bean/哪些方法增强。

    这里的what和where换成AOP中的概念分别就是对应 Advice 和 Pointcut 。

    因为事务是通过Advisor声明AOP的,因此本文也只针对Advisor的实现展开分析。

    动态代理

    既然是动态代理,那么必然存在被代理类(Target),代理类(Proxy),以及类被代理的过程(因为对用户而言,并不知道类被代理了)。

    被代理的类

    被代理类是最容易知道的,就是那些被Advisor的Pointcut匹配(classFliter匹配或是methodMatches)到的类。

    代理的类

    而代理类是在运行时直接创建的。通常有两种方式:

    • JDK的动态代理
    • CGLIB的动态代理

    二者的区别是JDK动态代理是通过实现接口的方式(代理的对象为接口),因此只能代理接口中的方法。
    而CGLIB动态代理是通过继承的方式,因此可以对对象中的方法进行代理,但是由于是继承关系,无法代理final的类和方法(无法继承),或是private的方法(对子类不可见)。

    创建代理及取代目标类的过程

    创建代理及取代目标类主要是应用了Spring容器在获取Bean时留下的一个拓展点。
    Spring在 getBean 的时候,如果Bean还不存在会分三步去创建Bean:

    • 实例化
    • 填充属性
    • 初始化

    实例化通常是通过反射创建Bean对象的实例,此时得到的 Bean还只是一个空白对象。
    填充属性主要是为这个Bean注入其他的Bean,实现自动装配。
    而初始化则是让用户可以控制Bean的创建过程。
    为Bean创建代理,并取代原有的Bean就是发生在初始化这一步,更具体的是在 BeanPostProcessor.postProcessorAfterInitialization() 中。

    动态代理也有可能在实例化之前直接创建代理,这种情况发生在 InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation() 中,此时的实例化过程不再是我们上文介绍的通过简单反射创建对象。

    在众多的 BeanPostProcessor 中有一类后置处理器就是专门用于创建代理的。例如,我们要介绍的 AbstractAdvisorAutoProxyCreator 。
    看一下 AbstractAutoProxyCreator 创建代理的流程:

    1. 先确认是否已经创建过代理对象( earlyProxyReferences ,避免对代理对象在进行代理)
    2. 如果没有,则考虑是否需要进行代理(通过 wrapIfNecessary )
    3. 如果是特殊的Bean 或者之前判断过不用创建代理的Bean则不创建代理
    4. 否则看是否有匹配的Advise(匹配方式就是上文介绍的通过PointCut或者IntroducationAdvisor可以直接匹配类)
    5. 如果找到了Advisor,说明需要创建代理,进入 createProxy
    6. 首先会创建 ProxyFactory ,这个工厂是用来创建AopProxy的,而 AopProxy 才是用来创建代理对象的。因为底层代理方式有两种(JDK动态代理和CGLIB,对应到 AopProxy 的实现就是 JdkDynamicAopProxy 和 ObjenesisCglibAopProxy ),所以这里使用了一个简单工厂的设计。 ProxyFactory 会设置此次代理的属性,然后根据这些属性选择合适的代理方式,创建代理对象。
    7. 创建的对象会替换掉被代理对象(Target),被保存在 BeanFactory.singletonObjects ,因此当有其他Bean希望注入Target时,其实已经被注入了Proxy。
      以上就是Spring实现动态代理的过程。

    Spring注解式事务

    上文中,我们从编程式事务了解了Spring事务API的基本使用方式,又了解了Spring Advisor的原理。现在,我们在回到Spring注解式事务中,验证下注解式事务是否就是通过以上这些方式隐藏了具体的事务控制逻辑。

    从@EnableTransactionManagement说起

    @EnableTransactionManagement 是开启注解式事务的事务。如果注解式事务真的有玄机,那么 @EnableTransactionManagement 就是我们揭开秘密的突破口。

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(TransactionManagementConfigurationSelector.class)
    public @interface EnableTransactionManagement {
    
    	/**
    	 * 用来表示默认使用JDK Dynamic Proxy还是CGLIB Proxy
    	 */
    	boolean proxyTargetClass() default false;
    
    	/**
    	 * 表示以Proxy-based方式实现AOP还是以Weaving-based方式实现AOP
    	 */
    	AdviceMode mode() default AdviceMode.PROXY;
    
    	/**
    	 * 顺序
    	 */
    	int order() default Ordered.LOWEST_PRECEDENCE;
    
    }
    

    @EnableTransactionManagement 注解看起来并没有特别之处,都是一些属性的配置。但它却通过 @Import 引入了另一个配置 TransactionManagentConfigurationSelector 。

    TransactionManangementConfigurationSelector

    在Spring中, Selector 通常都是用来选择一些Bean,向容器注册BeanDefinition的(严格意义上Selector仅时选择过程,注册的具体过程是在 ConfigurationClasspathPostProcessor 解析时,调用 ConfigurationClassParser 触发)。
    主要的逻辑就是根据代理模式,注册不同的BeanDefinition。
    对Proxy的模式而言,注入的有两个:

    • AutoProxyRegistrar
    • ProxyTransactionManagementConfiguration

    AutoProxyRegistrar

    Registrar同样也是用来向容器注册Bean的,在Proxy的模式下,它会调用 AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); 向容器中注册 InfrastructureAdvisorAutoProxyCreator 。而这个类就是我们上文提到的 AbstractAdvisorAutoProxyCreator 的子类。
    从而,我们完成了我们的第一个条件——AOP代理。

    ProxyTransactionManagementConfiguration

    ProxyTransactionManagementConfiguration 是一个配置类,如果算上其继承的父类,一共是声明了四个类:

    1. TransactionalEventListenerFactory
    2. BeanFactoryTransactionAttributeSourceAdvisor
    3. TransactionAttributeSource
    4. TransactionInterceptor

    后三个类相对比较重要,我们一一分析。

    BeanFactoryTransactionAttributeSourceAdvisor

    从名字看就知道这是一个Advisor,那么它身上应该有Pointcut和Advise。
    其中的Pointcut是 TransactionAttributeSourcePointcut ,主要是一些filter和matches之类的方法,用来匹配被代理类。
    而Adivise就是我们之后要介绍的 TransactionInterceptor 。

    TransactionAttributeSource

    TransactionAttributeSource 只是一个接口,扩展了 TransactionDefinition ,增加了 isCandidateClass() 的方法(可以用来帮助Pointcut匹配)。
    这里使用的具体实现是 AnnotationTransactionAttributeSource 。因为注解式事务候选类(即要被代理的类)是通过 @Transactional 注解标识的,并且所有的事务属性也都来自 @Transactional 注解。

    TransactionInterceptor

    刚才我们说了, TransactionInterceptor 就是我们找的Advise。
    这个类稍微复杂一点,首先根据事务处理相关的逻辑都放在了其父类 TransactionAspectSupport 中。此外,为了适配动态代理的反射调用(两种代理方式),实现了 MethodInterceptor 接口。
    也就是说,反射发起的入口是 MethodInterceptor.invoke() ,而反射逻辑在 TransactionAspectSupport.invokeWithinTransaction() 中。
    我们可以简单看 invokeWithTransaction() 方法中的部分代码:

    	@Nullable
    	protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
    			final InvocationCallback invocation) throws Throwable {
    
    		
    		TransactionAttributeSource tas = getTransactionAttributeSource();
    		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
    		final TransactionManager tm = determineTransactionManager(txAttr);
    
    		//省略部分代码
            
            //获取事物管理器
    		PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
    		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
    
    		if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
    			// 打开事务(内部就是getTransactionStatus的过程)
    			TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
    
    			Object retVal;
    			try {
    				// 执行业务逻辑 invocation.proceedWithInvocation();
    			}
    			catch (Throwable ex) {
    				// 异常回滚
    				completeTransactionAfterThrowing(txInfo, ex);
    				throw ex;
    			}
    			finally {
    				cleanupTransactionInfo(txInfo);
    			}
    
    			//省略部分代码
                
                //提交事物
    			commitTransactionAfterReturning(txInfo);
    			return retVal;
    		}
    
    

    虽然代码比我们之前的复杂,但是其主体结构依然是我们编程式事务的常见那几步。

    行文至此,隐藏在Spring自动事务下的逻辑都分析的差不多了。未避免枯燥,本文并没有对代码一行行的分析,而是希望能够帮助读者把握大概的原理。

    事务失效的常见情况及其背后的原因

    数据库存储引擎不支持

    常见的像mySQL的myISAM存储引擎就不支持事务功能。
    这很好理解,说到底事务是数据库的功能,如果数据库本身就没有这个功能,那上层再怎么五花八门也是没用的。

    未指定RollbackOn,且抛出的异常并非RuntimeException

    这个背后的原因我们可以从 DefualtTransactionAttribute 中来找。

        //可见默认触发回滚的异常是RuntimeException和Error
    	@Override
    	public boolean rollbackOn(Throwable ex) {
    		return (ex instanceof RuntimeException || ex instanceof Error);
    	}
    

    因此阿里巴巴代码规范倡议是显示指定rollbackOn为Exception

    同一个类中调用事务方法

    这是在Proxy模式下才会失效的。
    根据上文我们了解了Spring事务是机遇动态代理的,而当在类当中调用事务的方法时,动态代理是无法生效的,因为此时你拿到的this指向的已经是被代理类(Target),而非代理类(Proxy)。

    非公开方法上的事务

    如果你将 @Transactional 注解应用在一个non-public的方法上(即便是protected和defualt的方法),你会发现事务同样不生效(也是在Proxy模式下)。

    有读者可能会疑问,GCLIB的局限应该是在private或是final的方法上,private方法代理失效还能理解,为什么protected和defualt方法也不行呢?
    其实,non-public方法失效的真正原因不是动态代理的限制,而是Spring有意为之。
    之前我们介绍了 TransactionAttributeSource 会帮助Pointcut匹配类和方法,而在 AnnotationTransactionAttributeSource 中,有一个属性 final boolean publicMethodsOnly 表示是否只允许公有方法。这个属性在默认的构造函数中被设置了true。因此代理只会对public方法生效。
    网上找了下Spring这么设计的目的,有说业务类就是应该基于接口方法调用的,因此总为public。也有说这是为了r让CGLIB和JDK dynamic Proxy保持一致。
    Emm…我觉得Duck不必。
    不过Spring也没有把着属性限制死,如果你真想在non-public的方法上使用自动事务,使点手段修改这个变量即可(例如搞个高优先级的BeanPostProcessor,在通过反射修改这个变量)。但是尽量还是按照规范来吧。

    展开全文
  • MySQL事务实现原理

    千次阅读 2019-05-09 22:54:45
    MySql事务实现原理--参考极客时间专栏《MySQL实战45讲》,博客 1.事务的基本要素(ACID) 原子性(Atomicity):事务内的操作要嘛全部完成,要嘛全部回滚。 一致性(Consistency):事务执行的前后都是合法的数据...

    MySql事务实现原理--参考极客时间专栏《MySQL实战45讲》,博客

     

    1.事务的基本要素(ACID)

    原子性(Atomicity):事务内的操作要嘛全部完成,要嘛全部回滚。

    一致性(Consistency):事务执行的前后都是合法的数据状态,不会违反任何的数据完整性。

    隔离性(Isolation):主要是事务之间的相互的影响,根据隔离有不同的影响效果。

    持久性(Durability):事务一旦提交,就会体现在数据库上,不能回滚。

     

    2.事务并发的问题

    脏读:比如事务A执行的过程中,读到了事务B未提交的内容。

    不可重复度:指一个事务在前后两次查询的结果不一致。

    幻读:幻读是指前后两次相同条件下的查询,后一次查询读到了前一次查询没有的行数据。

    特别注意的是:在不可重复读的隔离级别下,普通查询是不会出现幻读的,因为都是快照读,只有在当前读,也就是select语句后面加for update查询的时候出现,幻读的解决办法,后面会有。

     

    3.事务的隔离级别

    (1)读未提交

    存在问题:脏读,不可重复读,幻读。

    (2)读已提交

    存在问题:不可重复读,幻读.

    实现依赖于一致性视图(MVCC实现):查询只承认在语句启动前就已经提交完成的数据。

    (3)可重复读

    存在问题:幻读,使用当前读解决读已提交隔离级别的不可重复读。

    实现依赖于一致性视图(MVCC实现):查询只承认在事务启动前就已经提交完成的数据;

    幻读的问题:

    1.破坏语义

    select * from test_table where col_1 = 1 for update;

    这条sql语句的本意是要把test_table的col_1 为1的行锁住,不允许别人事务对这些行进行操作。

    假设test_table中id为1的行,col_1的值不为1

    有以下两个语句:

    update test_table set col_1 = 1 where id = 1

    update test_table set col_2 = 12 where id = 1

     对test_table的col_1 为1的行进行修改,违背了将这些行锁住的语句。

    2.数据一致性问题

    主要体现在各个事务提交的时间点不确定,各个事务之间的语句会有影响,从而写到binlog的逻辑语句与表达的语义不一致。

    即使对所有的行加入了锁,也会出现不一致,因为可能要插入心的行。

     

    幻读解决办法:

    引入间隙锁,会锁住区间,跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。间隙锁和行锁合称 next-key lock。

    间隙锁的困扰:有可能引发死锁。主要体现在两个事务互相等待对方的间隙锁。

    解决办法:隔离级别设为读已提交,同时为了解决数据不一致问题,binlog的格式设置为row。

    选择哪一个事务隔离级别,具体问题具体分析。

     

    (4)串行化

    存在问题:没有幻读,脏读,不可重复读的问题,但是由于是串行,性能非常差。

    展开全文
  • Spring事务实现原理及源码分析

    万次阅读 多人点赞 2019-04-04 17:29:05
    Spring事务实现原理及源码分析流程介绍主流程关键对象介绍PlatformTransactionManager获取对应的TransactionManager事务的信息TransactionInfo当前事务状态TransactionStatus传播对象实现原理隔离级别对开始事务的...

    流程介绍

    主流程介绍

    众所周知,Spring事务采用AOP的方式实现,我们从TransactionAspectSupport这个类开始f分析。

    1. 获取事务的属性(@Transactional注解中的配置)
    2. 加载配置中的TransactionManager.
    3. 获取收集事务信息TransactionInfo
    4. 执行目标方法
    5. 出现异常,尝试处理。
    6. 清理事务相关信息
    7. 提交事务
    //1. 获取@Transactional注解的相关参数
    TransactionAttributeSource tas = getTransactionAttributeSource();
    // 2. 获取事务管理器
    	final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
    	final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    	final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
    
    	if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
    		// Standard transaction demarcation with getTransaction and commit/rollback calls.
    		// 3. 获取TransactionInfo,包含了tm和TransactionStatus
    		TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    		Object retVal = null;
    		try {
    			// This is an around advice: Invoke the next interceptor in the chain.
    			// This will normally result in a target object being invoked.
    			// 4.执行目标方法
    			retVal = invocation.proceedWithInvocation();
    		}
    		catch (Throwable ex) {
    		   //5.回滚
    			// target invocation exception
    			completeTransactionAfterThrowing(txInfo, ex);
    			throw ex;
    		}
    		finally {
    		  // 6. 清理当前线程的事务相关信息
    			cleanupTransactionInfo(txInfo);
    		}
    		// 提交事务
    		commitTransactionAfterReturning(txInfo);
    		return retVal;
    	}
    

    doBegin做了什么

    在执行createTransactionIfNecessary获取事务状态时,就准备好了开启事务的所有内容,主要是执行了JDBC的con.setAutoCommit(false)方法。同时处理了很多和数据库连接相关的ThreadLocal变量。

    关键对象介绍

    PlatformTransactionManager

    TransactionManager是做什么的?它保存着当前的数据源连接,对外提供对该数据源的事务提交回滚操作接口,同时实现了事务相关操作的方法。一个数据源DataSource需要一个事务管理器。

    属性:DataSource

    内部核心方法:
    public commit 提交事务
    public rollback 回滚事务
    public getTransaction 获得当前事务状态

    protected doSuspend 挂起事务

    protected doBegin 开始事务,主要是执行了JDBC的con.setAutoCommit(false)方法。同时处理了很多和数据库连接相关的ThreadLocal变量。

    protected doCommit 提交事务
    protected doRollback 回滚事务
    protected doGetTransaction() 获取事务信息
    final getTransaction 获取事务状态

    获取对应的TransactionManager

    @Nullable
    	protected PlatformTransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
    		// Do not attempt to lookup tx manager if no tx attributes are set
    		if (txAttr == null || this.beanFactory == null) {
    			return getTransactionManager();
    		}
    
    		String qualifier = txAttr.getQualifier();
    		//如果指定了Bean则取指定的PlatformTransactionManager类型的Bean
    		if (StringUtils.hasText(qualifier)) {
    			return determineQualifiedTransactionManager(this.beanFactory, qualifier);
    		}
    		//如果指定了Bean的名称,则根据bean名称获取对应的bean
    		else if (StringUtils.hasText(this.transactionManagerBeanName)) {
    			return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
    		}
    		else {
    		// 默认取一个PlatformTransactionManager类型的Bean
    			PlatformTransactionManager defaultTransactionManager = getTransactionManager();
    			if (defaultTransactionManager == null) {
    				defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
    				if (defaultTransactionManager == null) {
    					defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class);
    					this.transactionManagerCache.putIfAbsent(
    							DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
    				}
    			}
    			return defaultTransactionManager;
    		}
    	}
    

    可以看到,如果没有指定TransactionManager参数的话,会使用默认的一个实现,所以当程序中有多个数据库时,事务执行最好是指定事务管理器。

    事务的信息TransactionInfo

    TransactionInfo是对当前事务的描述,其中记录了事务的状态等信息。它记录了和一个事务所有的相关信息。它没有什么方法,只是对事务相关对象的一个组合。最关键的对象是TransactionStatus,它代表当前正在运行的是哪个事务。

    1.核心属性:事务状态TransactionStatus
    2.事务管理器
    3.事务属性
    4.上一个事务信息oldTransactionInfo,REQUIRE_NEW传播级别时,事务挂起后前一个事务的事务信息

    在这里插入图片描述

    当前事务状态TransactionStatus

    通过TransactionManager的getTransaction方法,获取当前事务的状态。
    具体是在AbstractPlatformTransactionManager中实现.
    TransactionStatus被用来做什么:TransactionManager对事务进行提交或回滚时需要用到该对象
    具体方法有:
    在这里插入图片描述
    作用:

    1. 判断当前事务是否是一个新的事务,否则加入到一个已经存在的事务中。事务传播级别REQUIRED和REQUIRE_NEW有用到。
    2. 当前事务是否携带保存点,嵌套事务用到。
      setRollbackOnly,isRollbackOnly,当子事务回滚时,并不真正回滚事务,而是对子事务设置一个标志位。
    3. 事务是否已经完成,已经提交或者已经回滚。

    传播级别

    介绍

    Spring事务的传播级别描述的是多个使用了@Transactional注解的方法互相调用时,Spring对事务的处理。包涵的传播级别有:

    1. REQUIRED, 如果当前线程已经在一个事务中,则加入该事务,否则新建一个事务。
    2. SUPPORT, 如果当前线程已经在一个事务中,则加入该事务,否则不使用事务。
    3. MANDATORY(强制的),如果当前线程已经在一个事务中,则加入该事务,否则抛出异常。
    4. REQUIRES_NEW,无论如何都会创建一个新的事务,如果当前线程已经在一个事务中,则挂起当前事务,创建一个新的事务。
    5. NOT_SUPPORTED,如果当前线程在一个事务中,则挂起事务。
    6. NEVER,如果当前线程在一个事务中则抛出异常。
    7. NESTED, 执行一个嵌套事务,有点像REQUIRED,但是有些区别,在Mysql中是采用SAVEPOINT来实现的。

    挂起事务,指的是将当前事务的属性如事务名称,隔离级别等属性保存在一个变量中,同时将当前线程中所有和事务相关的ThreadLocal变量设置为从未开启过线程一样。Spring维护着一个当前线程的事务状态,用来判断当前线程是否在一个事务中以及在一个什么样的事务中,挂起事务后,当前线程的事务状态就好像没有事务。

    现象描述

    @Service
    public class MyTsA {
        @Resource
        private UserServiceImpl userService;
        @Resource
        private MyTsB myTsB;
    
        @Transactional(rollbackFor = Exception.class)
        public void testNewRollback(){
            User user = new User();
            user.setId(1);
            user.setName("张三");
            userService.save(user);
            myTsB.save();
        }
    }
        
    @Service
    public class MyTsB {
        @Resource
        private UserServiceImpl userService;
        @Transactional(rollbackFor = RuntimeException.class, propagation = ???)
        public void save(){
            User user = new User();
            user.setId(2);
            user.setName(“李四”)
            userService.save(user);
    }
    
    

    如上面所示有两个事务A和事务B,事务A方法中调用了事务B方法,区分两种回滚情况。
    A提交,B回滚。
    A回滚,B提交。

    对于不同的传播级别:
    1.当传播级别为为REQUIRED时,第一种情况A和B都会回滚。
    2.。当传播级别为RQUIRED_NEW时,第一种情况A可以成功提交,B回滚,第二种情况A回滚,B可以正确提交,二者互不影响。(在A事务捕获B事务异常的情况下,保证A事务提交)
    3.当传播级别为NESTED时,第一种情况A可以正常提交,B回滚,第二种情况二者都会回滚。

    原理

    我们从两个角度看他们的实现原理,一个是刚进入事务时,针对不同的传播级别,它们的行为有什么区别。另一个角度是当事务提交或者回滚时,传播级别对事务行为的影响。

    首先在尝试获取事务信息时,如果当前已经存在一个事务,则会根据传播级别做一些处理:

    隔离级别对开始事务的影响(获取TransactionStatus)

    @Override
    public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
      // 从当前的transactionManager获取DataSource对象
      // 然后以该DataSource对象为Key,
      // 去一个ThreadLocal变量中的map中获取该DataSource的连接
      // 然后设置到DataSourceTransactionObject中返回。
    	Object transaction = doGetTransaction();
    	// Cache debug flag to avoid repeated checks.
    	boolean debugEnabled = logger.isDebugEnabled();
    	if (definition == null) {
    		// Use defaults if no transaction definition given.
    		definition = new DefaultTransactionDefinition();
    	}
    // 如果当前线程已经在一个事务中了,则需要根据事务的传播级别
    //来决定如何处理并获取事务状态对象
    	if (isExistingTransaction(transaction)) {
    		// Existing transaction found -> check propagation behavior to find out how to behave.
    		return handleExistingTransaction(definition, transaction, debugEnabled);
    	}
    	// Check definition settings for new transaction.
    	if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
    		throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
    	}
    	// No existing transaction found -> check propagation behavior to find out how to proceed.
    	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
    		throw new IllegalTransactionStateException(
    				"No existing transaction found for transaction marked with propagation 'mandatory'");
    	}
    	else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
    			definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
    			definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
    		SuspendedResourcesHolder suspendedResources = suspend(null);
    		if (debugEnabled) {
    			logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
    		}
    		try {
    		  //如果当前不在一个事务中,则执行事务的准备操作
    			boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    			// 构造事务状态对象,注意这里第三个参数为true,代表是一个新事务
    			DefaultTransactionStatus status = newTransactionStatus(
    					definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
    			//执行begin操作,核心操作是设置隔离级别,执行   conn.setAutoCommit(false); 同时将数据连接绑定到当前线程
    			doBegin(transaction, definition);
    			// 针对事务相关属性如隔离级别,是否在事务中,设置绑定到当前线程
    			prepareSynchronization(status, definition);
    			return status;
    		}
    		catch (RuntimeException | Error ex) {
    			resume(null, suspendedResources);
    			throw ex;
    		}
    	}
    	else {
    		// Create "empty" transaction: no actual transaction, but potentially synchronization.
    		if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
    			logger.warn("Custom isolation level specified but no actual transaction initiated; " +
    					"isolation level will effectively be ignored: " + definition);
    		}
    		boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
    		return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
    	}
    }
    

    可以看到,如果当前不存在事务时,创建一个新的TransactionStatus对象,否则进入到handleExistingTransaction。下面来看这个方法

    1.如果是NEVER传播级别则抛出异常
    2. 如果是不支持,则挂起事务,将当前事务对象设置为null,newTransaction设置为false,把线程的相关Threadlocal变量改的就像当前不存在事务一样
    3. 如果是required_NEW的话,则挂起当前事务,同时创建一个新的事务,执行doBegin操作,设置newTransaction为true
    4.如果是嵌入事务,则创建一个SAVEPOINT
    5.对于REQUIRED传播级别会把newTransaction标志位设置为false

    /**
     * Create a TransactionStatus for an existing transaction.
     */
    private TransactionStatus handleExistingTransaction(
    		TransactionDefinition definition, Object transaction, boolean debugEnabled)
    		throws TransactionException {
        //如果是NEVER传播级别则抛出异常
    	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
    		throw new IllegalTransactionStateException(
    				"Existing transaction found for transaction marked with propagation 'never'");
    	}
        // 如果是不支持,则挂起事务
    	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
    		if (debugEnabled) {
    			logger.debug("Suspending current transaction");
    		}
    		Object suspendedResources = suspend(transaction);
    		boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
    		//挂起事务同时将当前事务设置为null,newTransaction设置为false,把线程的相关Threadlocal变量改的就像当前不存在事务一样
    		return prepareTransactionStatus(
    				definition, null, false, newSynchronization, debugEnabled, suspendedResources);
    	}
        //如果是required_NEW的话,则挂起当前事务,同时创建一个新的事务,执行doBegin操作
    	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
    		if (debugEnabled) {
    			logger.debug("Suspending current transaction, creating new transaction with name [" +
    					definition.getName() + "]");
    		}
    		SuspendedResourcesHolder suspendedResources = suspend(transaction);
    		try {
    			boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    			DefaultTransactionStatus status = newTransactionStatus(
    					definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
    			doBegin(transaction, definition);
    			prepareSynchronization(status, definition);
    			return status;
    		}
    		catch (RuntimeException | Error beginEx) {
    			resumeAfterBeginException(transaction, suspendedResources, beginEx);
    			throw beginEx;
    		}
    	}
        // 如果是嵌入事务,则创建一个SAVEPOINT
    	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
    		if (!isNestedTransactionAllowed()) {
    			throw new NestedTransactionNotSupportedException(
    					"Transaction manager does not allow nested transactions by default - " +
    					"specify 'nestedTransactionAllowed' property with value 'true'");
    		}
    		if (debugEnabled) {
    			logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
    		}
    		if (useSavepointForNestedTransaction()) {
    			// Create savepoint within existing Spring-managed transaction,
    			// through the SavepointManager API implemented by TransactionStatus.
    			// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
    			DefaultTransactionStatus status =
    					prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
    			status.createAndHoldSavepoint();
    			return status;
    		}
    		else {
    			// Nested transaction through nested begin and commit/rollback calls.
    			// Usually only for JTA: Spring synchronization might get activated here
    			// in case of a pre-existing JTA transaction.
    			boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    			DefaultTransactionStatus status = newTransactionStatus(
    					definition, transaction, true, newSynchronization, debugEnabled, null);
    			doBegin(transaction, definition);
    			prepareSynchronization(status, definition);
    			return status;
    		}
    	}
    
    	// Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
    	if (debugEnabled) {
    		logger.debug("Participating in existing transaction");
    	}
    //这里判断是否需要对已经存在的事务进行校验,这个可以通过AbstractPlatformTransactionManager.setValidateExistingTransaction(boolean)来设置,设置为true后需要校验当前事务的隔离级别和已经存在的事务的隔离级别是否一致 
    	if (isValidateExistingTransaction()) {
    		if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
    			Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
    			if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
    				Constants isoConstants = DefaultTransactionDefinition.constants;
    				throw new IllegalTransactionStateException("Participating transaction with definition [" +
    						definition + "] specifies isolation level which is incompatible with existing transaction: " +
    						(currentIsolationLevel != null ?
    								isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
    								"(unknown)"));
    			}
    		}
    		if (!definition.isReadOnly()) {
    			if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
    				throw new IllegalTransactionStateException("Participating transaction with definition [" +
    						definition + "] is not marked as read-only but existing transaction is");
    			}
    		}
    	}
    	// 如果不设置是否校验已经存在的事务,则对于REQUIRED传播级别会走到这里来,这里把newTransaction标志位设置为false,
    	// 这里用的definition是当前事务的相关属性,所以隔离级别等依然是当前事务的(子事务),而不是已经存在的事务的隔离级别(父事务)
    	boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    	return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
    }
    

    隔离级别对回滚事务的影响

    1. 如果newTransaction设置为ture,则真正执行回滚。
    2. 如果有保存点,则回滚到保存点。
    3. 否则不会真正回滚,而是设置回滚标志位。
    private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
    		try {
    			boolean unexpectedRollback = unexpected;
    			try {
    				triggerBeforeCompletion(status);
    				// 如果有保存点
    				if (status.hasSavepoint()) {
    					if (status.isDebug()) {
    						logger.debug("Rolling back transaction to savepoint");
    					}
    					status.rollbackToHeldSavepoint();
    				}
    				// 如果是新的事务,当传播级别为RUQUIRED_NEW时会走到这里来
    				else if (status.isNewTransaction()) {
    					if (status.isDebug()) {
    						logger.debug("Initiating transaction rollback");
    					}
    					doRollback(status);
    				}
    				else {
    				    // 加入到事务中,设置回滚状态,适用于REQUIRED传播级别
    				    // 并不会真的回滚,而是设置回滚标志位
    					// Participating in larger transaction
    					if (status.hasTransaction()) {
    						if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
    							if (status.isDebug()) {
    								logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
    							}
    							doSetRollbackOnly(status);
    						}
    						else {
    							if (status.isDebug()) {
    								logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
    							}
    						}
    					}
    					else {
    						logger.debug("Should roll back transaction but cannot - no transaction available");
    					}
    					// Unexpected rollback only matters here if we're asked to fail early
    					if (!isFailEarlyOnGlobalRollbackOnly()) {
    						unexpectedRollback = false;
    					}
    				}
    			}
    			catch (RuntimeException | Error ex) {
    				triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
    				throw ex;
    			}
    
    			triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
    
    			// Raise UnexpectedRollbackException if we had a global rollback-only marker
    			if (unexpectedRollback) {
    				throw new UnexpectedRollbackException(
    						"Transaction rolled back because it has been marked as rollback-only");
    			}
    		}
    		finally {
    			cleanupAfterCompletion(status);
    		}
    	}
    

    隔离级别对提交事务的影响

    1. 就算事务方法没有抛出异常,走到了commit方法中,但是依然有可能回滚事务。
    2. 对于REQUIRED传播级别,即使父事务中没有抛出异常,但是子事务中已经设置了回滚标志,那么父事务依然会回滚
    3. 只有newTransaction标志位为true的事务才会真正执行commit操作。
    @Override
    public final void commit(TransactionStatus status) throws TransactionException {
    	if (status.isCompleted()) {
    		throw new IllegalTransactionStateException(
    				"Transaction is already completed - do not call commit or rollback more than once per transaction");
    	}
    
    	DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
    	if (defStatus.isLocalRollbackOnly()) {
    		if (defStatus.isDebug()) {
    			logger.debug("Transactional code has requested rollback");
    		}
    		processRollback(defStatus, false);
    		return;
    	}
     // 对于REQUIRED传播级别,即使父事务中没有抛出异常,但是子事务中已经设置了回滚标志,那么父事务依然会回滚
    	if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
    		if (defStatus.isDebug()) {
    			logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
    		}
    		processRollback(defStatus, true);
    		return;
    	}
    
    	processCommit(defStatus);
    }
    

    在这里插入图片描述

    为什么我的事务不生效

    1.如果不是Innodb存储引擎,MyISAM不支持事务。
    2.没有指定rollbackFor参数。
    3. 没有指定transactionManager参数,默认的transactionManager并不是我期望的,以及一个事务中涉及到了多个数据库。
    4. 如果AOP使用了JDK动态代理,对象内部方法互相调用不会被Spring的AOP拦截,@Transactional注解无效。
    5. 如果AOP使用了CGLIB代理,事务方法或者类不是public,无法被外部包访问到,或者是final无法继承,@transactional注解无效。

    展开全文
  • MySQL事务实现原理.pdf

    2021-03-26 16:40:48
    redo log与undo log介绍 mysql锁技术以及MVCC基础 事务实现原理
  • 事务的基本概念及Mysql事务实现原理

    千次阅读 多人点赞 2020-08-01 14:46:19
    我重新整理了大纲,思考了很久,决定单独将MySQL的事务实现原理跟Spring中的事务示例分为两篇文章,因为二者毕竟没有什么实际关系,实际上如果你对MySQL的事务原理不感兴趣也可以直接跳过本文,等待接下来两篇应用及...

    Spring事务专题(三)事务的基本概念,Mysql事务处理原理

    前言

    本专题大纲:

    专栏大纲

    我重新整理了大纲,思考了很久,决定单独将MySQL的事务实现原理跟Spring中的事务示例分为两篇文章,因为二者毕竟没有什么实际关系,实际上如果你对MySQL的事务原理不感兴趣也可以直接跳过本文,等待接下来两篇应用及源码分析,不过我觉得知识的学习应该慢慢行成一个体系,为了建立一个完善的体系应该要对数据库本身事务的实现有一定认知才行。

    本文为Spring事务专题第三篇,在前两篇文章中我们已经对Spring中的数据访问有了一定的了解,那么从本文开始我们正式接触事务,在分析Spring中事务的实现之前我们应该要对事务本身有一定的了解,同时也要对数据库层面的事务如何实现有一定了解。话不多说,我们开始正文

    本文大纲:

    MYSQL事务大纲

    初识事务

    为什么需要事务?

    这里又要掏出那个烂大街的银行转账案例了,以A、B两个账户的转账为例,假设现在要从A账户向B账户中转入1000员,当进行转账时,需要先从银行账户A中取出钱,然后再存入银行账户B中,SQL样本如下:

    // 第一步:A账户余额减少减少1000  
    update balance set money = money -500 where name= ‘A’;
    // 第二步:B账户余额增加1000  
    update balance set money = money +500 where name= ‘B’;
    

    如果在完成了第1步的时候突然宕机了,A的钱减少了而B的钱没有增加,那A岂不是白白丢了1000元,这时候就需要用到我们的事务了,开启事务后SQL样本如下:

    // 第一步:开始事务
    start transaction;
    // 第二步:A账户余额减少减少1000  
    update balance set money = money -500 where name= ‘A’;
    // 第三步:B账户余额增加1000  
    update balance set money = money +500 where name= ‘B’;
    // 第四步:提交事务
    commit;
    

    什么是事务

    事务(Transaction)是访问和更新数据库的程序执行单元;事务中可能包含一个或多个sql语句,这些语句要么都执行成功,要么全部执行失败。

    事务的四大特性(ACID)

    • 原子性(Atomicity,或称不可分割性)

    一个事务必须被视为一个不可分割的最小工作单元,整个事务中所有的操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性

    • 一致性(Consistency)

    数据库总是从一个一致性的状态转换到另外一个一致性的状态,在事务开始之前和之后,数据库的完整性约束没有被破坏。在前面的例子中,事务结束前后A、B账户总额始终保持不变

    • 隔离性(Isolation)

    隔离性是指,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。严格的隔离性,对应了事务隔离级别中的Serializable (可串行化),但实际应用中出于性能方面的考虑很少会使用可串行化。

    • 持久性(Durability)

    持久性是指事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

    事务的隔离级别

    在前文中我们介绍了隔离性,但实际上隔离性比想象的要复杂的多。在SQL标准中定义了四种隔离级别,每一种隔离级别都规定了一个事务所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的,较低级别的隔离通常可以执行跟高的并发,系统的开销也更低

    未提交读(READ UNCOMMITTED)

    在这个隔离级别下,事务的修改即使没有提交,对其他事务也是可见的。事务可以读取未提交的数据,这也被称之为脏读。这个级别会带来很多问题,从性能上来说,READ UNCOMMITTED不会比其他的级别好太多,但是却会带来很多问题,除非真的有非常必要的理由,在实际应用中一般很少使用。

    提交读(REDA COMMITED)

    大多数数据系统的默认隔离级别都是REDA COMMITED(MySql不是),REDA COMMITED满足前面提到的隔离性的简单定义:一个事务开始时,只能看到已经提交的事务所做的修改。换句话说,一个事物从开始直到提交前,所做的修改对其他事务不可见。这个级别有时候也叫做不可重复读,因为执行两次相同的查询可能会得到不同的结果。

    可重复读(REPEATABLE READ)

    REPEATABLE READ解决了脏读以及不可重复度的问题。该级别保证了同一个事务多次读取同样记录的结果是一致的。但是理论上,可重复度还是无法解决另外一个幻读的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,就会产生幻行。

    不可重复读跟幻读的区别在于,前者是数据发生了变化,后者是数据的行数发生了变化

    可串行化(SERIALIZABLE)

    SERIALIZABLE是最高的隔离级别,它通过强制事务串行执行,避免前面说的幻读。简单来说SERIALIZABLE会在读取的每一行数据上都加锁,所以可能会导致大量的超时和锁争用的问题。实际应用中也很少使用这个隔离级别,只有在非常需要确保数据一致性而且可以接受没有并发的情况下,才考虑此级别。

    保存点

    我们可以在事务执行的过程中定义保存点,在回滚时直接指定回滚到指定的保存点而不是事务开始之初,有点像我们玩游戏的时候可以存档而不是每次都要重新再来

    定义保存点的语法如下:

    SAVEPOINT 保存点名称;
    

    当我们想回滚到某个保存点时,可以使用下边这个语句(下边语句中的单词WORKSAVEPOINT是可有可无的):

    ROLLBACK [WORK] TO [SAVEPOINT] 保存点名称;
    

    MySQL中的事务跟原理

    MySQL中的事务

    1. MySQL中不是所有的存储引擎都支持事务,例如MyISAM就不支持事务,实际上支持事务的只有InnoDBNDB Cluster本文关于事务的分析都是基于InnoDB

    2. MySQL默认采用的是自动提交的方式,也就是说如果不是显示的开始一个事务,则系统会自动向数据库提交结果。在当前连接中,还可以通过设置AUTOCONNIT变量来启用或者禁用自动提交模式。

    • 开启自动提交功能
    SET AUTOCOMMIT = 1;
    

    MySQL中默认情况下的自动提交功能是已经开启的。

    • 关闭自动提交功能。
    SET AUTOCOMMIT = 0;
    

    关闭自动提交功能后,只用当执行COMMIT命令后,MySQL才将数据表中的资料提交到数据库中。如果执行ROLLBACK命令,数据将会被回滚。如果不提交事务,而终止MySQL会话,数据库将会自动执行回滚操作。

    1. MySQL的默认隔离级别是可重复读(REPEATABLE READ)

    事务的实现原理

    我们要探究MySQL中事务的实现原理,实际上就是要弄明天它的ACID特性是如何实现的,在这里有必要先说明的是,ACID中的一致性是事务的最终目标,前面提到的原子性、持久性和隔离性,都是为了保证数据库状态的一致性。所以我们要分析的就是MySQL的原子性、持久性和隔离性的实现原理,在分析事务的实现原理之前我们需要补充一些InnoDB的相关知识

    1. InnoDB是一个将表中的数据存储到磁盘上的存储引擎,所以即使关机后重启我们的数据还是存在的。而真正处理数据的过程是发生在内存中的所以需要把磁盘中的数据加载到内存中,如果是处理写入或修改请求的话,还需要把内存中的内容刷新到磁盘上。而我们知道读写磁盘的速度非常慢,和内存读写差了几个数量级,所以当我们想从表中获取某些记录时,InnoDB存储引擎需要一条一条的把记录从磁盘上读出来么?不,那样会慢死,InnoDB采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16 KB。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。

    2. 我们还需要对MySQL中的日志有一定了解。MySQL的日志有很多种,如二进制日志(bin log)、错误日志、查询日志、慢查询日志等,此外InnoDB存储引擎还提供了两种事务日志:redo log(重做日志)和undo log(回滚日志)。其中redo log用于保证事务持久性;undo log则是事务原子性和隔离性实现的基础。

    3. InnoDB作为MySQL的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘IO,效率会很低。为此,InnoDB提供了缓存(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。

    4. InnoDB存储引擎文件主要可以分为两类,表空间文件及重做日志文件(redo log file),表空间文件又可以细分为两类,共享表空间跟独立表空间。undo log位于共享表空间中的undo段中,每个表空间都被划分成了若干个页面,凡是页面的读写都在buffer pool中进行,这意味着undo log也需要先写入到buffer pool,所以undo log的生成也需要持久化,也就是说undo log的生成需要记录对应的redo log。(注意:不是所有的undo log的生成都会产生对应的redo log,对于操作临时表生成的undo log并不会生成对应的undo log,因为修改临时表而产生的undo日志只需要在系统运行过程中有效,如果系统奔溃了,那么在重启时也不需要恢复这些undo日志所在的页面,所以在写针对临时表的Undo页面时,并不需要记录相应的redo日志。)

    持久性实现原理

    ​ 通过前面的补充知识我们知道InnoDB引入了Buffer Pool来优化读写的性能,但是虽然Buffer Pool优化了性能,但同时也带来了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证

    ​ 基于此,redo log就诞生了,redo log是物理日志,记录的是数据库中数据库中物理页的情况,redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。在概念上,innodb通过force log at commit机制实现事务的持久性,即在事务提交的时候,必须先将该事务的所有事务日志写入到磁盘上的redo log file和undo log file中进行持久化。

    ​ 看到这里可能有的小伙伴又会有疑问了,既然redo log也需要在事务提交时将日志写入磁盘,为什么它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快呢?主要有以下两方面的原因:

    (1)刷脏是随机IO,因为每次修改的数据位置随机,但写redo log是追加操作,属于顺序IO。

    (2)刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效IO大大减少。

    这里我以文章开头的例子进行说明redo log为何能保证持久性:

    // 第一步:开始事务
    start transaction;
    // 第二步:A账户余额减少减少1000  
    update balance set money = money -500 where name= ‘A’;
    // 第三步:B账户余额增加1000  
    update balance set money = money +500 where name= ‘B’;
    // 第四步:提交事务
    commit;
    

    redo

    这里需要对redo log的刷盘补充一点内容:

    MySQL支持用户自定义在commit时如何将log buffer中的日志刷log file中。这种控制通过变量 innodb_flush_log_at_trx_commit 的值来决定。该变量有3种值:0、1、2,默认为1。但注意,这个变量只是控制commit动作是否刷新log buffer到磁盘。

    • 当设置为1的时候,事务每次提交都会将log buffer中的日志写入os buffer并调用fsync()函数刷到log file on disk中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能较差。
    • 当设置为0的时候,事务提交时不会将log buffer中日志写入到os buffer(内核缓冲区),而是每秒写入os buffer并调用fsync()写入到log file on disk中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据。
    • 当设置为2的时候,每次提交都仅写入到os buffer,然后是每秒调用fsync()将os buffer中的日志写入到log file on disk。

    可以看到设置为0或者2时,都有可能丢失1s的数据

    原子性实现原理

    前面提到了,所谓原子性就是指整个事务是一个不可分隔的整体,组成事务的一组SQL要么全部成功,要么全部失败,要达到这个目的就意味着当某一个SQL执行失败时,我们要能够撤销掉其它SQL的执行结果,在MySQL中这是依赖undo log(回滚日志)来实现。

    undo log属于逻辑日志前面提到的redo log属于物理日志,记录的是数据页的情况),我们可以这么认为,当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。

    但执行发生异常时,会根据undo log中的记录进行回滚。undo log主要分为两种

    1. insert undo log
    2. update undo log

    insert undo log是指在insert 操作中产生的undo log,因为insert操作的记录,只对事务本身可见,对其他事务不可见。故该undo log可以在事务提交后直接删除,不需要进行purge操作。

    而update undo log记录的是对delete 和update操作产生的undo log,该undo log可能需要提供MVCC机制,因此不能再事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除。

    补充:purge线程两个主要作用是:清理undo页和清除page里面带有Delete_Bit标识的数据行。在InnoDB中,事务中的Delete操作实际上并不是真正的删除掉数据行,而是一种Delete Mark操作,在记录上标识Delete_Bit,而不删除记录。是一种"假删除",只是做了个标记,真正的删除工作需要后台purge线程去完成。

    这里我们就来看看insert undo log的结构,如下:

    insert undo

    在上图中,undo type记录的是undo log的类型,对于insert undo log,该值始终为11(TRX_UNDO_INSERT_REC),undo no在一个事务中是从0开始递增的,也就是说只要事务没提交,每生成一条undo日志,那么该条日志的undo no就增1。table id记录undo log所对应的表对象。如果记录中的主键只包含一个列,那么在类型为TRX_UNDO_INSERT_RECundo日志中只需要把该列占用的存储空间大小和真实值记录下来,如果记录中的主键包含多个列(复合主键),那么每个列占用的存储空间大小和对应的真实值都需要记录下来(图中的len就代表列占用的存储空间大小,value就代表列的真实值),在回滚时只需要根据主键找到对应的列然后删除即可。end of record记录了下一条undo log在页面中开始的地址,start of record记录了本条undo log在页面中开始的地址。

    对undo log有一定了解后,我们再回头看看文章开头的例子,分析下为什么undo log能保证原子性

    // 第一步:开始事务
    start transaction;
    // 第二步:A账户余额减少减少1000  
    update balance set money = money -500 where name= ‘A’;
    // 第三步:B账户余额增加1000  
    update balance set money = money +500 where name= ‘B’;
    // 第四步:提交事务
    commit;
    

    undo redo

    考虑到排版,这里我只画了一条语句的流程图,第二条也是一样的,每次更新或者插入前,先记录undo,再修改内存中数据,再记录redo。

    隔离性实现原理

    我们知道,一个事务中的读操作是不会影响到另外一个事务的,所以在讨论隔离性我们主要分为两种情况

    1. 一个事务中的写操作,对另外一个事务中写操作的影响
    2. 一个事务中的写操作,对另外一个事务中读操作的影响

    写操作之间的隔离是通过锁来实现的,MySQL中的锁机制要详细来讲是很复杂的,要讲明白整个锁需要从索引开始介绍,限于笔者能力及文章篇幅,本文只对MySQL中的锁机制做一个简单的介绍

    MySQL中的锁机制(InnoDB)

    读锁跟写锁
    1. 读锁又称为共享锁`,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。

    2. 写锁又称为排他锁`,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

    行锁跟表锁
    1. 表锁在操作数据时会锁定整张表,并发性能较差;

    2. 行锁则只锁定需要操作的数据,并发性能好。

    3. 但是由于加锁本身需要消耗资源(获得锁、检查锁、释放锁等都需要消耗资源),因此在锁定数据较多情况下使用表锁可以节省大量资源。MySQL中不同的存储引擎支持的锁是不一样的,例如MyIsam只支持表锁,而InnoDB同时支持表锁和行锁,且出于性能考虑,绝大多数情况下使用的都是行锁。

    意向锁
    1. 意向锁分为两种,意向读锁(IS)跟意向写锁(IX)

    2. 意向锁是表级别的锁

    3. 为什么需要意向锁呢?思考一个问题:如果我们想对某个表加一个表锁,那么在加锁之前我们需要去检查表中的每一行记录是否已经被单独加了行锁,这样的话岂不是意味着我们需要去遍历表中所有的记录依次进行检查,遍历是不可能的,这辈子都不可能遍历的,基于效率的考虑,我们可以在每次给行记录加锁时先给当前表加一个意向锁,如果我们要对行加读锁(S)的话,那么就先给表加一个意向读锁(IS),如果要对行加写锁(X)的话,那么先给表加一个意向写锁(IX),这样当我们需要给整个表加锁的时候就可以通过先判断表上是否已经存在了意向锁来决定是否可以上锁了,避免遍历,提高了效率。

    4. 意向锁跟普通的读锁写锁间的兼容性如下:

    ISIXSX
    IS兼容兼容兼容不兼容
    IX兼容兼容不兼容不兼容
    S兼容不兼容兼容不兼容
    X不兼容不兼容不兼容不兼容

    注:IS(意向读锁/意向共享锁), IX(意向写锁/意向排他锁), S(读锁/共享锁),X(写锁/排他锁)

    从上图中可以看出,意向锁之间都是兼容的,这是因为意向锁的作用仅仅是来快速判断是否可以直接上表锁。


    接下来介绍的这几种锁都属于行锁,为了更好的理解这几种锁,我们先创建一个表

    CREATE TABLE `user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(10) NOT NULL,
      PRIMARY KEY (`id`),
    ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
    

    其中id为主键,没有建其余的索引,插入如下数据

    INSERT INTO `test`.`user`(`id`, `name`) VALUES (1, 'a张大胆');
    INSERT INTO `test`.`user`(`id`, `name`) VALUES (3, 'b王翠花');
    INSERT INTO `test`.`user`(`id`, `name`) VALUES (6, 'c范统');
    INSERT INTO `test`.`user`(`id`, `name`) VALUES (8, 'd朱逸群');
    INSERT INTO `test`.`user`(`id`, `name`) VALUES (15, 'e董格求');
    
    Record Lock(记录锁)
    1. 锁定单条记录
    2. 也分为S锁跟X锁

    如果我们对id为3的记录添加一个行锁,对应如下(图中每一列代表数据库中的一行记录):

    行锁

    Gap Lock(间隙锁)
    1. 锁定一个范围,但是不包含记录本身
    2. 间隙锁的主要作用在于防止幻读的发生,虽然也有S锁跟X锁的区分,但是它们的作用都是相同的,而且如果你对一条记录加了间隙锁(不论是共享间隙锁还是独占间隙锁),并不会限制其他事务对这条记录加记录锁或者继续加间隙锁,再强调一遍,间隙锁的作用仅仅是为了防止幻读的发生。

    假设我们要对id为6的记录添加间隙锁,那么此时锁定的区域如下所示

    其中虚线框代表的是要锁定的间隙,其实就是当前需要加间隙锁的记录跟上一条记录之间的范围,但是间隙锁不会锁定当前记录,如图所示,id=6的记录并没有被加锁。(图中虚线框表锁间隙,没有插入真实的记录)
    在这里插入图片描述

    Next-Key Lock(Gap Lock+Record Lock)

    假设我们要对id为6的记录添加Next-Key Lock,那么此时锁定的区域如下所示

    next key lock

    跟间隙锁最大的区别在于,Next-Key Lock除了锁定间隙之外还要锁定当前记录

    通过锁实现了写、写操作之间的隔离性,实际上我们也可以通过加锁来实现读、写之间的隔离性,但是这样带来一个问题,读、写需要串行执行这样会大大降低效率,所以MySQL中实现读写之间的隔离性是通过MVCC+锁来实现的,对于读采用快照都,对于写使用加锁!

    MVCC(多版本并发控制)

    版本链

    在介绍MVCC之前我们需要对MySQL中的行记录格式有一定了解,其实除了我们在数据库中定义的列之外,每一行中还包含了几个隐藏列,分别是

    • row_id:行记录的唯一标志
    • transaction_id:事务ID
    • roll_pointer:回滚指针

    row_id是行记录的唯一标志,这一列不是必须的。

    MySQL会优先使用用户自定义主键作为主键,如果用户没有定义主键,则选取一个Unique键作为主键,如果表中连Unique键都没有定义的话,则InnoDB会为表默认添加一个名为row_id的隐藏列作为主键。也就是说只有在表中既没有定义主键,也没有申明唯一索引的情况MySQL才会添加这个隐藏列。

    transaction_id代表的是事务的ID。当一个事务对某个表执行了增、删、改操作,那么InnoDB存储引擎就会给它分配一个独一无二的事务id,分配方式如下:

    • 对于只读事务来说,只有在它第一次对某个用户创建的临时表执行增、删、改操作时才会为这个事务分配一个事务id,否则的话是不分配事务id的。

    • 对于读写事务来说,只有在它第一次对某个表(包括用户创建的临时表)执行增、删、改操作时才会为这个事务分配一个事务id,否则的话也是不分配事务id的。

      有的时候虽然我们开启了一个读写事务,但是在这个事务中全是查询语句,并没有执行增、删、改的语句,那也就意味着这个事务并不会被分配一个事务id

    roll_pointer表示回滚指针,指向该记录对应的undo log。前文已经提到过了,undo log记录了对应记录在修改前的状态,通过roll_pointer我们就可以找到对应的undo log,然后根据undo log进行回滚。

    在之前介绍undo log的时候我们只介绍了insert undo log的数据格式,实际上除了insert undo log还有update undo log,而update undo log中也包含roll_pointertransaction_idupdate undo log中的roll_pointer指针其实就是保存的被更新的记录中的roll_pointer指针

    除了这些隐藏列以外,实际上每条记录的记录头信息中还会存储一个标志位,标志该记录是否删除。

    我们以实际的例子来说明上面三个隐藏列的作用,还是以之前的表为例,现在对其执行如下SQL:

    # 开启事务
    START TRANSACTION;
    # 插入一条数据
    INSERT INTO `test`.`user`(`id`, `name`) VALUES (16, 'e杜子騰');
    # 更新插入的数据
    UPDATE `test`.`user` SET name = "史珍香" WHERE id = 16;
    # 删除数据
    DELETE from  `test`.`user` WHERE id = 16;
    

    我们通过画图来看看上面这段SQL在执行的过程中都做了什么

    SQL执行流程图

    从上图中我们可以看到,每对记录进行一次增、删、改时,都会生成一条对应的undo log,并且被修改后的记录中的roll pointer指针指向了这条undo log,同时如果不是新增操作,那么生成的undo log中也会保存一个roll pointer,其值是从被修改的数据中复制过来了,在我们上边的例子中update undo log的roll pointer就复制了insert进去的数据中的roll pointer指针的值。

    另外我们会发现,根据当前记录中的roll pointer指针,我们可以找到一个有undo log组成的链表,这个undo log链表其实就是这条记录的版本链

    ReadView(快照)

    对于使用READ UNCOMMITTED隔离级别的事务来说,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了;

    对于使用SERIALIZABLE隔离级别的事务来说,MySQL规定使用加锁的方式来访问记录;

    对于使用READ COMMITTEDREPEATABLE READ隔离级别的事务来说,都必须保证读到已经提交了的事务修改过的记录,也就是说假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,核心问题就是:需要判断一下版本链中的哪个版本是当前事务可见的

    为了解决这个问题,MySQL提出了一个ReadView(快照)的概念,在Select操作前会为当前事务生成一个快照,然后根据快照中记录的信息来判断当前记录是否对事务是可见的,如果不可见那么沿着版本链继续往上找,直至找到一个可见的记录。

    ReadView(快照)中包含了下面几个关键属性:

    • m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。

    • min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值。

    • max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。

      小贴士: 注意max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4。

    • creator_trx_id:表示生成该ReadView的事务的事务id

      小贴士: 我们前边说过,只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在一个只读事务中的事务id值都默认为0。

    当生成快照后,会通过下面这个流程来判断该记录对当前事务是否可见

    MVCC

    1. 从上图中我们可以看到,在根据当前数据库中运行中的读写事务id,会去生成一个ReadView。
    2. 然后根据要读取的数据记录中的事务id(方便区别,记为r_trx_id)跟ReadView中保存的几个属性做如下判断
    • 如果被访问版本的r_trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
    • 如果被访问版本的r_trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
    • 如果被访问版本的r_trx_id属性值大于或等于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
    • 如果被访问版本的r_trx_id属性值在ReadViewmin_trx_idmax_trx_id之间,那就需要判断一下r_trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。
    • 如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。

    实际上,提交读跟可重复读在实现上最大的差异就在于

    1. 提交读每次select都会生成一个快照
    2. 可重复读只有在第一次会生成一个快照

    总结

    本文主要介绍了事务的基本概念跟MySQL中事务的实现原理。下篇文章开始我们就要真正的进入Spring的事务学习啦!铺垫了这么久,终于开始主菜了…

    在前面的大纲里也能看到,会分为上下两篇,第一篇讲应用以及在使用过程中会碰到的问题,第二篇我们就深入源码分析Spring中的事务机制的实现原理!

    我叫DMZ,一个在学习路上匍匐前行的小菜鸟!

    参考

    书籍:掘金小册《MySQL 是怎样运行的:从根儿上理解 MySQL》:https://juejin.im/book/6844733769996304392

    书籍:《MySQL技术内幕:InnoDB存储引擎》:关注公众号,程序员DMZ,后台回复InnoDB即可领取

    书籍:《高性能MySQL》:关注公众号,程序员DMZ,后台回复MySQL即可领取

    文章:《深入学习MySQL事务:ACID特性的实现原理》:https://www.cnblogs.com/kismetv/p/10331633.html

    文章:《详细分析MySQL事务日志(redo log和undo log)》:https://www.cnblogs.com/f-ck-need-u/p/9010872.html

    文章:《Mysql事务实现原理》:https://www.lagou.com/lgeduarticle/82740.html

    文章:《面试官:你说熟悉MySQL事务,那来谈谈事务的实现原理吧!》:https://mp.weixin.qq.com/s/jrfZr3YzE_E0l3KjWAz1aQ

    文章:《InnoDB 事务分析-Undo Log》:http://leviathan.vip/2019/02/14/InnoDB%E7%9A%84%E4%BA%8B%E5%8A%A1%E5%88%86%E6%9E%90-Undo-Log/

    文章:《InnoDB 的 Redo Log 分析》:http://leviathan.vip/2018/12/15/InnoDB%E7%9A%84Redo-Log%E5%88%86%E6%9E%90/

    文章:《MySQL redo & undo log-这一篇就够了》:https://www.jianshu.com/p/336e4995b9b8

    展开全文
  • 在Redis中实现事务主要依靠以下几个命令来实现:Redis事务从开始到结束通常会通过三个阶段:1.事务开始2.命令入队3.事务执行以下是一个最简单的Redis事务流程:第一步跟其他的关系型数据库类似,也是需要开启一个事务...
  • 基于SpringBoot的Spring事务实现原理

    千次阅读 2020-07-10 00:03:40
    基于SpringBoot的Spring事务实现原理 jar包版本 spring-boot-starter-web 2.1.3.RELEASE spring-aop 5.1.5.RELEASE spring-boot-starter-jdbc 2.1.3.RELEASE spring-jdbc 5.1.5.RELEASE Spring事务概述 ...
  • Spring 声明式事务实现原理 起因 本人之前使用Spring事务时遇到两种场景。同一个class没有事务的A方法调用含有事务的B方法发现事务不生效,同一个class有事务的A方法调用含有事务的B方法(传播性为REQUIRES_NEW)...
  • Spring的事务实现原理与传播机制

    千次阅读 2020-07-02 10:39:10
    一、Spring的事务实现原理 (1)在要开启事务的方法上加@Transactional注解; (2)此时Spring就会使用AOP的思想,对你的这个方法在执行之前,先去开启事务,执行完毕之后根据方法是否报错,决定回滚或者提交事务。 ...
  • 一 事务 事务的特性 事务隔离级别 二 Spring事务 Spring 编程式事务 使用方式 Spring 声明式事务 使用方式 ... Spring 事务实现原理 Spring 事务注意的问题 事务失效问题 一、事务 事务是指 ...
  • redis 事务实现原理

    千次阅读 2016-08-31 16:53:48
    一:简介Redis事务通常会使用MULTI,EXEC,WATCH等命令来完成,redis实现事务实现的机制与常见的关系型数据库有很大的却别,比如redis的事务不支持回滚,事务执行时会阻塞其它客户端的请求执行。二:事务实现细节redis...
  • 数据库事务实现原理

    2020-03-07 22:05:32
    数据库事务 1. 定义 数据库事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。 2. 典型特性 典型特性有原子性...
  • MySQL事务实现原理之Redo Log

    千次阅读 2020-05-16 18:09:26
    理论是灰色的,实践之树长青???? ——恩格斯 前言 在介绍事务原理之前我们先来说一下事务的四个核心属性: ...而说起事务实现,Redo Log是一个重要的话题,它主要实现了事务的持久化属性; Write-Ahead Log 一个
  •    本文将重点分析RocketMQ Broker如何处理事务消息提交、回滚命令,其核心实现就是根据commitlogOffset找到消息,如果是提交动作,就恢复原消息的主题与队列,再次存入commitlog文件进而转到消息消费队列,供消费...
  • 事务实现原理-回滚原理

    万次阅读 2015-11-21 16:42:32
    1. 每个事务都有个事务id  2. 最终落到 每一个块的原子性。   写一个块,部分写完部分未写完。...2. 回滚操作是要实现的.回滚后才能执行下一个对page的改动.  每次操作有事务id,放在块最后。重写的时
  • 之前写了几篇mysql存储原理的文章。 6Innodb_buffer_pool 5b+ tree和每个page存储结构 4innodb文件系统基本结构(段、簇、页面) 3innodb文件系统初步入门 2表对象缓存 1 连接层 这一篇终于到事务了,事务...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 212,512
精华内容 85,004
关键字:

事务实现原理