摘要:而批處理,可以復(fù)用一條簡單,實現(xiàn)批量數(shù)據(jù)的寫入或更新,為系統(tǒng)帶來更低更穩(wěn)定的耗時。批處理的簡要流程說明如下經(jīng)業(yè)務(wù)中實踐,使用批處理方式的寫入或更新,比常規(guī)或性能更穩(wěn)定,耗時也更低。
作者:陳維,轉(zhuǎn)轉(zhuǎn)優(yōu)品技術(shù)部 RD。開篇
世界級的開源分布式數(shù)據(jù)庫 TiDB 自 2016 年 12 月正式發(fā)布第一個版本以來,業(yè)內(nèi)諸多公司逐步引入使用,并取得廣泛認(rèn)可。
對于互聯(lián)網(wǎng)公司,數(shù)據(jù)存儲的重要性不言而喻。在 NewSQL 數(shù)據(jù)庫出現(xiàn)之前,一般采用單機數(shù)據(jù)庫(比如 MySQL)作為存儲,隨著數(shù)據(jù)量的增加,“分庫分表”是早晚面臨的問題,即使有諸如 MyCat、ShardingJDBC 等優(yōu)秀的中間件,“分庫分表”還是給 RD 和 DBA 帶來較高的成本;NewSQL 數(shù)據(jù)庫出現(xiàn)后,由于它不僅有 NoSQL 對海量數(shù)據(jù)的管理存儲能力、還支持傳統(tǒng)關(guān)系數(shù)據(jù)庫的 ACID 和 SQL,所以對業(yè)務(wù)開發(fā)來說,存儲問題已經(jīng)變得更加簡單友好,進(jìn)而可以更專注于業(yè)務(wù)本身。而 TiDB,正是 NewSQL 的一個杰出代表!
站在業(yè)務(wù)開發(fā)的視角,TiDB 最吸引人的幾大特性是:
支持 MySQL 協(xié)議(開發(fā)接入成本低);
100% 支持事務(wù)(數(shù)據(jù)一致性實現(xiàn)簡單、可靠);
無限水平拓展(不必考慮分庫分表)。
基于這幾大特性,TiDB 在業(yè)務(wù)開發(fā)中是值得推廣和實踐的,但是,它畢竟不是傳統(tǒng)的關(guān)系型數(shù)據(jù)庫,以致我們對關(guān)系型數(shù)據(jù)庫的一些使用經(jīng)驗和積累,在 TiDB 中是存在差異的,現(xiàn)主要闡述“事務(wù)”和“查詢”兩方面的差異。
TiDB 事務(wù)和 MySQL 事務(wù)的差異 MySQL 事務(wù)和 TiDB 事務(wù)對比在 TiDB 中執(zhí)行的事務(wù) b,返回影響條數(shù)是 1(認(rèn)為已經(jīng)修改成功),但是提交后查詢,status 卻不是事務(wù) b 修改的值,而是事務(wù) a 修改的值。
可見,MySQL 事務(wù)和 TiDB 事務(wù)存在這樣的差異:
MySQL 事務(wù)中,可以通過影響條數(shù),作為寫入(或修改)是否成功的依據(jù);而在 TiDB 中,這卻是不可行的!
作為開發(fā)者我們需要考慮下面的問題:
同步 RPC 調(diào)用中,如果需要嚴(yán)格依賴影響條數(shù)以確認(rèn)返回值,那將如何是好?
多表操作中,如果需要嚴(yán)格依賴某個主表數(shù)據(jù)更新結(jié)果,作為是否更新(或?qū)懭耄┢渌淼呐袛嘁罁?jù),那又將如何是好?
原因分析及解決方案對于 MySQL,當(dāng)更新某條記錄時,會先獲取該記錄對應(yīng)的行級鎖(排他鎖),獲取成功則進(jìn)行后續(xù)的事務(wù)操作,獲取失敗則阻塞等待。
對于 TiDB,使用 Percolator 事務(wù)模型:可以理解為樂觀鎖實現(xiàn),事務(wù)開啟、事務(wù)中都不會加鎖,而是在提交時才加鎖。參見 這篇文章(TiDB 事務(wù)算法)。
其簡要流程如下:
在事務(wù)提交的 PreWrite 階段,當(dāng)“鎖檢查”失敗時:如果開啟沖突重試,事務(wù)提交將會進(jìn)行重試;如果未開啟沖突重試,將會拋出寫入沖突異常。
可見,對于 MySQL,由于在寫入操作時加上了排他鎖,變相將并行事務(wù)從邏輯上串行化;而對于 TiDB,屬于樂觀鎖模型,在事務(wù)提交時才加鎖,并使用事務(wù)開啟時獲取的“全局時間戳”作為“鎖檢查”的依據(jù)。
所以,在業(yè)務(wù)層面避免 TiDB 事務(wù)差異的本質(zhì)在于避免鎖沖突,即,當(dāng)前事務(wù)執(zhí)行時,不產(chǎn)生別的事務(wù)時間戳(無其他事務(wù)并行)。處理方式為事務(wù)串行化。
TiDB 事務(wù)串行化在業(yè)務(wù)層,可以借助分布式鎖,實現(xiàn)串行化處理,如下:
基于 Spring 和分布式鎖的事務(wù)管理器拓展在 Spring 生態(tài)下,spring-tx 中定義了統(tǒng)一的事務(wù)管理器接口:PlatformTransactionManager,其中有獲取事務(wù)(getTransaction)、提交(commit)、回滾(rollback)三個基本方法;使用裝飾器模式,事務(wù)串行化組件可做如下設(shè)計:
其中,關(guān)鍵點有:
超時時間:為避免死鎖,鎖必須有超時時間;為避免鎖超時導(dǎo)致事務(wù)并行,事務(wù)必須有超時時間,而且鎖超時時間必須大于事務(wù)超時時間(時間差最好在秒級)。
加鎖時機:TiDB 中“鎖檢查”的依據(jù)是事務(wù)開啟時獲取的“全局時間戳”,所以加鎖時機必須在事務(wù)開啟前。
事務(wù)模板接口設(shè)計隱藏復(fù)雜的事務(wù)重寫邏輯,暴露簡單友好的 API:
TiDB 查詢和 MySQL 的差異在 TiDB 使用過程中,偶爾會有這樣的情況,某幾個字段建立了索引,但是查詢過程還是很慢,甚至不經(jīng)過索引檢索。
索引混淆型(舉例)表結(jié)構(gòu):
CREATE TABLE `t_test` ( `id` bigint(20) NOT NULL DEFAULT "0" COMMENT "主鍵id", `a` int(11) NOT NULL DEFAULT "0" COMMENT "a", `b` int(11) NOT NULL DEFAULT "0" COMMENT "b", `c` int(11) NOT NULL DEFAULT "0" COMMENT "c", PRIMARY KEY (`id`), KEY `idx_a_b` (`a`,`b`), KEY `idx_c` (`c`) ) ENGINE=InnoDB;
查詢:如果需要查詢 (a=1 且 b=1)或 c=2 的數(shù)據(jù),在 MySQL 中,sql 可以寫為:SELECT id from t_test where (a=1 and b=1) or (c=2);,MySQL 做查詢優(yōu)化時,會檢索到 idx_a_b 和 idx_c 兩個索引;但是在 TiDB(v2.0.8-9)中,這個 sql 會成為一個慢 SQL,需要改寫為:
SELECT id from t_test where (a=1 and b=1) UNION SELECT id from t_test where (c=2);
小結(jié):導(dǎo)致該問題的原因,可以理解為 TiDB 的 sql 解析還有優(yōu)化空間。
冷熱數(shù)據(jù)型(舉例)表結(jié)構(gòu):
CREATE TABLE `t_job_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT "主鍵id", `job_code` varchar(255) NOT NULL DEFAULT "" COMMENT "任務(wù)code", `record_id` bigint(20) NOT NULL DEFAULT "0" COMMENT "記錄id", `status` tinyint(3) NOT NULL DEFAULT "0" COMMENT "執(zhí)行狀態(tài):0 待處理", `execute_time` bigint(20) NOT NULL DEFAULT "0" COMMENT "執(zhí)行時間(毫秒)", PRIMARY KEY (`id`), KEY `idx_status_execute_time` (`status`,`execute_time`), KEY `idx_record_id` (`record_id`) ) ENGINE=InnoDB COMMENT="異步任務(wù)job"
數(shù)據(jù)說明:
a. 冷數(shù)據(jù),status=1 的數(shù)據(jù)(已經(jīng)處理過的數(shù)據(jù));
b. 熱數(shù)據(jù),status=0 并且 execute_time<= 當(dāng)前時間 的數(shù)據(jù)。
慢查詢:對于熱數(shù)據(jù),數(shù)據(jù)量一般不大,但是查詢頻度很高,假設(shè)當(dāng)前(毫秒級)時間為:1546361579646,則在 MySQL 中,查詢 sql 為:
SELECT * FROM t_job_record where status=0 and execute_time<= 1546361579646
這個在 MySQL 中很高效的查詢,在 TiDB 中雖然也可從索引檢索,但其耗時卻不盡人意(百萬級數(shù)據(jù)量,耗時百毫秒級)。
原因分析:在 TiDB 中,底層索引結(jié)構(gòu)為 LSM-Tree,如下圖:
當(dāng)從內(nèi)存級的 C0 層查詢不到數(shù)據(jù)時,會逐層掃描硬盤中各層;且 merge 操作為異步操作,索引數(shù)據(jù)更新會存在一定的延遲,可能存在無效索引。由于逐層掃描和異步 merge,使得查詢效率較低。
優(yōu)化方式:盡可能縮小過濾范圍,比如結(jié)合異步 job 獲取記錄頻率,在保證不遺漏數(shù)據(jù)的前提下,合理設(shè)置 execute_time 篩選區(qū)間,例如 1 小時,sql 改寫為:
SELECT * FROM t_job_record where status=0 and execute_time>1546357979646 and execute_time<= 1546361579646
優(yōu)化效果:耗時 10 毫秒級別(以下)。
關(guān)于查詢的啟發(fā)在基于 TiDB 的業(yè)務(wù)開發(fā)中,先摒棄傳統(tǒng)關(guān)系型數(shù)據(jù)庫帶來的對 sql 先入為主的理解或經(jīng)驗,謹(jǐn)慎設(shè)計每一個 sql,如 DBA 所提倡:設(shè)計 sql 時務(wù)必關(guān)注執(zhí)行計劃,必要時請教 DBA。
和 MySQL 相比,TiDB 的底層存儲和結(jié)構(gòu)決定了其特殊性和差異性;但是,TiDB 支持 MySQL 協(xié)議,它們也存在一些共同之處,比如在 TiDB 中使用“預(yù)編譯”和“批處理”,同樣可以獲得一定的性能提升。
服務(wù)端預(yù)編譯在 MySQL 中,可以使用 PREPARE stmt_name FROM preparable_stm 對 sql 語句進(jìn)行預(yù)編譯,然后使用 EXECUTE stmt_name [USING @var_name [, @var_name] ...] 執(zhí)行預(yù)編譯語句。如此,同一 sql 的多次操作,可以獲得比常規(guī) sql 更高的性能。
mysql-jdbc 源碼中,實現(xiàn)了標(biāo)準(zhǔn)的 Statement 和 PreparedStatement 的同時,還有一個ServerPreparedStatement 實現(xiàn),ServerPreparedStatement 屬于PreparedStatement的拓展,三者對比如下:
容易發(fā)現(xiàn),PreparedStatement 和 Statement 的區(qū)別主要區(qū)別在于參數(shù)處理,而對于發(fā)送數(shù)據(jù)包,調(diào)用服務(wù)端的處理邏輯是一樣(或類似)的;經(jīng)測試,二者速度相當(dāng)。其實,PreparedStatement 并不是服務(wù)端預(yù)處理的;ServerPreparedStatement 才是真正的服務(wù)端預(yù)處理,速度也較 PreparedStatement 快;其使用場景一般是:頻繁的數(shù)據(jù)庫訪問,sql 數(shù)量有限(有緩存淘汰策略,使用不宜會導(dǎo)致兩次 IO)。
批處理對于多條數(shù)據(jù)寫入,常用 sql 為 insert … values (…),(…);而對于多條數(shù)據(jù)更新,亦可以使用 update … case … when… then… end 來減少 IO 次數(shù)。但它們都有一個特點,數(shù)據(jù)條數(shù)越多,sql 越加復(fù)雜,sql 解析成本也更高,耗時增長可能高于線性增長。而批處理,可以復(fù)用一條簡單 sql,實現(xiàn)批量數(shù)據(jù)的寫入或更新,為系統(tǒng)帶來更低、更穩(wěn)定的耗時。
對于批處理,作為客戶端,java.sql.Statement 主要定義了兩個接口方法,addBatch 和 executeBatch 來支持批處理。
批處理的簡要流程說明如下:
經(jīng)業(yè)務(wù)中實踐,使用批處理方式的寫入(或更新),比常規(guī) insert … values(…),(…)(或 update … case … when… then… end)性能更穩(wěn)定,耗時也更低。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/17883.html
摘要:業(yè)務(wù)延遲和錯誤量對比接入數(shù)據(jù)庫后業(yè)務(wù)邏輯層服務(wù)接口耗時穩(wěn)定無抖動,且沒有發(fā)生丟棄的情況上圖錯誤大多由數(shù)據(jù)訪問層服務(wù)隊列堆積發(fā)生請求丟棄造成。 作者:孫玄,轉(zhuǎn)轉(zhuǎn)公司首席架構(gòu)師;陳東,轉(zhuǎn)轉(zhuǎn)公司資深工程師;冀浩東,轉(zhuǎn)轉(zhuǎn)公司資深 DBA。 公司及業(yè)務(wù)架構(gòu)介紹 轉(zhuǎn)轉(zhuǎn)二手交易網(wǎng) —— 把家里不用的東西賣了變成錢,一個幫你賺錢的網(wǎng)站。由騰訊與 58 集團(tuán)共同投資。為海量用戶提供一個有擔(dān)保、便捷的二手...
閱讀 3146·2021-10-27 14:16
閱讀 2954·2021-09-24 10:33
閱讀 2364·2021-09-23 11:21
閱讀 3283·2021-09-22 15:14
閱讀 887·2019-08-30 15:55
閱讀 1749·2019-08-30 15:53
閱讀 1854·2019-08-29 11:14
閱讀 2244·2019-08-28 18:11