假設(shè)分庫分表情況如下
- 分庫 id0:分表 test_0 、 test_1
- 分庫 id1: 分表 test_2、 test_3
sql語句: select test.* from test
一、路由結(jié)果
DefaultRouter#router 路由出來的結(jié)果兩個 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
二、執(zhí)行過程
如果路由定位到多個分庫,會根據(jù)并發(fā)度n,將每個分庫的sql語句 拆分成n個任務(wù),放入線程池執(zhí)行
默認單庫并發(fā)度 concurrentLevel = 1, 執(zhí)行過程為
(1) 執(zhí)行分庫id0里各分表的sql
- 從分庫id0 對應(yīng)的數(shù)據(jù)源中一個數(shù)據(jù)連接,創(chuàng)建 (SELECT test_0.* \nFROM test_0)的 statement
- 從分庫id0 對應(yīng)的數(shù)據(jù)源中一個數(shù)據(jù)連接,創(chuàng)建l (SELECT test_1.* \nFROM test_1 )的statement
- 把這兩個statement包裹到一個線程task中
(2) 執(zhí)行分庫id1里各分表的sql
- 從分庫id1 對應(yīng)的數(shù)據(jù)源中一個數(shù)據(jù)連接,創(chuàng)建 (SELECT test_2.* \nFROM test_2 ) 的statement
- 從分庫id1 對應(yīng)的數(shù)據(jù)源中一個數(shù)據(jù)連接,創(chuàng)建l (SELECT test_3.* \nFROM test_3 )的statement
- 把這兩個statement包裹到一個線程task中
(3) 把這兩個task 丟到 SQLThreadPoolExecutor 中執(zhí)行, 阻塞等待執(zhí)行完畢
三、結(jié)果集合并
ShardResultSet#init() -> ShardResultSetMerger.merge 合并這四個 ResultSet
ShardResultSet 內(nèi)部包含多個sql執(zhí)行的結(jié)果集 ResultSet, 它實現(xiàn)了 ResultSet ,當從它遍歷查詢結(jié)果的時候,會根據(jù) MergeContext( join、limit…etc)來組合結(jié)果數(shù)據(jù)
debug單測入口:
com.dianping.zebra.shard.jdbc.MultiDBPreparedStatementLifeCycleTest#testSingleRouterResult1
總結(jié)
路由定位到多個分庫或分表的執(zhí)行邏輯:
ShardPrepardStatement#normalSelectExecute 會依次執(zhí)行這多個路由目標分庫 RouterResult 內(nèi)的語句,然后 ShardPrepardStatement#executeQueryByOriginal執(zhí)行單個分庫內(nèi)的所有sql, 如果它發(fā)現(xiàn)有多個sql需要執(zhí)行,則會根據(jù) 單庫并發(fā)度的配置 concurrentLevel=1
(1) 默認concurrentLevel = 1 ,每個分庫內(nèi)不同表的sql, 先創(chuàng)建對應(yīng)的 statement, 然后會打包到一個task里
(2) 如果 concurrentLevel > 1, 則每個分庫會獲取 concurrentLevel 個數(shù)據(jù)庫連接,將這幾條分表的sql均攤到這幾個數(shù)據(jù)庫連接,創(chuàng)建多個statement, 包成 concurrentLevel 個線程task
(3) 然后丟到j(luò)ava線程池中并發(fā)執(zhí)行 ,然后阻塞等待執(zhí)行完畢,獲取結(jié)果