MySQL数据库之数据库读写分离与事务纠缠
小标 2019-03-06 来源 : 阅读 1121 评论 0

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

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

MySQL数据库之数据库读写分离与事务纠缠

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


 

8


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

map>


 

property>


 


读写动态库配置


 

1


 

2


 

3


 

4


 

5


 

6


 

7


 

8


 

9


 

10


 

 

 

 

 

 

 

map>


 

property>


 

 

 

 

 

 

 

bean>


 


DynamicDataSource:


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


 

1


 

2


 

3


 

4


 

5


 

6


 

7


 

 

publicclassDynamicDataSourceextendsAbstractRoutingDataSource{


 

@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


 

31


 

 

publicclassDynamicDataSourceHolder{


 

//写库对应的数据源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


 

9


 

 

 

 

 

 

 

 

 

 

 

 

 

aop: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


 

25


 

 

publicclassDataSourceAspect{


 

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


 

14


 

 

 

 

 

bean>


 

 

 

*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


 

21


 

 

publicclassDaoSourceInterceptorimplementsMethodInterceptor{


 

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


 

 

 

<beanid="transactionManager"


 

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数据库频道!

本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程