最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

MySQL分庫分表的聚合問題踩坑實(shí)錄

 更新時(shí)間:2026年04月27日 10:34:10   作者:是小崔啊  
MySQL分庫分表是應(yīng)對(duì)大數(shù)據(jù)量和高并發(fā)的核心方案,主要包括垂直分片和水平分片兩種方式,下面這篇文章主要介紹了MySQL分庫分表聚合問題的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下

一:問題的引入

你們公司的訂單表有 2 億數(shù)據(jù),怎么做的分庫分表?

如果我們按用戶 ID(user_id)取模,分了 16 個(gè)庫,每個(gè)庫 64 張表,一共 1024 張表。這樣用戶查自己的訂單特別快,直接定位到具體的表。

此時(shí)問題就來了,那商家(Seller)要查自己店鋪的訂單列表怎么辦? 商家又沒有 user_id,按你的分法,商家查一次豈不是要掃描全部 1024 張表?這系統(tǒng)能不崩?

# ds0 是數(shù)據(jù)源名稱,db0 是實(shí)際的數(shù)據(jù)庫名稱。在配置中:
# ds0 是一個(gè)邏輯名稱,代表一個(gè)數(shù)據(jù)庫連接配置
# db0 是 MySQL 中真實(shí)的數(shù)據(jù)庫名
databaseName: order_system

dataSources:
  ds0:
    url: jdbc:mysql://localhost:3306/db0
    username: root
    password: ******
  ds1:
    url: jdbc:mysql://localhost:3306/db1
    username: root
    password: ******
  # ... 一直到 ds15,共16個(gè)數(shù)據(jù)源

rules:
- !SHARDING
  tables:
    t_order:
      actualDataNodes: ds$->{0..15}.t_order_$->{0..63}
      
      databaseStrategy:
        standard:
          shardingColumn: user_id
          shardingAlgorithmName: database_inline
          
      tableStrategy:
        standard:
          shardingColumn: user_id
          shardingAlgorithmName: table_inline
          
  shardingAlgorithms:
    database_inline:
      type: INLINE
      props:
        algorithm-expression: ds$->{user_id % 16}
    table_inline:
      type: INLINE
      props:
        algorithm-expression: t_order_$->{user_id % 64}

內(nèi)部執(zhí)行情況如下:假設(shè)我們要插入一條訂單數(shù)據(jù):

INSERT INTO t_order (order_id, user_id, amount) 
VALUES (10001, 12345, 299.00);

第1步:ShardingSphere 接收 SQL

應(yīng)用層發(fā)送 SQL 到 ShardingSphere,它作為中間件攔截了這個(gè) SQL。

第2步:分片路由計(jì)算

根據(jù)配置的分片算法計(jì)算:

user_id = 12345

// 計(jì)算目標(biāo)庫:user_id % 16 = 12345 % 16 = 9
// 所以目標(biāo)庫是 ds9

// 計(jì)算目標(biāo)表:user_id % 64 = 12345 % 64 = 57
// 所以目標(biāo)表是 t_order_57

第3步:SQL 路由改寫

ShardingSphere 將原始 SQL 改寫為:

-- 原SQL
INSERT INTO t_order (order_id, user_id, amount) VALUES (10001, 12345, 299.00);

-- 改寫后的SQL
INSERT INTO ds9.t_order_57 (order_id, user_id, amount) VALUES (10001, 12345, 299.00);

第4步:查找數(shù)據(jù)源配置

ShardingSphere 查找 ds9 對(duì)應(yīng)的實(shí)際數(shù)據(jù)庫連接:

ds9:  # 邏輯數(shù)據(jù)源
  url: jdbc:mysql://localhost:3306/db9  # 實(shí)際數(shù)據(jù)庫是 db9
  username: root
  password: ******

第5步:執(zhí)行 SQL

ShardingSphere 連接到 localhost:3306/db9 數(shù)據(jù)庫,執(zhí)行:

INSERT INTO t_order_57 (order_id, user_id, amount) 
VALUES (10001, 12345, 299.00);

第6步:返回結(jié)果

數(shù)據(jù)庫執(zhí)行成功,返回結(jié)果給 ShardingSphere,再返回給應(yīng)用。

邏輯數(shù)據(jù)源實(shí)際數(shù)據(jù)庫包含的表
ds0db0t_order_0 ~ t_order_63
ds1db1t_order_0 ~ t_order_63
ds2db2t_order_0 ~ t_order_63
ds9db9t_order_0 ~ t_order_63
ds15db15t_order_0 ~ t_order_63

