本文介紹了在應(yīng)用內(nèi)通過代碼高效抽取數(shù)據(jù)的方法。
簡(jiǎn)介
數(shù)據(jù)抽取是指通過代碼或者數(shù)據(jù)導(dǎo)出工具,從PolarDB-X中批量讀取數(shù)據(jù)的操作。主要包括以下場(chǎng)景:- 通過數(shù)據(jù)導(dǎo)出工具將數(shù)據(jù)全量抽取到下游系統(tǒng)。PolarDB-X支持多種數(shù)據(jù)導(dǎo)出工具,更多內(nèi)容請(qǐng)參考數(shù)據(jù)導(dǎo)入導(dǎo)出。
- 在應(yīng)用內(nèi)處理數(shù)據(jù)或者批量的將查詢結(jié)果返回給用戶瀏覽時(shí),不能依賴外部工具,必須在應(yīng)用內(nèi)通過代碼完成數(shù)據(jù)全量抽取。
本文主要介紹在應(yīng)用內(nèi)通過代碼高效抽取數(shù)據(jù)的方法,根據(jù)是否一次性讀取全量數(shù)據(jù),分為全量抽取和分頁查詢。
全量抽取場(chǎng)景
全量抽取使用的SQL通常不包含表的拆分鍵,以全表掃描的方式執(zhí)行,隨著讀取數(shù)據(jù)量的增加,數(shù)據(jù)抽取操作的執(zhí)行時(shí)間線性增長(zhǎng)。為了避免占用過多網(wǎng)絡(luò)或連接資源,可以使用HINT直接下發(fā)查詢語句,從物理分片中拉取數(shù)據(jù)。以下示例采用Java代碼編寫,完整使用方法參考如何使用HINT。
public static void extractData(Connection connection, String logicalTableName, Consumer<ResultSet> consumer)
throws SQLException {
final String topology = "show topology from {0}";
final String query = "/*+TDDL:NODE({0})*/select * from {1}";
try (final Statement statement = connection.createStatement()) {
final Map<String, List<String>> partitionTableMap = new LinkedHashMap<>();
// Get partition id and physical table name of given logical table
try (final ResultSet rs = statement.executeQuery(MessageFormat.format(topology, logicalTableName))) {
while (rs.next()) {
partitionTableMap.computeIfAbsent(rs.getString(2), (k) -> new ArrayList<>()).add(rs.getString(3));
}
}
// Serially extract data from each partition
for (Map.Entry<String, List<String>> entry : partitionTableMap.entrySet()) {
for (String tableName : entry.getValue()) {
try (final ResultSet rs = statement
.executeQuery(MessageFormat.format(query, entry.getKey(), tableName))) {
// Consume data
consumer.accept(rs);
}
}
}
}
}
分頁查詢場(chǎng)景
向用戶展示列表信息時(shí),需要分頁來提高頁面的加載效率,避免返回過多冗余信息,用于處理分頁顯示需求的查詢,稱為分頁查詢。關(guān)系型數(shù)據(jù)庫沒有直接提供分段返回表中數(shù)據(jù)的能力,高效的實(shí)現(xiàn)分頁查詢,還需要結(jié)合數(shù)據(jù)庫本身的特點(diǎn)來設(shè)計(jì)查詢語句。
以MySQL為例,分頁查詢最直觀的實(shí)現(xiàn)方法,是使用limit offset,pageSize來實(shí)現(xiàn),例如如下查詢:
select * from t_order where user_id = xxx order by gmt_create, id limit offset, pageSize
因?yàn)間mt_create可能重復(fù),所以order by時(shí)應(yīng)加上id,保證結(jié)果順序的確定性。
假設(shè)我們?cè)趗ser_id,gmt_create上創(chuàng)建了局部索引,由于只有user_id上的條件,每次需要掃描的總數(shù)據(jù)量為offset + pageSize ,隨著offset的增大逐漸接近全表掃描,導(dǎo)致耗時(shí)增加。并且在分布式數(shù)據(jù)庫中,全表排序的吞吐無法通過增加DN數(shù)量來提高。
改進(jìn)方案1每次獲取下一頁記錄時(shí),指定從上次結(jié)束的位置繼續(xù)往后取,這樣不需要設(shè)置offset ,能夠避免出現(xiàn)全表掃描的情況。如下為一個(gè)按ID進(jìn)行分頁查詢的例子:
select * from t_order where id > lastMaxId order by id limit pageSize
第一次查詢不指定條件,后續(xù)查詢則傳入前一次查詢的最大id,在執(zhí)行時(shí),數(shù)據(jù)庫首先在索引上定位到lastMaxId的位置,然后連續(xù)返回pageSize條記錄即可,非常高效。
MySQL支持通過 Row Constructor Expression實(shí)現(xiàn)多列比較的語義(PolarDB-X同樣支持)。
(c2,c3) > (1,1)
等價(jià)于
c2 > 1 OR ((c2 = 1) AND (c3 > 1))
因此,可以用下面的方法實(shí)現(xiàn)分頁查詢:
select * from t_order
where user_id = xxx and (gmt_create, id) > (lastMaxGmtCreate, lastMaxId)
order by user_id, gmt_create, id limit pageSize
第一次查詢不指定條件,后續(xù)查詢則傳入前一次查詢的最大gmt_create和id,通過Row Constructor Expression正確處理gmt_create存在重復(fù)的情況。
結(jié)合上述分析,給出一個(gè)PolarDB-X上分頁查詢的最佳實(shí)踐:
-- lastMaxGmtCreate is not null
select * from t_order
where user_id = xxx
and (
(gmt_create > lastMaxGmtCreate)
or ((gmt_create = lastMaxGmtCreate) and (id > lastMaxId))
)
order by user_id, gmt_create, id limit pageSize
-- lastMaxGmtCreate is null
select * from t_order
where user_id = xxx
and (
(gmt_create is not null)
or (gmt_create is null and id > lastMaxId)
)
order by user_id, gmt_create, id limit pageSize