事务绑定在mysql连接_MySQL数据库之数据库读写分离与事务纠缠

本文主要向大家介绍了MySQL数据库之数据库读写分离与事务纠缠 ,通过具体的内容向大家展现,希望对大家学习MySQL数据库有所帮助。

49bb8590262ed139d1b8930c1b0f2f95.png

1. 在读写分离时会不会造成事务主从切换错误

一个线程在Serivcie时Select时选择的是从库,DynamicDataSourceHolder中ThreadLocal对应线程存储的是slave,然后调用Manager时进入事务,事务使用默认的transacatinManager关联的dataSource,而此时会不会获取到的是slave?

2. 事务隔离级别和传播特性会不会影响数据连接池死锁

一个线程在Service层Select数据会从数据库获取一个Connection,通常来讲,后续DB的操作在同一线线程会复用这个DB Connection,但是从Service进入Manager的事务后,Get Seq获取全局唯一标识,所以Get Seq一般都会开启新的事物从DB Pool里重新获取一个新连接进行操作,但是问题是如果两个事务关联的datasource是同一个,即DB Pool是同一个,那么如果DB Pool已经为空,是否会造成死锁?

为了减轻数据库的压力,一般会进行数据库的读写分离,实现方法一是通过分析sql语句是insert/select/update/delete中的哪一种,从而对应选择主从,二是通过拦截方法名称的方式来决定主从的,如:save*()、insert*() 形式的方法使用master库,select()开头的使用slave库。

通常在方法上标上自定义标签来选择主从。1

2@DataSource("slave")

intqueryForCount(OrderQueryConditionqueryCondition);

或者通过拦截器动态选择主从。

1

2

3

4

5

6

7

8map>

property>

读写动态库配置1

2

3

4

5

6

7

8

9

10map>

property>

bean>

DynamicDataSource:

定义动态数据源,实现通过集成Spring提供的AbstractRoutingDataSource,只需要实现determineCurrentLookupKey方法即可,由于DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全,由DynamicDataSourceHolder完成。1

2

3

4

5

6

7publicclassDynamicDataSourceextendsAbstractRoutingDataSource{

@Override

protectedObjectdetermineCurrentLookupKey(){

//使用DynamicDataSourceHolder保证线程安全,并且得到当前线程中的数据源key

returnDynamicDataSourceHolder.getDataSourceKey();

}

}

DynamicDataSourceHolder类:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31publicclassDynamicDataSourceHolder{

//写库对应的数据源key

privatestaticfinalStringMASTER="master";

//读库对应的数据源key

privatestaticfinalStringSLAVE="slave";

//使用ThreadLocal记录当前线程的数据源key

privatestaticfinalThreadLocalholder=newThreadLocal();

publicstaticvoidputDataSourceKey(Stringkey){

holder.set(key);

}

publicstaticStringgetDataSourceKey(){

returnholder.get();

}

publicstaticvoidmarkDBMaster(){

putDataSourceKey(MASTER);

}

publicstaticvoidmarkDBSlave(){

putDataSourceKey(SLAVE);

}

publicstaticvoidmarkClear(){

putDataSourceKey(null);

}

}

动态设置数据源可以通过Spring AOP来实现,而AOP切面的方式也有很多种。

Spring AOP的原理:Spring AOP采用动态代理实现,在Spring容器中的bean会被代理对象代替,代理对象里加入了增强逻辑,当调用代理对象的方法时,目标对象的方法就会被拦截。

事务切面和读/写库选择切面1

2

3

4

5

6

7

8

9aop:aspect>

aop:config>

Java逻辑:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25publicclassDataSourceAspect{

privatestaticfinalString[]defaultSlaveMethodStart

=newString[]{"query","find","get","select","count","list"};

/**

*在进入Dao方法之前执行

*

*@parampoint切面对象

*/

publicvoidbefore(JoinPointpoint){

StringmethodName=point.getSignature().getName();

booleanisSlave=isSlave(methodName);

if(isSlave){

DynamicDataSourceHolder.markDBSlave();

}else{

DynamicDataSourceHolder.markDBMaster();

}

}

publicvoidafter(){

DynamicDataSourceHolder.markClear();

}

}

使用BeanNameAutoProxyCreator创建代理1

2

3

4

5

6

7

8

9

10

11

12

13

14bean>

*Mappervalue>

property>