二:切開后怎么聚合

所以分庫分表不僅僅是“把數(shù)據(jù)切開”這么簡單,難點(diǎn)永遠(yuǎn)在于 “切開后怎么聚合”

1:什么時(shí)候該分

面試?yán)飭?ldquo;什么時(shí)候分庫分表”,很多人上來就背:“阿里開發(fā)手冊(cè)說單表超過 500 萬行或者 2GB 就要分……”,顯然過于教條了,現(xiàn)在的現(xiàn)在的硬件(SSD + 大內(nèi)存),單表跑個(gè) 1000 萬數(shù)據(jù),索引建好了照樣飛快。

所有真正逼你分庫分表的,通常不是 “存儲(chǔ)容量”,而是 “連接數(shù)” 和 “維護(hù)成本”:

  • 連接數(shù)瓶頸:一個(gè) MySQL 實(shí)例的連接數(shù)是有限的(通常幾千個(gè))。當(dāng)并發(fā) QPS 極高,所有請(qǐng)求都打到一個(gè)主庫,數(shù)據(jù)庫連接池瞬間被打爆,這時(shí)候必須 “分庫” 來分?jǐn)偛l(fā)寫壓力。
  • DDL 痛苦:給一張 5000 萬行的表加個(gè)字段,你試試?鎖表能鎖到你懷疑人生,業(yè)務(wù)直接停擺。這時(shí)候必須 “分表” 來降低單表大小。
DDL:動(dòng)結(jié)構(gòu)(Create、Alter、Drop)
	 操作后自動(dòng)提交,不能回滾,影響數(shù)據(jù)庫結(jié)構(gòu)
DML:動(dòng)數(shù)據(jù)(Insert、Update、Delete)
	 操作表里的數(shù)據(jù),可以回滾(在事務(wù)中)
DQL:查數(shù)據(jù)(Select)
	 最常用的操作,不修改數(shù)據(jù)
DCL:控權(quán)限(Grant、Revoke)
TCL:管事務(wù)(Commit、Rollback)

2:多維查詢的三種解法

2.1:異構(gòu)索引表(雙寫冗余)

既然切分維度不能兼顧,那就 用空間換時(shí)間

  • C 端(用戶視角) :主庫依然按 user_id % 16 分片。用戶查訂單,快如閃電。
  • B 端(商家視角) :再搞一套“商家?guī)?rdquo;,數(shù)據(jù)按 merchant_id % 16 分片。
  • 同步機(jī)制:用戶下單寫入“用戶庫”成功后,異步把數(shù)據(jù)同步一份到“商家?guī)?rdquo;。

這種方案需要配置兩個(gè)獨(dú)立的 ShardingSphere 規(guī)則,一個(gè)用于用戶庫,一個(gè)用于商家?guī)欤?/p>

# shardingsphere-config.yaml
databaseName: order_system
dataSources:
  # 用戶庫數(shù)據(jù)源 (16個(gè))
  user_ds0:
    url: jdbc:mysql://localhost:3306/user_db0
    username: root
    password: ******
  user_ds1:
    url: jdbc:mysql://localhost:3306/user_db1
    username: root
    password: ******
  # ... user_ds2 ~ user_ds15
  # 商家?guī)鞌?shù)據(jù)源 (16個(gè))
  merchant_ds0:
    url: jdbc:mysql://localhost:3306/merchant_db0
    username: root
    password: ******
  merchant_ds1:
    url: jdbc:mysql://localhost:3306/merchant_db1
    username: root
    password: ******
  # ... merchant_ds2 ~ merchant_ds15
rules:
- !SHARDING
  tables:
    # 用戶視角的訂單表配置
    t_order_user:
      actualDataNodes: user_ds$->{0..15}.t_order_user_$->{0..63}
      databaseStrategy:
        standard:
          shardingColumn: user_id
          shardingAlgorithmName: user_db_inline
      tableStrategy:
        standard:
          shardingColumn: user_id
          shardingAlgorithmName: user_table_inline
    # 商家視角的訂單表配置  
    t_order_merchant:
      actualDataNodes: merchant_ds$->{0..15}.t_order_merchant_$->{0..63}
      databaseStrategy:
        standard:
          shardingColumn: merchant_id
          shardingAlgorithmName: merchant_db_inline
      tableStrategy:
        standard:
          shardingColumn: merchant_id
          shardingAlgorithmName: merchant_table_inline
  shardingAlgorithms:
    # 用戶庫分片算法
    user_db_inline:
      type: INLINE
      props:
        algorithm-expression: user_ds$->{user_id % 16}
    user_table_inline:
      type: INLINE
      props:
        algorithm-expression: t_order_user_$->{user_id % 64}
    # 商家?guī)旆制惴?
    merchant_db_inline:
      type: INLINE
      props:
        algorithm-expression: merchant_ds$->{merchant_id % 16}
    merchant_table_inline:
      type: INLINE
      props:
        algorithm-expression: t_order_merchant_$->{merchant_id % 64}
