這篇文章主要介紹“數(shù)據(jù)庫(kù)分庫(kù)分表后非分片鍵怎么查詢”,在日常操作中,相信很多人在數(shù)據(jù)庫(kù)分庫(kù)分表后非分片鍵怎么查詢問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”數(shù)據(jù)庫(kù)分庫(kù)分表后非分片鍵怎么查詢”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!
創(chuàng)新互聯(lián)專注于企業(yè)成都營(yíng)銷網(wǎng)站建設(shè)、網(wǎng)站重做改版、高平網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5場(chǎng)景定制、商城開發(fā)、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站制作、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁(yè)設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為高平等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
我們知道在分庫(kù)分表中對(duì)于toC業(yè)務(wù)來說,需要選擇用戶屬性如 user_id 作為分片鍵,不推薦使用order_id這樣的作為分片鍵。
那問題來了,對(duì)于訂單表來說,選擇了user_id作為分片鍵以后如何查看訂單詳情呢?比如下面這樣一條SQL:
SELECT * FROM T_ORDER WHERE order_id = 801462878019256325
由于查詢條件中的order_id不是分片鍵,所以需要查詢所有分片才能得到最終的結(jié)果。如果下面有 1000 個(gè)分片,那么就需要執(zhí)行 1000 次這樣的 SQL,這時(shí)性能就比較差了。
可以通過ShardingSphere-JDBC生成的SQL得知,根據(jù)order_id查詢會(huì)對(duì)所有分片進(jìn)行查詢?nèi)缓笸ㄟ^UNION ALL
進(jìn)行合并。
但是,我們知道 order_id 是主鍵,應(yīng)該只有一條返回記錄,也就是說,order_id 只存在于一個(gè)分片中。這時(shí),可以有以下三種設(shè)計(jì):
冗余數(shù)據(jù)法
索引表法
基因分片法
當(dāng)然,這三種設(shè)計(jì)的本質(zhì)都是通過冗余實(shí)現(xiàn)空間換時(shí)間的效果,否則就需要掃描所有的分片,當(dāng)分片數(shù)據(jù)非常多,效率就會(huì)變得極差。
下面我們逐一分析。
這種做法很容易理解,同一份訂單數(shù)據(jù)在插入時(shí)保存兩份,根據(jù)user_id 和 order_id分別做兩個(gè)分庫(kù)分表的實(shí)現(xiàn)。
通過對(duì)表進(jìn)行冗余,對(duì)于 order_id 的查詢,只需要在 order_id = 801462878019256325
的分片中直接查詢就行,效率最高。但是這個(gè)方案設(shè)計(jì)的缺點(diǎn)又很明顯:冗余數(shù)據(jù)量太大。
索引表法是對(duì)第一種冗余法的改進(jìn),由于第一種方案冗余的數(shù)據(jù)量太大,所以索引表方案中只創(chuàng)建一個(gè)包含user_id和order_id的索引表,在插入訂單時(shí)再插入一條數(shù)據(jù)到索引表中。
表結(jié)構(gòu)如下
CREATE TABLE idx_orderid_userid ( order_id bigint user_id bigint, PRIMARY KEY (order_id) )
在實(shí)現(xiàn)時(shí)可以將idx_orderid_userid表通過redis緩存來代替,如果此表數(shù)據(jù)量很大也可以將其分庫(kù)分表,但是它的分片鍵是 order_id。
如果這時(shí)再根據(jù)字段 order_id 進(jìn)行查詢,可以進(jìn)行類似二級(jí)索引的回表實(shí)現(xiàn):先通過查詢索引表得到記錄 order_id = 801462878019256325
對(duì)應(yīng)的分片鍵 user_id 的值,接著再根據(jù) user_id 進(jìn)行查詢,最終定位到想要的數(shù)據(jù),如:
原始SQL:
SELECT * FROM T_ORDER WHERE order_id = 801462878019256325
拆分后的SQL:
# step 1 SELECT user_id FROM idx_orderid_userid WHERE order_id = 801462890610556951 # step 2 SELECT * FROM T_ORDER WHERE user_id = ? AND order_id = 801462890610556951
這個(gè)例子是將一條 SQL 語句拆分成 2 條 SQL 語句,但是拆分后的 2 條 SQL 都可以通過分片鍵進(jìn)行查詢,這樣能保證只需要在單個(gè)分片中完成查詢操作。不論有多少個(gè)分片,也只需要查詢 2個(gè)分片的信息,這樣 SQL 的查詢性能可以得到極大的提升。
通過索引表的方式,雖然存儲(chǔ)上較冗余全表容量小了很多,但是要根據(jù)另一個(gè)分片鍵進(jìn)行數(shù)據(jù)的存儲(chǔ),還是顯得不夠優(yōu)雅。
因此,最優(yōu)的設(shè)計(jì),不是創(chuàng)建一個(gè)索引表,而是將分片鍵的信息保存在想要查詢的列中,這樣通過查詢的列就能直接知道所在的分片信息,這種方法也叫叫做基因法。
基因法的原理出自一個(gè)理論:對(duì)一個(gè)數(shù)取余2的n次方,那么余數(shù)就是這個(gè)數(shù)的二進(jìn)制的最后n位數(shù)。
假如我們現(xiàn)在根據(jù)user_id進(jìn)行分片,采用user_id % 16的方式來進(jìn)行數(shù)據(jù)庫(kù)路由,這里的user_id%16,其本質(zhì)是user_id的最后4個(gè)bit位 log(16,2) = 4 決定這行數(shù)據(jù)落在哪個(gè)分片上,這4個(gè)bit就是分片基因。
如上圖所示,user_id=20160169的用戶創(chuàng)建了一個(gè)訂單(20160169的二進(jìn)制表示為:1001100111001111010101001)
使用user_id%16分片,決定這行數(shù)據(jù)要插入到哪個(gè)分片中
分庫(kù)基因是user_id的最后4個(gè)bit,log(16,2) = 4,即1001
在生成order_id時(shí),先使用一種分布式ID生成算法生成前60bit(上圖中綠色部分)
將分庫(kù)基因加入到order_id的最后4個(gè)bit(上圖中粉色部分)
拼裝成最終的64bit訂單order_id(上圖中藍(lán)色部分)
這樣保證了同一個(gè)用戶創(chuàng)建的所有訂單都落到了同一個(gè)分片上,order_id的最后4個(gè)bit都相同,于是:
通過user_id %16 能夠定位到分片
通過order_id % 16也能定位到分片
不好理解的話,可以看下面這段代碼:
@Test public void modIdTest(){ long userID = 20160169L; //分片數(shù)量 int shardNum = 16; String gen = getGen(userID, shardNum); log.info("userID:{}的基因?yàn)?{}",userID,gen); long snowId = IdWorker.getId(Order.class); log.info("雪花算法生成的訂單ID為{}",snowId); Long orderId = buildGenId(snowId,gen); log.info("基因轉(zhuǎn)換后的訂單ID為{}",orderId); Assert.assertEquals(orderId % shardNum , userID % shardNum); }
運(yùn)行結(jié)果如下:
原始訂單ID為1595662702879973377
,通過基因轉(zhuǎn)換后ID變成了1595662702879973385
,對(duì)于用戶id 和 新生成的訂單id對(duì)其取模結(jié)果一樣。
上面那種做法是基因替換,替換掉訂單id的分片基因。下面這種做法就更顯直接。
將訂單表 orders 的主鍵設(shè)計(jì)為一個(gè)字符串,這個(gè)字符串中最后一部分包含分片鍵的信息,如:
order_id = string(order_id + user_id)
那么這時(shí)如果根據(jù) order_id 進(jìn)行查詢:
SELECT * FROM T_ORDER WHERE order_id = '1595662702879973377-20160169';
由于字段 order_id 的設(shè)計(jì)中直接包含了分片鍵信息,所以我們可以直接通過分片鍵部分直接定位到分片上。
同樣地,在插入時(shí),由于可以知道插入時(shí) user_id 對(duì)應(yīng)的值,所以只要在業(yè)務(wù)層做一次字符的拼接,然后再插入數(shù)據(jù)庫(kù)就行了。
這樣的實(shí)現(xiàn)方式較冗余表和索引表的設(shè)計(jì)來說,效率更高,查詢時(shí)可以直接定位到數(shù)據(jù)對(duì)應(yīng)的分片信息,只需 1 次查詢就能獲取想要的結(jié)果。
這樣實(shí)現(xiàn)的缺點(diǎn)是,主鍵值會(huì)變大一些,存儲(chǔ)也會(huì)相應(yīng)變大。但是只要主鍵值是有序的,插入的性能就不會(huì)變差。而通過在主鍵值中保存分片信息,卻可以大大提升后續(xù)的查詢效率,這樣空間換時(shí)間的設(shè)計(jì),總體上看是非常值得的。
實(shí)際上淘寶的訂單號(hào)也是這樣構(gòu)建的
上圖是我的淘寶訂單信息,可以看到,訂單號(hào)的最后 6 位都是 607041,所以可以大概率推測(cè)出:
淘寶訂單表的分片鍵是用戶 ID;
淘寶訂單表,訂單表的主鍵包含用戶 ID,也就是分片信息。這樣通過訂單號(hào)進(jìn)行查詢,可以獲得分片信息,從而查詢 1 個(gè)分片就能得到最終的結(jié)果。
到此,關(guān)于“數(shù)據(jù)庫(kù)分庫(kù)分表后非分片鍵怎么查詢”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!