假设分库分表情况如下
- 分库 id0:分表 test_0 、 test_1
- 分库 id1: 分表 test_2、 test_3
sql语句: select test.* from test
一、路由结果
DefaultRouter#router
路由出来的结果两个 RouterResult, 每个里边有多个分表的sql, 即所有分库下的所有test分表
|
dbName: id0 |
|
sqls: |
|
- SELECT test_0.* \nFROM test_0 |
|
- SELECT test_1.* \nFROM test_1 |
|
|
|
dbName: id1 |
|
sqls: |
|
- SELECT test_2.* \nFROM test_2 |
|
- SELECT test_3.* \nFROM test_3 |
二、执行过程
如果路由定位到多个分库,会根据并发度n,将每个分库的sql语句 拆分成n个任务,放入线程池执行 默认单库并发度 concurrentLevel = 1, 执行过程为
(1) 执行分库id0里各分表的sql
- 从分库id0 对应的数据源中一个数据连接,创建 (SELECT test_0.* \nFROM test_0)的 statement
- 从分库id0 对应的数据源中一个数据连接,创建l (SELECT test_1.* \nFROM test_1 )的statement
- 把这两个statement包裹到一个线程task中
(2) 执行分库id1里各分表的sql
- 从分库id1 对应的数据源中一个数据连接,创建 (SELECT test_2.* \nFROM test_2 ) 的statement
- 从分库id1 对应的数据源中一个数据连接,创建l (SELECT test_3.* \nFROM test_3 )的statement
- 把这两个statement包裹到一个线程task中
(3) 把这两个task 丢到 SQLThreadPoolExecutor 中执行, 阻塞等待执行完毕
三、结果集合并
ShardResultSet#init() -> ShardResultSetMerger.merge 合并这四个 ResultSet
ShardResultSet 内部包含多个sql执行的结果集 ResultSet, 它实现了 ResultSet ,当从它遍历查询结果的时候,会根据 MergeContext( join、limit…etc)来组合结果数据
debug单测入口:
com.dianping.zebra.shard.jdbc.MultiDBPreparedStatementLifeCycleTest#testSingleRouterResult1
总结
路由定位到多个分库或分表的执行逻辑:
ShardPrepardStatement#normalSelectExecute
会依次执行这多个路由目标分库 RouterResult 内的语句,然后 ShardPrepardStatement#executeQueryByOriginal
执行单个分库内的所有sql, 如果它发现有多个sql需要执行,则会根据 单库并发度的配置 concurrentLevel=1
(1) 默认concurrentLevel = 1 ,每个分库内不同表的sql, 先创建对应的 statement, 然后会打包到一个task里
(2) 如果 concurrentLevel > 1, 则每个分库会获取 concurrentLevel 个数据库连接,将这几条分表的sql均摊到这几个数据库连接,创建多个statement, 包成 concurrentLevel 个线程task
(3) 然后丢到java线程池中并发执行 ,然后阻塞等待执行完毕,获取结果
完整目录:数据库中间件zebra源码分析