)
SEATA Sharding-JDBC 分庫分表分布式事務(wù)完整實(shí)戰(zhàn)原理場景代碼優(yōu)缺點(diǎn)一、業(yè)務(wù)背景在海量數(shù)據(jù)高并發(fā)微服務(wù)場景中單庫單表會(huì)出現(xiàn)數(shù)據(jù)量大、查詢慢、寫入瓶頸、索引失效等問題行業(yè)普遍采用Sharding-JDBC做分庫分表、讀寫分離解決存儲(chǔ)與性能瓶頸。但分庫分表后會(huì)引入新的核心問題跨分片分布式事務(wù)。Sharding-JDBC 僅負(fù)責(zé) SQL 路由、分片規(guī)則、多數(shù)據(jù)源管理不具備分布式事務(wù)一致性能力。當(dāng)一次業(yè)務(wù)操作涉及多個(gè)分庫、多個(gè)分片表寫入/更新時(shí)本地事務(wù)只能保證單庫一致性必然出現(xiàn)部分分片成功、部分分片失敗的數(shù)據(jù)臟寫問題。因此生產(chǎn)環(huán)境必須引入SEATA AT 模式結(jié)合 Sharding-JDBC解決分庫分表場景下的跨分片分布式事務(wù)一致性問題實(shí)現(xiàn)分表擴(kuò)容事務(wù)可控的高可用架構(gòu)。二、核心適用場景SEATA Sharding-JDBC 組合方案并非所有項(xiàng)目都需要使用精準(zhǔn)適配以下業(yè)務(wù)場景海量數(shù)據(jù)業(yè)務(wù)訂單、支付、流水、用戶行為數(shù)據(jù)單表數(shù)據(jù)量突破千萬/億級(jí)需要水平分表擴(kuò)容高并發(fā)寫入場景秒殺、下單、流水記錄單庫寫入壓力過大需要分庫分擔(dān)IO壓力跨分片事務(wù)場景一次業(yè)務(wù)操作需要同時(shí)修改多個(gè)分片數(shù)據(jù)必須保證多分片同時(shí)成功/同時(shí)回滾高性能、低侵入訴求不想使用TCC硬編碼事務(wù)希望通過框架自動(dòng)實(shí)現(xiàn)分布式事務(wù)讀寫分離分庫分表混合架構(gòu)多數(shù)據(jù)源、多分片復(fù)雜架構(gòu)下的數(shù)據(jù)一致性保障。不適用場景數(shù)據(jù)量小、并發(fā)低、單庫完全夠用的項(xiàng)目過度架構(gòu)增加維護(hù)成本金融級(jí)強(qiáng)一致性核心賬務(wù)優(yōu)先Sharding-TCC模式。三、整合核心原理3.1 組件分工Sharding-JDBCJDBC層中間件負(fù)責(zé)攔截SQL、根據(jù)分片算法路由至對(duì)應(yīng)分庫分表、管理多數(shù)據(jù)源、合并查詢結(jié)果SEATA AT模式負(fù)責(zé)跨分片分布式事務(wù)管控一階段記錄undo鏡像、二階段統(tǒng)一提交/回滾保證多分片數(shù)據(jù)最終一致。3.2 關(guān)鍵整合機(jī)制核心難點(diǎn)原生 SEATA 會(huì)通過DataSourceProxy代理數(shù)據(jù)源實(shí)現(xiàn)事務(wù)管控Sharding-JDBC 內(nèi)部擁有自己的數(shù)據(jù)源分片代理邏輯。兩者直接整合會(huì)出現(xiàn)數(shù)據(jù)源代理沖突、事務(wù)失效。標(biāo)準(zhǔn)解決方案SEATA 嵌套代理 Sharding 數(shù)據(jù)源執(zhí)行鏈路業(yè)務(wù)SQL → SEATA數(shù)據(jù)源代理 → Sharding-JDBC分片路由 → 真實(shí)分庫數(shù)據(jù)源 → 執(zhí)行SQL并生成undo鏡像3.3 分片事務(wù)執(zhí)行流程TM開啟全局事務(wù)業(yè)務(wù)方法添加GlobalTransactional生成全局XIDSQL分片路由Sharding-JDBC 根據(jù)分片鍵路由到一個(gè)或多個(gè)分片庫表一階段預(yù)提交所有分片分支執(zhí)行SQLSEATA 生成每個(gè)分片的undo_log鏡像本地事務(wù)提交、釋放鎖二階段統(tǒng)一處理全局成功批量刪除所有分片undo_log事務(wù)正常結(jié)束全局異常根據(jù)各分片undo鏡像反向回滾所有分片數(shù)據(jù)保證多分片數(shù)據(jù)一致。四、方案優(yōu)缺點(diǎn)分析4.1 核心優(yōu)點(diǎn)零代碼侵入基于SEATA AT模式無需手動(dòng)編寫Try/Confirm/Cancel僅注解開啟事務(wù)適配快速開發(fā)解決分片事務(wù)痛點(diǎn)徹底解決跨庫、跨分片部分成功、部分失敗的數(shù)據(jù)一致性問題高性能高并發(fā)一階段提交、短事務(wù)、無長鎖相比XA、TCC性能更高適配海量分片場景架構(gòu)解耦分片擴(kuò)容由Sharding管控事務(wù)一致性由SEATA管控職責(zé)清晰、擴(kuò)展靈活兼容主流架構(gòu)完美適配SpringCloud、MyBatis、MyBatis-Plus生態(tài)。4.2 現(xiàn)存缺點(diǎn)與生產(chǎn)局限存在臟讀風(fēng)險(xiǎn)AT模式一階段釋鎖高并發(fā)跨分片場景存在短暫臟讀、數(shù)據(jù)覆蓋風(fēng)險(xiǎn)極端場景會(huì)觸發(fā)鏡像校驗(yàn)失敗、事務(wù)重試卡死不支持強(qiáng)隔離級(jí)別無法做到串行化隔離金融核心賬務(wù)場景不適用整合配置復(fù)雜多數(shù)據(jù)源、分片規(guī)則、事務(wù)分組配置不當(dāng)極易出現(xiàn)事務(wù)不生效、代理沖突大事務(wù)風(fēng)險(xiǎn)放大跨多分片大事務(wù)一旦超時(shí)會(huì)出現(xiàn)多分片事務(wù)狀態(tài)割裂修復(fù)成本極高undo日志膨脹分片越多事務(wù)日志越多增加數(shù)據(jù)庫存儲(chǔ)與清理壓力。五、入門實(shí)戰(zhàn)SEATA Sharding-JDBC 完整整合案例5.0 整合核心常用注解及適配業(yè)務(wù)場景SEATA 結(jié)合 Sharding-JDBC 實(shí)現(xiàn)跨分片分布式事務(wù)核心依靠兩類注解實(shí)現(xiàn)事務(wù)管控與業(yè)務(wù)適配不同注解對(duì)應(yīng)不同分片業(yè)務(wù)場景是入門開發(fā)與生產(chǎn)落地的核心重點(diǎn)下面結(jié)合分庫分表場景逐一說明。5.0.1 GlobalTransactionalSEATA 全局事務(wù)注解注解作用開啟跨庫、跨分片全局分布式事務(wù)是 SEATASharding-JDBC 整合的核心注解??山y(tǒng)一管控多個(gè)分片數(shù)據(jù)源的分支事務(wù)保證所有分片數(shù)據(jù)要么全部提交、要么全部回滾徹底解決分庫分表「部分分片成功、部分分片失敗」的一致性問題。核心屬性適配分片場景rollbackFor Exception.class捕獲所有業(yè)務(wù)異常任意分片報(bào)錯(cuò)觸發(fā)全分片回滾適配跨多分片復(fù)雜業(yè)務(wù)timeout自定義全局事務(wù)超時(shí)時(shí)間解決跨多庫分片事務(wù)耗時(shí)過長導(dǎo)致的強(qiáng)制回滾失效問題propagation支持事務(wù)傳播配置適配多服務(wù)嵌套分片事務(wù)場景。貼合使用場景一次業(yè)務(wù)操作路由多個(gè)分庫、多個(gè)分片表的讀寫場景如用戶下單根據(jù)user_id分庫、order_id分表單次下單跨分片寫入跨分片數(shù)據(jù)聯(lián)動(dòng)更新場景如下單同步扣減分片庫存、生成分片流水記錄高并發(fā)分片寫入場景需要嚴(yán)格保證多分片數(shù)據(jù)一致性禁止臟數(shù)據(jù)殘留。5.0.2 TransactionalSpring本地事務(wù)注解注解作用在 SEATASharding 整合架構(gòu)中僅用于單分片本地事務(wù)兜底負(fù)責(zé)單個(gè)數(shù)據(jù)源內(nèi)部的SQL事務(wù)控制不具備跨分片事務(wù)能力必須配合 GlobalTransactional 使用。貼合使用場景單個(gè)分片內(nèi)多條SQL批量執(zhí)行保證單庫數(shù)據(jù)本地一致性作為全局事務(wù)的分支兜底輔助SEATA完成單分片事務(wù)提交與回滾。重要規(guī)范分庫分表跨分片事務(wù)必須以 GlobalTransactional 為入口僅使用 Transactional 會(huì)導(dǎo)致跨分片事務(wù)完全失效。5.0.3 注解組合使用規(guī)范生產(chǎn)標(biāo)準(zhǔn)分片分布式事務(wù)標(biāo)準(zhǔn)寫法入口方法加 GlobalTransactional分支業(yè)務(wù)方法加 Transactional形成「全局事務(wù)統(tǒng)籌本地事務(wù)兜底」的雙層事務(wù)架構(gòu)。5.1 項(xiàng)目依賴5.1 項(xiàng)目依賴核心引入Sharding-JDBC分片依賴、SEATA事務(wù)依賴版本適配SpringCloud Alibaba生態(tài)。!-- sharding-jdbc 分庫分表核心依賴 -- dependency groupIdorg.apache.shardingsphere/groupId artifactIdsharding-jdbc-spring-boot-starter/artifactId version4.1.1/version /dependency !-- seata 分布式事務(wù) -- dependency groupIdio.seata/groupId artifactIdseata-spring-boot-starter/artifactId version1.5.2/version /dependency5.2 數(shù)據(jù)庫準(zhǔn)備模擬分庫場景創(chuàng)建2個(gè)分庫order_db_0、order_db_1每個(gè)庫包含訂單分片表t_order_0 ~ t_order_1所有分片庫必須初始化undo_log表。CREATE TABLE IF NOT EXISTS undo_log ( id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 自增主鍵, branch_id BIGINT(20) NOT NULL COMMENT 分支事務(wù)ID, xid VARCHAR(128) NOT NULL COMMENT 全局事務(wù)ID, context VARCHAR(128) NOT NULL COMMENT 上下文, rollback_info LONGBLOB NOT NULL COMMENT 回滾鏡像信息, log_status INT(11) NOT NULL COMMENT 0正常1已回滾, log_created DATETIME NOT NULL, log_modified DATETIME NOT NULL, PRIMARY KEY (id), UNIQUE KEY ux_undo_log (xid,branch_id) ) ENGINEInnoDB DEFAULT CHARSETutf8 COMMENTSEATA事務(wù)回滾日志表;5.3 核心差異化配置整合專屬與單獨(dú)使用差異對(duì)比SEATA、Sharding-JDBC單獨(dú)使用時(shí)無需特殊適配但二者整合場景存在數(shù)據(jù)源代理、事務(wù)托管沖突必須添加專屬差異化配置也是整合事務(wù)生效的核心關(guān)鍵僅列舉差異配置常規(guī)分片、SEATA基礎(chǔ)配置不再贅述。# SEATA Sharding 整合差異化專屬配置 spring: shardingsphere: # 核心差異1關(guān)閉Sharding原生事務(wù)避免與SEATA事務(wù)沖突 # 單獨(dú)用Sharding默認(rèn)LOCAL整合SEATA必須改為NONE交由SEATA統(tǒng)一托管 transaction: type: NONE # 其余常規(guī)分片規(guī)則、數(shù)據(jù)源配置、SEATA注冊(cè)配置與單獨(dú)使用保持一致無需改動(dòng)配套Java差異化配置唯一代碼差異單獨(dú)使用 Sharding/SEATA 無需手動(dòng)代理數(shù)據(jù)源整合場景必須手動(dòng)用 SEATA 數(shù)據(jù)源代理包裹 Sharding 數(shù)據(jù)源解決多層代理沖突否則跨分片事務(wù)完全不生效。Configuration public class SeataShardingConfig { Bean Primary public DataSource dataSource(ShardingDataSource shardingDataSource) { // 整合專屬差異SEATA嵌套代理Sharding數(shù)據(jù)源 return new DataSourceProxy(shardingDataSource); } }全局事務(wù)模式兜底差異生產(chǎn)環(huán)境統(tǒng)一兜底配置spring.shardingsphere.transaction.typeBASE適配SEATA AT模式禁止XA/LOCAL模式混用規(guī)避事務(wù)協(xié)議沖突。重點(diǎn)關(guān)閉Sharding自帶事務(wù)、適配SEATA事務(wù)代理保證分布式事務(wù)生效。spring: # 關(guān)閉sharding默認(rèn)本地事務(wù)交給seata托管 shardingsphere: props: sql-show: true allow-range-query-without-sharding-key: true # 關(guān)閉sharding原生事務(wù) transaction: type: NONE # 分庫分表規(guī)則配置 datasource: names: ds0,ds1 ds0: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/order_db_0?useSSLfalseserverTimezoneGMT%2B8 username: root password: 123456 ds1: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/order_db_1?useSSLfalseserverTimezoneGMT%2B8 username: root password: 123456 sharding: tables: t_order: actual-data-nodes: ds$-{0..1}.t_order_$-{0..1} database-strategy: inline: sharding-column: user_id algorithm-expression: ds$-{user_id % 2} table-strategy: inline: sharding-column: order_id algorithm-expression: t_order_$-{order_id % 2} # SEATA 分布式事務(wù)配置 seata: enabled: true tx-service-group: default_tx_group service: vgroup-mapping: default_tx_group: default registry: type: nacos nacos: server-addr: 127.0.0.1:8848 config: type: nacos nacos: server-addr: 127.0.0.1:8848手動(dòng)注入SEATA代理數(shù)據(jù)源覆蓋Sharding默認(rèn)數(shù)據(jù)源解決事務(wù)不生效、代理沖突核心問題。Configuration public class SeataShardingConfig { Bean Primary public DataSource dataSource(ShardingDataSource shardingDataSource) { // SEATA代理Sharding數(shù)據(jù)源實(shí)現(xiàn)分片事務(wù)管控 return new DataSourceProxy(shardingDataSource); } }5.5 業(yè)務(wù)實(shí)戰(zhàn)代碼跨分片分布式事務(wù)模擬一次下單跨分片寫入手動(dòng)制造異常測試多分片事務(wù)一致性。Service public class OrderServiceImpl implements OrderService { Resource private OrderMapper orderMapper; // 開啟跨分片分布式事務(wù) Override GlobalTransactional(rollbackFor Exception.class) public void createOrder(Order order) { // 1.插入訂單自動(dòng)根據(jù)分片鍵路由至不同分庫分表 orderMapper.insert(order); // 2.模擬業(yè)務(wù)異常觸發(fā)全分片回滾 int i 1 / 0; } }5.6 效果驗(yàn)證正常無異常所有分片數(shù)據(jù)寫入成功undo_log自動(dòng)清除代碼拋出異常所有已寫入的分片數(shù)據(jù)全部回滾無任何臟數(shù)據(jù)殘留徹底解決原生Sharding-JDBC跨分片部分成功、部分失敗的事務(wù)問題。5.7 分片場景注解搭配高頻坑點(diǎn)匯總生產(chǎn)致命問題SEATA Sharding-JDBC 整合架構(gòu)中注解使用不規(guī)范是跨分片事務(wù)失效、臟讀、回滾重試卡死、數(shù)據(jù)不一致的第一大成因。下面匯總生產(chǎn) 7 類最高頻、最隱蔽的注解搭配錯(cuò)誤明確錯(cuò)誤場景、故障原理與標(biāo)準(zhǔn)規(guī)范。5.7.1 坑點(diǎn)一只寫 GlobalTransactional不加 ShardingSphereTransactionType(BASE)錯(cuò)誤場景跨多分庫、多分片業(yè)務(wù)僅使用 SEATA 全局事務(wù)注解未手動(dòng)指定 Sharding 事務(wù)類型。故障原理Sharding-JDBC 默認(rèn)自帶本地事務(wù)管控若未強(qiáng)制指定BASE適配 SEATA AT 模式分片底層會(huì)走原生本地事務(wù)邏輯??绶制霈F(xiàn)異常時(shí)Sharding 本地事務(wù)無法聯(lián)動(dòng) SEATA 全局事務(wù)部分分片本地提交成功、SEATA 全局回滾指令無法覆蓋分片數(shù)據(jù)最終導(dǎo)致分庫數(shù)據(jù)回滾失效、分片數(shù)據(jù)不一致。正確規(guī)范SEATA AT 整合分片場景統(tǒng)一配置spring.shardingsphere.transaction.typeBASE全局適配 SEATA 事務(wù)托管杜絕原生事務(wù)沖突。5.7.2 坑點(diǎn)二GlobalLock 缺少 Transactional / for update錯(cuò)誤場景分片高并發(fā)讀寫場景僅使用 SEATA 全局鎖注解GlobalLock未搭配本地事務(wù)或行鎖。故障原理GlobalLock 僅控制全局分布式鎖不替代本地事務(wù)與數(shù)據(jù)庫行鎖。缺少本地事務(wù)包裹時(shí)數(shù)據(jù)庫語句自動(dòng)提交、鎖瞬間釋放全局鎖約束失效。高并發(fā)下依然存在跨分片臟讀、數(shù)據(jù)覆蓋問題最終觸發(fā) SEATAundo 鏡像校驗(yàn)失敗、事務(wù)無限重試卡死。正確規(guī)范GlobalLock 必須嵌套 Transactional 本地事務(wù)熱點(diǎn)數(shù)據(jù)查詢更新搭配 for update 行鎖雙重杜絕臟讀。5.7.3 坑點(diǎn)三分片場景 GlobalLock lockKey 未攜帶分片字段錯(cuò)誤場景跨庫分表場景全局鎖 lockKey 僅使用業(yè)務(wù)ID未攜帶分片字段user_id/分庫標(biāo)識(shí)。故障原理Sharding 分片數(shù)據(jù)分散在不同庫、不同表中若鎖 Key 不攜帶分片字段鎖作用域會(huì)錯(cuò)亂。不同分片的不同業(yè)務(wù)數(shù)據(jù)會(huì)共用同一把鎖或本該互斥的分片數(shù)據(jù)無法加鎖跨分片互相臟讀、數(shù)據(jù)覆蓋引發(fā)回滾校驗(yàn)失敗。正確規(guī)范分片場景 lockKey 必須包含「分片鍵唯一業(yè)務(wù)ID」保證鎖粒度精準(zhǔn)綁定單分片單行數(shù)據(jù)隔離跨分片并發(fā)讀寫。5.7.4 坑點(diǎn)四DDL 語句放在 GlobalTransactional 方法內(nèi)錯(cuò)誤場景全局事務(wù)方法中執(zhí)行建表、改表、加索引等 DDL 語句。故障原理MySQL 對(duì) DDL 語句會(huì)強(qiáng)制觸發(fā)自動(dòng)提交本地事務(wù)會(huì)直接打斷 SEATA 一階段鏡像生成流程導(dǎo)致當(dāng)前分支 undo 日志丟失、無回滾快照。全局事務(wù)異常時(shí)SEATA 無數(shù)據(jù)可還原事務(wù)回滾徹底失效產(chǎn)生永久性分片臟數(shù)據(jù)。正確規(guī)范所有 DDL 語句禁止納入 SEATA 全局事務(wù)單獨(dú)執(zhí)行、避開事務(wù)切面。5.7.5 坑點(diǎn)五遺漏內(nèi)層 Transactional 本地事務(wù)錯(cuò)誤場景外層僅添加 GlobalTransactional內(nèi)層分片業(yè)務(wù)方法無任何本地事務(wù)注解。故障原理SEATA 分支 RM 生成 undo_log 鏡像的前置條件是本地事務(wù)上下文生效。內(nèi)層無 Transactional 時(shí)分片 SQL 自動(dòng)提交、無法被 SEATA 切面攔截RM 分支無 undo 快照生成。二階段全局回滾時(shí)無鏡像可還原直接回滾失敗。正確規(guī)范嚴(yán)格遵循「外層 GlobalTransactional 內(nèi)層分片業(yè)務(wù) Transactional」雙層事務(wù)規(guī)范。5.7.6 坑點(diǎn)六混用 XA 與 BASE 事務(wù)類型錯(cuò)誤場景項(xiàng)目同時(shí)存在 XA、BASE 兩種事務(wù)模式或分片配置與 SEATA 模式不匹配。故障原理XA 強(qiáng)一致性事務(wù)與 BASE 最終一致性事務(wù)的分支注冊(cè)、日志機(jī)制、鎖機(jī)制完全不兼容?;煊煤笫聞?wù)協(xié)議沖突SEATA TC 無法正常識(shí)別、注冊(cè)分片分支事務(wù)導(dǎo)致全局事務(wù)創(chuàng)建失敗、分片事務(wù)不生效、提交回滾異常。正確規(guī)范一個(gè)項(xiàng)目統(tǒng)一一種事務(wù)模式分片整合 SEATA AT 必須固定 typeBASE禁止多模式混用。5.7.7 坑點(diǎn)七單純依賴 GlobalTransactional完全放棄 Sharding 事務(wù)管控錯(cuò)誤場景認(rèn)為開啟 SEATA 全局事務(wù)即可隨意關(guān)閉、修改 Sharding 事務(wù)配置。故障原理Sharding-JDBC 承擔(dān)多分片 SQL 路由、多數(shù)據(jù)源調(diào)度若完全取消 Sharding 事務(wù)適配分片多數(shù)據(jù)源執(zhí)行時(shí)序錯(cuò)亂SEATA 無法統(tǒng)一接管多分片分支出現(xiàn)部分分片未納入全局事務(wù)、回滾遺漏的問題。正確規(guī)范必須配置spring.shardingsphere.transaction.typeBASE適配 SEATA不關(guān)閉分片事務(wù)適配能力實(shí)現(xiàn)框架分層托管。六、生產(chǎn)踩坑與解決方案6.1 坑點(diǎn)1整合后分布式事務(wù)不生效原因Sharding-JDBC原生事務(wù)與SEATA數(shù)據(jù)源代理沖突未關(guān)閉Sharding默認(rèn)事務(wù)。解決配置spring.shardingsphere.transaction.typeNONE強(qiáng)制交給SEATA托管。6.2 坑點(diǎn)2跨分片回滾觸發(fā)data validation failed原因高并發(fā)下多分片數(shù)據(jù)被外部線程篡改鏡像校驗(yàn)失敗。解決熱點(diǎn)分片數(shù)據(jù)增加分布式鎖、禁止并發(fā)修改核心業(yè)務(wù)拆分小事務(wù)。6.3 坑點(diǎn)3多分片大事務(wù)超時(shí)回滾失敗原因跨多庫多分片事務(wù)耗時(shí)過長TC強(qiáng)制終止分片狀態(tài)割裂。解決禁止大事務(wù)、拆分分片業(yè)務(wù)調(diào)優(yōu)SEATA超時(shí)時(shí)間增加定時(shí)對(duì)賬兜底。七、全文總結(jié)Sharding-JDBC 解決了海量數(shù)據(jù)的存儲(chǔ)擴(kuò)容、讀寫性能問題但缺失分布式事務(wù)能力SEATA AT 模式解決了跨分片數(shù)據(jù)一致性問題。兩者結(jié)合是目前互聯(lián)網(wǎng)海量數(shù)據(jù)高并發(fā)項(xiàng)目的標(biāo)準(zhǔn)生產(chǎn)架構(gòu)。該方案具備零侵入、高性能、易擴(kuò)展的優(yōu)勢適配絕大多數(shù)電商、流水、賬單、用戶數(shù)據(jù)業(yè)務(wù)但存在AT模式天生的隔離性缺陷金融級(jí)強(qiáng)一致場景需要升級(jí)為Sharding SEATA TCC模式。同時(shí)生產(chǎn)必須遵循小事務(wù)規(guī)范、配合兜底對(duì)賬保證分片事務(wù)百分百可靠。