项目背景:spring+mybatis,多数据源,T(T1,T2),P(P1,P2),R(R1,R2),其中1表示主库,2表示从库,使用Spring的AbstractRoutingDataSource在主从之间进行切换
最近在查看请求日志时,发现对数据库P1的一个update操作,提示数据库是只读的,错误提示: Error updating database. Cause: java.sql.SQLException: The MySQL server is running with the –read-only option so it cannot execute this statement.
遇到这类问题,首先会第一时间想到,是不是db出问题了,于是赶紧找到dba确认,结果dba反馈说P1库一切正常,那么会不会是主从路由的时候切换到从库上面去了呢,于是赶紧接着去查日志,发现路由日志显示是master,但为了保险起见,还是请求了dba查看数据库log,看看update的请求究竟是打到哪里了,结果一查,还真是打到从库上了,既然问题已经定位了,那只能接着撸代码了,业务关键代码如下:
@Resource(name = "t_transactionManager")
private DataSourceTransactionManager transactionManager;
TransactionDefinition def = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus transactionStatus = transactionManager.getTransaction(def);
Pay pay = payService.queryPayInSlave(id);
…………………………………逻辑处理…………………………
payService.updatePay(pay);
transactionManager.commit(transactionStatus);
我们来看下mybatis一次数据库操作是如何执行的,执行堆栈如下:
我们看到堆栈的最上层,程序开始获取Connection,这也是我们关注的重点,请求既然到从库P2了,那么获取到的连接肯定是从库P2的连接。获取Connection的源码如下:
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(dataSource.getConnection());
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = dataSource.getConnection();
if<