17. SQL重寫為limit Integer.MAX_VALUE的無奈
阿飛Javaer,轉載請註明原創出處,謝謝!!
這篇文章源於sharding-jdbc原始碼分析之重寫的遺留問題,相關sharding-jdbc原始碼如下:
private void appendLimitRowCount(final SQLBuilder sqlBuilder, final RowCountToken rowCountToken, final int count, final List<SQLToken> sqlTokens, final boolean isRewrite) {
SelectStatement selectStatement = (SelectStatement) sqlStatement;
Limit limit = selectStatement.getLimit();
if (!isRewrite) {
... ...
} else if ((!selectStatement.getGroupByItems().isEmpty() || !selectStatement.getAggregationSelectItems().isEmpty()) && !selectStatement.isSameGroupByAndOrderByItems()) {
// 如果要重寫sql中的limit的話,且sql中有group by或者有group by & order by,例如"select user_id, sum(score) from t_order group by user_id order by sum(score) desc limit 5",那麼limit 5需要重寫為limit Integer.MAX_VALUE,原因接下來分析
sqlBuilder.appendLiterals(String.valueOf(Integer.MAX_VALUE));
} else {
... ...
}
... ...
}
構造資料
為了解釋為什麼limit rowCount中的rowCount需要重寫為Integer.MAX_VALUE,需要先構造一些資料,如下圖所示:
如果不分庫分表的話,資料如下圖所示:
執行SQL
假定執行如下SQL:
select user_id, sum(score) from t_order group by user_id order by sum(score) desc limit 5;
結果如下所示:
user_id | sum(score) |
---|---|
20 | 36 |
21 | 32 |
10 | 30 |
11 | 25 |
14 | 20 |
假定select user_id, sum(score) from t_order group by user_id order by sum(score) desc limit 5;
這個SQL不重寫為limit 0, Integer.MAX_VALUE
,那麼t_order_0
和t_order_1
的結果分別如下;
t_order_0
的結果:
user_id | sum(score) |
---|---|
15 | 20 |
16 | 18 |
20 | 18 |
21 | 16 |
17 | 14 |
t_order_1
的結果:
user_id | sum(score) |
---|---|
10 | 30 |
11 | 25 |
12 | 20 |
13 | 20 |
14 | 20 |
路由到兩個表的執行結果歸併後的結果如下:
user_id | sum(score) |
---|---|
10 | 30 |
11 | 25 |
15 | 20 |
12 | 20 |
13 | 20 |
分析
根據執行結果可知,主要差異在於,真實結果有user_id為20,21的資料。我們在看一下t_order_0
和t_order_1
兩個分表中這兩個user_id的資料有什麼特殊之處:
在t_order_1
這個分表中,由於user_id為20,21的score值在TOP 5以外。但是合併t_order_0
和t_order_1
兩個分表的結果,user_id為20的sum(score)能夠排在第一(18+18=36);所以,如果group by這類的SQL不重寫為limit 0, Integer.MAX_VALUE
的話,會導致結果有誤。所以sharding-jdbc的原始碼必須要這樣重寫,沒有其他辦法!
延伸
事實上不只是sharding-jdbc,任何有sharding概念的中介軟體例如ElasticSearch,都要這麼處理,因為sharding後資料處理的流程幾乎都要經過解析->重寫->路由->執行->結果歸併這幾個階段;所以,當我們的資料是以分片的方式儲存時(分庫分表,ElasticSearch等),儘量保證CRUD只路由到一個分片上。最起碼要保證那些高併發低延遲的CRUD只路由到一個分片上,所以,sharding column的選取非常非常重要。