如何通过Hint来强制sql走主库或者从库

一般当公司很小时,基本一台Mysql服务单个库就可以搞定所有的db操作。当公司发展到一定规模时,数据量的陡增,一台mysql已经不能满足我们的需求。这时我们要提高生产力就需要做读写分离,分库分表,我们的应用须机操作多个数据库实例,这时我们就需要使用到数据库中间件。而目前主流的数据库中间件方案有两种,一种是数据库服务端代理(代理数据库),比如像Mycat,Arkproxy。另一种就是业务代理也叫客户端代理(代理数据源),比如像shardingJDBC,这种代理你会在应用中创建多个实例的连接,且他可以ORM框架进行良好的配合,而前者须要去实现和mysql服务端的通信协议比较复杂。

让我们通过直接来对比两者的差别(图来源田守枝的blog):
如何通过Hint来强制sql走主库或者从库_第1张图片
数据库中间件对比.png

目前很多公司都是做的数据源代理,毕竟像shardingJdbc这种中件间做得很出色,我们很容易上手使用,我们之前项目也是采用了这种方式。后面由于其他原因我们将数据库的代理方式改为后者。那么,这种方式我们怎么来让我们的sql走对应的数据库节点呢?比如我有一台master,两台slave节点 slave1,slave2.一般正常的像insert,update,delete或者开启的事务都会走master节点;所有的query查询或者show variables ** 这种的默认走slave节点(一般这些中间件都会提供这种功能)。
那么问题来了,有一些场景,比如添加用户须要校验手机号是否存在或者使用悲观锁时须要强制走master节点,我们该怎么办呢?最常用的方式就是使用hint方式(就是约定,暗示)的方式来判断这个sql走哪个节点,换句话说就是在sql的前面加一些特殊的标记,来表示强制走主库。要实现这种方式就须要我们去修改sql.
例如:

/*!8899 route to master*/ SELECT * FROM user

当中间件收到这条sql时,他会取出/!8899 route to master/ ,然后根据规则判断发现这个sql须要强制走master库,那么他就会将此sql路由到主库上去执行。

此时,我们须要我们在java代码层面去修改这个sql,一般我们可以使用下面两种方式(基于Mybatis),他们各有优缺点:

1.直接在mapper文件里修改sql:
  

这种方式的优点就是简单方便。缺点就是这里是硬编码,所有调这个方法时都会被强制走主库,如果你想走从库的话,你还得单独再写一个查询mapper.如果你的查询大数据须要走主库的话可以考虑使用这种方式。

2.使用ThreadLocal并自己实现一个Mybatis的拦截器去修改sql。

大致方式就是在你须要执行走主库的sql前面,通过ThreadLocal把值设置进去,然后再自定义的mybatis 拦截器中去拿到这个值并将该值附加到sql上。这种方式优点就是比较灵活,想在哪里用就在哪里用,难点就是须要自己实现一下,但是整体来讲也不复杂。
伪代码(shardingJDBC也是用的这种方式):

HintManager hintManager = HintManager.getInstance();
hintManager.setMasterRouteOnly();
 //要执行的sql
HintManager.clear();
注意:

1.ThreadLocal使用后一定要执行clear ThreadLocal操作,如果在中间抛出sql异常一定要注意处理。可以使用try-with-resource finally来处理。
块来处理
2.ThreadLocal只能适用于当前线程,如果你的查询是在主线程上开启的子线程的话(就是ThreadLocal设置值的时候在一个线程,取的时候在子线程中),那么是不生效的,这时可以考虑使用InheritableThreadLocal来实现。

你可能感兴趣的:(如何通过Hint来强制sql走主库或者从库)