MySqlDaoSourceInterceptorvalue>

list>

property>

bean>

Java逻辑:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21publicclassDaoSourceInterceptorimplementsMethodInterceptor{

publicObjectinvoke(MethodInvocationinvocation)throwsThrowable{

dataSourceAspect(invocation);

Objectresult=invocation.proceed();

DataSourceHandler.putDataSource(null);

returnresult;

}

privatevoiddataSourceAspect(MethodInvocationinvocation){

Stringmethod=invocation.getMethod().getName();

for(Stringkey:ChooseDataSource.METHOD_TYPE_MAP.keySet()){

for(Stringtype:ChooseDataSource.METHOD_TYPE_MAP.get(key)){

if(method.startsWith(type)){

DataSourceHandler.putDataSource(key);

return;

}

}

}

}

}

Spring的事务处理为了与数据访问解耦,它提供了一套处理数据资源的机制,而这个机制采用ThreadLocal的方式。

事务管理器

Spring中通常通过@Transactional来声明使用事务。如果@Transactional不指定事务管理器,使用缺省。注意如果Spring容器中定义了两个事务管理器,@Transactional标注是不支持区分使用哪个事务管理器的,Spring 3.0之后的版本Transactional增加了个string类型的value属性来特殊指定加以区分。1

2

3

4

5@Transactional

publicintinsertEntryCreateId(UrpMenuurpMenu){

urpMenu.setMId(this.sequenceUtil.get(SequenceConstants.MARKET_URP_MENU));

returnsuper.insertEntryCreateId(urpMenu);

}

同时进行XML配置1

2

3

4

5

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

bean>

其中dataSource是在Spring配置文件中定义的数据源的对象实例。transaction-manager属性保存一个对在Spring配置文件中定义的事务管理器bean的引用,如果没有它,就会忽略@Transactional注释,导致代码不会使用任何事务。proxy-target-class控制是基于接口的还是基于类的代理被创建,如果属性值被设置为true,那么基于类的代理将起作用,如果属性值为false或者被省略,那么标准的JDK基于接口的代理将起作用。

注意@Transactional建议在具体的类(或类的方法)上使用,不要使用在类所要实现的任何接口上。

SQL四类隔离级别

事务的实现是基于数据库的存储引擎。不同的存储引擎对事务的支持程度不一样。Mysql中支持事务的存储引擎有InnoDB和NDB。InnoDB是mysql默认的存储引擎,默认的隔离级别是RR(Repeatable Read)。

事务的隔离性是通过锁实现,而事务的原子性、一致性和持久性则是通过事务日志实现。

Q1在读写分离时会不会造成事务主从切换错误

一个线程在Serivcie时Select时选择的是从库,DynamicDataSourceHolder中ThreadLocal对应线程存储的是slave,然后调用Manager时进入事务,事务使用默认的transacatinManager关联的dataSource,而此时会不会获取到的是slave?

经验证不会,但这是因为在AOP设置动态织出的时候,都要清空DynamicDataSourceHolder的ThreadLocal,如此避免了数据库事务传播行为影响的主从切换错误。如果Selelct DB从库完成之后不清空ThreadLocal,那么ThreadLocal跟线程绑定就会传播到Transaction,造成事务操作从库异常。而清空ThreadLocal之后,Spring的事务拦截先于动态数据源的判断,所以事务会切换成主库,即使事务中再有查询从库的操作,也不会造成主库事务异常。

Q2事务隔离级别和传播特性会不会影响数据连接池死锁

一个线程在Service层Select数据会从数据库获取一个Connection,通常来讲,后续DB的操作在同一线线程会复用这个DB Connection,但是从Service进入Manager的事务后,Get Seq获取全局唯一标识,所以Get Seq一般都会开启新的事物从DB Pool里重新获取一个新连接进行操作,但是问题是如果两个事务关联的datasource是同一个,即DB Pool是同一个,那么如果DB Pool已经为空,是否会造成死锁?

经验证会死锁,所以在实践过程中,如果有此实现,建议Get Seq不要使用与事务同一个连接池。或者采用事务隔离级别设置PROPAGATION_REQUIRES_NEW进行处理。最优的实践是宎把Get SeqId放到事务里处理。

本文由职坐标整理并发布,希望对同学们学习MySQL有所帮助,更多内容请关注职坐标数据库MySQL数据库频道!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值