@Service
public class OrderService {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Transactional
    public void createOrder(Order order) {
        // 1. 寫入用戶庫(主庫)
        orderMapper.insertToUserDb(order);
        
        // 2. 發(fā)送消息,異步同步到商家?guī)?
        rabbitTemplate.convertAndSend("order.exchange", "order.sync", order);
    }
}

@Component
@Slf4j
public class OrderSyncConsumer {
    
    @Autowired
    private MerchantOrderMapper merchantOrderMapper;
    
    @RabbitListener(queues = "order.sync.queue")
    public void syncToMerchantDb(Order order) {
        try {
            // 3. 異步寫入商家?guī)?
            merchantOrderMapper.insertToMerchantDb(order);
        } catch (Exception e) {
            log.error("同步訂單到商家?guī)焓?, e);
            // 記錄失敗日志,后續(xù)補(bǔ)償處理
        }
    }
}

如果消息發(fā)送失敗,或者消費(fèi)者掛了,商家?guī)熵M不是一直少訂單?

為了保證最終一致性,我們通常采用 RocketMQ 事務(wù)消息 或 本地事務(wù)表 + 定時(shí)輪詢 模式

  • 事務(wù)消息: 利用 MQ 的半消息機(jī)制,確保‘本地訂單入庫’和‘消息發(fā)送’要么同時(shí)成功,要么同時(shí)失敗。
  • 兜底重試: 配合定時(shí)任務(wù)掃描未確認(rèn)的消息,確保 At Least Once(至少投遞一次) ,保證商家?guī)旖^對(duì)不會(huì)丟單。”

2.2:大寬表 + ElasticSearch

如果運(yùn)營人員要按“下單時(shí)間”、“金額”、“地區(qū)”等奇葩條件篩選,MySQL 分庫分表就徹底歇菜了。

  1. MySQL 只負(fù)責(zé)核心交易鏈路(存、取、改狀態(tài)),按 user_id 分片。
  2. 把數(shù)據(jù)通過 Canal + MQ 實(shí)時(shí)同步到 Elasticsearch (ES)。
  3. 商家查詢、運(yùn)營后臺(tái)查詢、復(fù)雜報(bào)表,全部走 ES。

ES 不是實(shí)時(shí)強(qiáng)一致的(通常有 1 秒延遲)。

如果商家剛收到新訂單通知,點(diǎn)進(jìn)去卻在 ES 查不到,怎么辦?

在代碼層做降級(jí)邏輯。當(dāng) ES 查不到結(jié)果(或數(shù)據(jù)明顯滯后)時(shí),系統(tǒng)會(huì)自動(dòng)降級(jí)回 MySQL 的商家異構(gòu)庫進(jìn)行點(diǎn)查。雖然后端壓力大一點(diǎn),但保證了用戶體驗(yàn)的閉環(huán)。

三:分表后SQL三大坑

1:分表后的全局 ID 怎么生成

