前段时间把 ShardingSphere 升级到了 5.1.1 版本,奈何官方版本升级太快跟不上速度,这不最近又发现了一个 BUG。
问题现象
数据库做了分库分表,在需要查询多表数据进行 merge 的时候发生了一个 NPE 的异常。
Caused by: java.lang.NullPointerException
at org.apache.shardingsphere.sharding.merge.dql.orderby.OrderByValue.getOrderValuesCaseSensitiveFromTables(OrderByValue.java:73) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
at org.apache.shardingsphere.sharding.merge.dql.orderby.OrderByValue.getOrderValuesCaseSensitive(OrderByValue.java:64) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
at org.apache.shardingsphere.sharding.merge.dql.orderby.OrderByValue.<init>(OrderByValue.java:58) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
at org.apache.shardingsphere.sharding.merge.dql.orderby.OrderByStreamMergedResult.orderResultSetsToQueue(OrderByStreamMergedResult.java:56) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
at org.apache.shardingsphere.sharding.merge.dql.orderby.OrderByStreamMergedResult.<init>(OrderByStreamMergedResult.java:50) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
at org.apache.shardingsphere.sharding.merge.dql.ShardingDQLResultMerger.build(ShardingDQLResultMerger.java:89) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
at org.apache.shardingsphere.sharding.merge.dql.ShardingDQLResultMerger.merge(ShardingDQLResultMerger.java:63) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
at org.apache.shardingsphere.infra.merge.MergeEngine.executeMerge(MergeEngine.java:90) ~[shardingsphere-infra-merge-5.1.1.jar:5.1.1]
at org.apache.shardingsphere.infra.merge.MergeEngine.merge(MergeEngine.java:80) ~[shardingsphere-infra-merge-5.1.1.jar:5.1.1]
at org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement.mergeQuery(ShardingSpherePreparedStatement.java:487) ~[shardingsphere-jdbc-core-5.1.1.jar:5.1.1]
at org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement.getResultSet(ShardingSpherePreparedStatement.java:435) ~[shardingsphere-jdbc-core-5.1.1.jar:5.1.1]
at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.getFirstResultSet(DefaultResultSetHandler.java:237) ~[mybatis-3.5.3.jar:3.5.3]
at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSets(DefaultResultSetHandler.java:187) ~[mybatis-3.5.3.jar:3.5.3]
at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:65) ~[mybatis-3.5.3.jar:3.5.3]
at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79) ~[mybatis-3.5.3.jar:3.5.3]
at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:63) ~[mybatis-3.5.3.jar:3.5.3]
at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324) ~[mybatis-3.5.3.jar:3.5.3]
at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156) ~[mybatis-3.5.3.jar:3.5.3]
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109) ~[mybatis-3.5.3.jar:3.5.3]
at jdk.internal.reflect.GeneratedMethodAccessor346.invoke(Unknown Source) ~[?:?]
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:63) ~[mybatis-3.5.3.jar:3.5.3]
at com.sun.proxy.$Proxy410.query(Unknown Source) ~[?:?]
at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:108) ~[pagehelper-5.1.11.jar:?]
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) ~[mybatis-3.5.3.jar:3.5.3]
at com.sun.proxy.$Proxy410.query(Unknown Source) ~[?:?]
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147) ~[mybatis-3.5.3.jar:3.5.3]
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140) ~[mybatis-3.5.3.jar:3.5.3]
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:427) ~[mybatis-spring-2.0.6.jar:2.0.6]
... 95 more
问题排查
跟踪到报错的地方,发现是这个地方的schema
是个null
,从而引发了 NPE。
一路往上看代码,最终定位到了这个获取schema
的地方,也就是元数据去getDefaultSchema
获取默认的schema
名称改的时候拿到了一个空值。
进入这个方法后发现通过schema
去schemas
这个map
里获取名称的时候是个空值,debug
到这个地方其实发现了问题。
我们的schemaName
配置的是orderTrade
包含有大写字符的,所以name
传进来的是orderTrade
,但是问题是这个schemas
确是ordertrade
。
所以很显然,这里获取不到正确的schema
名称,导致了这个 NPE 的异常,那么问题是这个schemas
是怎么加载进来的呢?
我们发现schemas
是在创建元数据的时候,通过构造函数赋值的,那么只要找到这个赋值的地方应该就能发现问题了。
通过一番查找,找到了调用的地方,这个schemas
值就是databaseMap
中的value
,那么我们要继续看这个databaseMap
是如何初始化来的。
继续看源码,找到了databaseMap
进行初始化的地方,原来是通过DatabaseLoader
去加载元数据的时候初始化的,那么这个load
方法是怎么处理的呢?
从代码来看他包含了两部分的信息,第一个是我们自己通过schema
配置的一些分库分表的配置信息,另外一部分则是数据库默认的一些表的元数据,比如mysql
、information_schema
这些,那我们只要看自己配置的那部分就可以了,也就是SchemaLoader.load(dataSourceMap, rules, props)
方法。
看他实际上就是获取数据库是什么类型,比如mysql
,然后去加载表的元数据,最后new
出来ShardingSphereSchema
,直接看最后的new
部分代码就行了。
进入这个方法,瞬间就真相大白了,原来在put
的时候对所有的schemaName
进行了小写处理,所以在最上面我们去get
的时候肯定会拿到一个空值,最终导致merge
的时候发生了 NPE 异常。
解决方案
现在问题原因已经发现了,那么该如何解决呢?总不能不让别人配置的时候不让写大写吧,本着能不能白嫖一个 PR 的想法,又去给 Sharding 提了一个 Issue。
就我点了根烟的功夫,回头就给我回复说新版本已经修复了,希望落空了,修复方案就是查询的时候也做小写处理了,好吧,那就这样吧。