本文旨在介紹 Spark 通過JDBC讀取數(shù)據(jù)時常用的一些優(yōu)化手段
關(guān)于數(shù)據(jù)庫索引
無論使用哪種JDBC API,spark拉取數(shù)據(jù)最終都是以select語句來執(zhí)行的,所以在自定義分區(qū)條件或者指定的long型column時,都需要結(jié)合表的索引來綜合考慮,才能以更高性能并發(fā)讀取數(shù)據(jù)庫數(shù)據(jù)。
API的使用可以參考文檔:Spark JDBC系列--取數(shù)的四種方式
離散型的分區(qū)字段
當(dāng)使用spark拉取table_example表的數(shù)據(jù)時,使用的分區(qū)字段,并不是連續(xù)或均勻分布的。這時如果簡單的按預(yù)期的numPartitions做均分,則會造成數(shù)據(jù)傾斜,讀取性能也會受到影響。
ID離散型例舉
背景
一般情況下,表的ID字段,都會設(shè)置成自增,即使 step!=1,也是均勻分布的的。但是當(dāng)數(shù)據(jù)積累到一定程度,需要進(jìn)行分庫分表時,多個實例中ID的唯一性就需要借助分庫分表中間件,使用如snowflake之類的全局唯一編號,來生成全局唯一ID了,此時必定會出現(xiàn)一定程度的ID離散。
入?yún)?/h4>
min_id:1,max_id:1000000,數(shù)據(jù)集中在:1~500,10000~20000,100000~400000 。。。即存在多段不均勻分布
普通處理方式
sqlContext.read.jdbc(url,tableName, "id", 1, 1000000,400,prop)
min_id:1,max_id:1000000,數(shù)據(jù)集中在:1~500,10000~20000,100000~400000 。。。即存在多段不均勻分布
sqlContext.read.jdbc(url,tableName, "id", 1, 1000000,400,prop)
此方式的分區(qū)where查詢條件,會存在很多的無用查詢(返回了空結(jié)果),劃分的task為400,但實際有效的可能只有200個,且數(shù)據(jù)還可能存在一定程度的傾斜,對后續(xù)的計算產(chǎn)生影響。
自定義處理方式
def getPredicates = {
//1.獲取表total數(shù)據(jù)。
//2.按numPartitions均分,獲得offset,可以確保每個分片的數(shù)據(jù)一致
//3.獲取每個分片內(nèi)的最大最小ID,組裝成條件數(shù)組
。。。實現(xiàn)細(xì)節(jié)省略
}
sqlContext.read.jdbc(url,table, getPredicates,connectionProperties)
通過自由組裝方式,可以達(dá)到精確控制,但是實現(xiàn)成本較高。
ID取模方式
sqlContext.read.jdbc(url,tableName, "id%200", 1, 1000000,400,prop)
根據(jù)numPartitions確定合理的模值,可以盡量做到數(shù)據(jù)的連續(xù),且寫法簡單,但是由于在ID字段上使用了函數(shù)計算,所以索引將失效,此時需要配合其他包含索引的where條件加以輔助,才能使查詢性能最大化。
原理:
API中的columnName其實只會作為where條件進(jìn)行簡單的拼接,所以數(shù)據(jù)庫中支持的語法,都可以使用。tableName的原理也一樣,僅會作為from 后的內(nèi)容進(jìn)行拼接,所以也可以寫一個子句傳入tableName中,但依然要在保證性能的前提下。
結(jié)語
不僅僅是取模操作,數(shù)據(jù)庫語法支持的任何函數(shù),都可以在API中傳入使用,關(guān)鍵在于性能是否達(dá)到預(yù)期。