絕對(duì)不能用自增主鍵。面試時(shí)推薦答 美團(tuán) Leaf 的號(hào)段模式。相比于雪花算法(Snowflake),它不強(qiáng)依賴機(jī)器時(shí)鐘,不會(huì)因?yàn)闀r(shí)鐘回?fù)軐?dǎo)致 ID 重復(fù),更適合嚴(yán)謹(jǐn)?shù)慕鹑诩?jí)業(yè)務(wù)。

  • 數(shù)據(jù)庫自增ID:就像每次做蛋糕都要去總部申請(qǐng)一個(gè)唯一的編號(hào),做完一個(gè)申請(qǐng)一個(gè)。總部很忙,一旦總部出問題,整個(gè)蛋糕店就得停工。
  • Leaf號(hào)段模式:相當(dāng)于總部一次性批發(fā)給蛋糕店一批連續(xù)編號(hào)的蛋糕券(比如 #1001-#2000)。蛋糕店自己用券賣蛋糕,速度極快。當(dāng)券只剩10%時(shí),就派個(gè)小弟異步去總部領(lǐng)下一批券(比如 #2001-#3000)。這樣,即使總部暫時(shí)聯(lián)系不上,蛋糕店也能靠手里的券繼續(xù)營業(yè)

雙buffer(雙緩存區(qū))是Leaf保證高可用和高性能的秘訣。每個(gè)業(yè)務(wù)在Leaf服務(wù)的內(nèi)存中都有兩個(gè)緩存區(qū)(Segment),當(dāng)前一個(gè)Buffer的ID快用完時(shí),會(huì)異步地去加載下一個(gè)Buffer,確保了發(fā)號(hào)過程行云流水,不會(huì)卡頓

1?? 從美團(tuán)官方GitHub倉庫克隆Leaf項(xiàng)目:https://github.com/Meituan-Dianping/Leaf

2?? 在你的MySQL中創(chuàng)建一個(gè)數(shù)據(jù)庫(例如 leaf_db),執(zhí)行建表語句并初始化

CREATE TABLE `leaf_alloc` (
    `biz_tag` varchar(128) NOT NULL DEFAULT '' COMMENT '業(yè)務(wù)標(biāo)識(shí),如 order',
    `max_id` bigint(20) NOT NULL DEFAULT '1' COMMENT '當(dāng)前已分配的最大ID',
    `step` int(11) NOT NULL COMMENT '號(hào)段步長,即每次批發(fā)的數(shù)量',
    `description` varchar(256) DEFAULT NULL COMMENT '描述',
    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`biz_tag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 初始化訂單服務(wù)的發(fā)號(hào)記錄
INSERT INTO leaf_alloc(biz_tag, max_id, step, description) 
VALUES ('order', 1, 2000, '訂單ID發(fā)號(hào)器');

3?? 修改 leaf-server/src/main/resources/leaf.properties 文件,開啟號(hào)段模式并配置數(shù)據(jù)庫連接

# 開啟號(hào)段模式
leaf.segment.enable=true
# 關(guān)閉雪花算法模式(兩者不能同時(shí)開啟)
leaf.snowflake.enable=false
# 數(shù)據(jù)庫連接配置
leaf.jdbc.url=jdbc:mysql://你的數(shù)據(jù)庫IP:3306/leaf_db?useUnicode=true&characterEncoding=utf8
leaf.jdbc.username=你的用戶名
leaf.jdbc.password=你的密碼

4?? 啟動(dòng)Leaf服務(wù)。你可以將它作為一個(gè)獨(dú)立的發(fā)號(hào)中心集群來部署,以提高可用性

2:分表后,怎么做深分頁(Limit 10000, 10)

分布式數(shù)據(jù)庫的‘內(nèi)存殺手’。中間件需要去每個(gè)分片取前 10010 條,聚合排序,性能極差

  • 業(yè)務(wù)規(guī)避: 禁止跳頁,只允許‘下一頁’。
  • Seek 游標(biāo)法: 利用 WHERE id < last_id LIMIT 10 的方式查詢,利用索引避開 Offset 掃描。
  • ES Scroll: 如果是極其復(fù)雜的深分頁,直接走 ES。”

3:擴(kuò)容怎么辦?原本 16 個(gè)庫不夠用了,要擴(kuò)到 32 個(gè)

采用 2 倍擴(kuò)容(Scale Out) ,且必須配合 在線數(shù)據(jù)遷移

  • 全量同步: 將舊庫數(shù)據(jù)全量搬運(yùn)到新庫。
  • 增量追平: 利用 Canal/DTS 追平遷移期間產(chǎn)生的增量數(shù)據(jù)。
  • 數(shù)據(jù)校驗(yàn): 全量比對(duì)一致后。
  • 切流: 短暫切斷寫入(秒級(jí)),更新路由規(guī)則,將流量切到新庫。

總結(jié) 

到此這篇關(guān)于MySQL分庫分表聚合問題踩坑實(shí)錄的文章就介紹到這了,更多相關(guān)MySQL分庫分表聚合內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • MySQL中表索引定義的方法與介紹

    MySQL中表索引定義的方法與介紹

    這篇文章主要給大家介紹了關(guān)于MySQL中表索引定義的方法與介紹,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用MySQL具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • MySQL的讀寫分離配置最佳實(shí)踐

    MySQL的讀寫分離配置最佳實(shí)踐

    MySQL讀寫分離是一種常見的數(shù)據(jù)庫優(yōu)化策略,特別是在高并發(fā)和大數(shù)據(jù)量的場景下,它可以有效提高系統(tǒng)的性能和穩(wěn)定性,這篇文章主要介紹了MySQL讀寫分離配置的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-11-11
  • Windows下MySQL5.7.18安裝教程

    Windows下MySQL5.7.18安裝教程

    這篇文章主要為大家詳細(xì)介紹了Windows下MySQL5.7.18安裝教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • mysql仿oracle的decode效果查詢

    mysql仿oracle的decode效果查詢

    今天遇到群里發(fā)的一個(gè)問題,覺得有點(diǎn)意思,發(fā)上來看一下
    2011-04-04
  • MySQL 文本文件的導(dǎo)入導(dǎo)出數(shù)據(jù)的方法

    MySQL 文本文件的導(dǎo)入導(dǎo)出數(shù)據(jù)的方法

    但有時(shí)為了更快速地插入大批量數(shù)據(jù)或交換數(shù)據(jù),需要從文本中導(dǎo)入數(shù)據(jù)或?qū)С鰯?shù)據(jù)到文本。下面的具體的方法大家可以參考下。多測試。
    2009-11-11
  • 一篇文章搞懂MySQL加鎖機(jī)制

    一篇文章搞懂MySQL加鎖機(jī)制

    線程安全就是多線程訪問時(shí),采用了加鎖機(jī)制,當(dāng)一個(gè)線程訪問該類的某個(gè)數(shù)據(jù)時(shí)進(jìn)行保護(hù),下面這篇文章主要給大家介紹了關(guān)于MySQL加鎖機(jī)制的相關(guān)資料,需要的朋友可以參考下
    2022-01-01
  • JMeter壓測Mysql教程分享

    JMeter壓測Mysql教程分享

    文章介紹了如何使用JMeter進(jìn)行數(shù)據(jù)庫壓力測試的步驟,包括安裝JMeter、下載并配置MySQL JDBC驅(qū)動(dòng)、設(shè)置測試計(jì)劃、添加JDBC連接配置元件、添加JDBC請(qǐng)求取樣器以及可選的監(jiān)聽器配置,最后,文章指導(dǎo)如何運(yùn)行測試并分析結(jié)果,以評(píng)估數(shù)據(jù)庫在不同壓力下的性能表現(xiàn)
    2024-11-11
  • MySQL數(shù)據(jù)庫的高可用方案總結(jié)

    MySQL數(shù)據(jù)庫的高可用方案總結(jié)

    這篇文章主要針對(duì)MySQL數(shù)據(jù)庫的高可用方案進(jìn)行詳細(xì)總結(jié),高可用架構(gòu)對(duì)于互聯(lián)網(wǎng)服務(wù)基本是標(biāo),本文是對(duì)各種方案的總結(jié),感興趣的小伙伴們可以參考一下
    2016-05-05
  • mysql計(jì)算2個(gè)日期的月份差和天數(shù)差實(shí)現(xiàn)方式

    mysql計(jì)算2個(gè)日期的月份差和天數(shù)差實(shí)現(xiàn)方式

    文章主要講述如何計(jì)算兩個(gè)日期之間的相隔月份和天數(shù),強(qiáng)調(diào)了日期格式的重要性,并提供了一個(gè)計(jì)算天數(shù)的示例
    2025-11-11
  • MySQL 服務(wù)器參數(shù)說明及查看 設(shè)置方法

    MySQL 服務(wù)器參數(shù)說明及查看 設(shè)置方法

    MySQL 服務(wù)器參數(shù)說明及查看,設(shè)置方法,對(duì)于mysql不是很熟悉的朋友,可以參考下。
    2009-03-03

最新評(píng)論

陇西县| 呼伦贝尔市| 云南省| 青海省| 益阳市| 壶关县| 湛江市| 泸水县| 平乡县| 福清市| 潼关县| 衡阳县| 阳西县| 阳朔县| 祁东县| 临颍县| 塔河县| 天津市| 巫山县| 河北省| 区。| 宜良县| 栖霞市| 长丰县| 武宣县| 广饶县| 庆安县| 泸西县| 冷水江市| 富锦市| 安多县| 通州市| 贞丰县| 深州市| 尉氏县| 永仁县| 五常市| 宜章县| 合川市| 油尖旺区| 黄冈市|