欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

MySQL按時(shí)間維度對(duì)億級(jí)數(shù)據(jù)表進(jìn)行平滑分表

 更新時(shí)間:2025年08月17日 09:25:40   作者:碼農(nóng)阿豪@新空間  
本文將以一個(gè)真實(shí)的4億數(shù)據(jù)表分表案例為基礎(chǔ),詳細(xì)介紹如何在不影響線上業(yè)務(wù)的情況下,完成按時(shí)間維度分表的完整過程,感興趣的小伙伴可以了解一下

引言

在互聯(lián)網(wǎng)應(yīng)用快速發(fā)展的今天,數(shù)據(jù)量呈現(xiàn)爆炸式增長(zhǎng)。作為后端開發(fā)者,我們常常會(huì)遇到單表數(shù)據(jù)量過億導(dǎo)致的性能瓶頸問題。本文將以一個(gè)真實(shí)的4億數(shù)據(jù)表分表案例為基礎(chǔ),詳細(xì)介紹如何在不影響線上業(yè)務(wù)的情況下,完成按時(shí)間維度分表的完整過程,包含架構(gòu)設(shè)計(jì)、具體實(shí)施方案、Java代碼適配以及注意事項(xiàng)等全方位內(nèi)容。

一、為什么我們需要分表

1.1 單表數(shù)據(jù)量過大的問題

當(dāng)MySQL單表數(shù)據(jù)量達(dá)到4億級(jí)別時(shí),會(huì)面臨諸多挑戰(zhàn):

  • 索引膨脹,B+樹層級(jí)加深,查詢效率下降
  • 備份恢復(fù)時(shí)間呈指數(shù)級(jí)增長(zhǎng)
  • DDL操作(如加字段、改索引)鎖表時(shí)間不可接受
  • 高頻寫入導(dǎo)致鎖競(jìng)爭(zhēng)加劇

1.2 分表方案選型

常見的分表策略有:

  1. 水平分表 :按行拆分,如按ID范圍、哈希、時(shí)間等
  2. 垂直分表 :按列拆分,將不常用字段分離
  3. 分區(qū)表 :MySQL內(nèi)置分區(qū)功能

本文選擇 按時(shí)間水平分表 ,因?yàn)椋?/p>

  • 業(yè)務(wù)查詢大多帶有時(shí)間條件
  • 天然符合數(shù)據(jù)冷熱特征
  • 便于歷史數(shù)據(jù)歸檔

二、分表前的準(zhǔn)備工作

2.1 數(shù)據(jù)評(píng)估分析

-- 分析數(shù)據(jù)時(shí)間分布
SELECT 
    DATE_FORMAT(create_time, '%Y-%m') AS month,
    COUNT(*) AS count
FROM original_table
GROUP BY month
ORDER BY month;

2.2 分表命名規(guī)范設(shè)計(jì)

制定明確的分表命名規(guī)則:

  • 主表:original_table
  • 月度分表:original_table_202301
  • 年度分表:original_table_2023
  • 歸檔表:archive_table_2022

2.3 應(yīng)用影響評(píng)估

檢查所有涉及該表的SQL:

  • 是否都有時(shí)間條件
  • 是否存在跨時(shí)間段的復(fù)雜查詢
  • 事務(wù)是否涉及多表關(guān)聯(lián)

三、分表實(shí)施方案詳解

3.1 方案一:平滑遷移方案(推薦)

第一步:創(chuàng)建分表結(jié)構(gòu)

-- 創(chuàng)建2023年1月的分表(結(jié)構(gòu)完全相同)
CREATE TABLE original_table_202301 LIKE original_table;

-- 為分表添加同樣的索引
ALTER TABLE original_table_202301 ADD INDEX idx_user_id(user_id);

第二步:分批遷移數(shù)據(jù)

使用Java編寫遷移工具:

public class DataMigrator {
    private static final int BATCH_SIZE = 5000;
    
    public void migrateByMonth(String month) throws SQLException {
        String sourceTable = "original_table";
        String targetTable = "original_table_" + month;
        
        try (Connection conn = dataSource.getConnection()) {
            long maxId = getMaxId(conn, sourceTable);
            long currentId = 0;
            
            while (currentId < maxId) {
                String sql = String.format(
                    "INSERT INTO %s SELECT * FROM %s " +
                    "WHERE create_time BETWEEN '%s-01' AND '%s-31' " +
                    "AND id > %d ORDER BY id LIMIT %d",
                    targetTable, sourceTable, month, month, currentId, BATCH_SIZE);
                
                try (Statement stmt = conn.createStatement()) {
                    stmt.executeUpdate(sql);
                    currentId = getLastInsertedId(conn, targetTable);
                }
                
                Thread.sleep(100); // 控制遷移速度
            }
        }
    }
}

第三步:建立聯(lián)合視圖

CREATE VIEW original_table_unified AS
SELECT * FROM original_table_202301 UNION ALL
SELECT * FROM original_table_202302 UNION ALL
...
SELECT * FROM original_table; -- 當(dāng)前表作為最新數(shù)據(jù)

3.2 方案二:觸發(fā)器過渡方案

對(duì)于不能停機(jī)的關(guān)鍵業(yè)務(wù)表:

-- 創(chuàng)建分表
CREATE TABLE original_table_new LIKE original_table;

-- 創(chuàng)建觸發(fā)器
DELIMITER //
CREATE TRIGGER tri_original_table_insert
AFTER INSERT ON original_table
FOR EACH ROW
BEGIN
    IF NEW.create_time >= '2023-01-01' THEN
        INSERT INTO original_table_new VALUES (NEW.*);
    END IF;
END//
DELIMITER ;

四、Java應(yīng)用層適配

4.1 動(dòng)態(tài)表名路由

實(shí)現(xiàn)一個(gè)簡(jiǎn)單的表名路由器:

public class TableRouter {
    private static final DateTimeFormatter MONTH_FORMAT = 
        DateTimeFormatter.ofPattern("yyyyMM");
    
    public static String routeTable(LocalDateTime createTime) {
        String month = createTime.format(MONTH_FORMAT);
        return "original_table_" + month;
    }
}

4.2 MyBatis分表適配

方案一:動(dòng)態(tài)SQL

<select id="queryByTime" resultType="com.example.Entity">
    SELECT * FROM ${tableName}
    WHERE user_id = #{userId}
    AND create_time BETWEEN #{start} AND #{end}
</select>
public List<Entity> queryByTime(Long userId, LocalDate start, LocalDate end) {
    List<String> tableNames = getTableNamesBetween(start, end);
    return tableNames.stream()
        .flatMap(table -> mapper.queryByTime(table, userId, start, end).stream())
        .collect(Collectors.toList());
}

方案二:插件攔截(高級(jí))

實(shí)現(xiàn)MyBatis的Interceptor接口:

@Intercepts(@Signature(type= StatementHandler.class, 
        method="prepare", args={Connection.class, Integer.class}))
public class TableShardInterceptor implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        BoundSql boundSql = ((StatementHandler)invocation.getTarget()).getBoundSql();
        String originalSql = boundSql.getSql();
        
        if (originalSql.contains("original_table")) {
            Object param = boundSql.getParameterObject();
            LocalDateTime createTime = getCreateTime(param);
            String newSql = originalSql.replace("original_table", 
                "original_table_" + createTime.format(MONTH_FORMAT));
            
            resetSql(invocation, newSql);
        }
        
        return invocation.proceed();
    }
}

五、分表后的運(yùn)維管理

5.1 自動(dòng)建表策略

使用Spring Scheduler實(shí)現(xiàn)每月自動(dòng)建表:

@Scheduled(cron = "0 0 0 1 * ?") // 每月1號(hào)執(zhí)行
public void autoCreateNextMonthTable() {
    LocalDate nextMonth = LocalDate.now().plusMonths(1);
    String tableName = "original_table_" + nextMonth.format(MONTH_FORMAT);
    
    jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS " + tableName + 
        " LIKE original_table_template");
}

5.2 數(shù)據(jù)歸檔策略

public void archiveOldData(int keepMonths) {
    LocalDate archivePoint = LocalDate.now().minusMonths(keepMonths);
    String archiveTable = "archive_table_" + archivePoint.getYear();
    
    // 創(chuàng)建歸檔表
    jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS " + archiveTable + 
        " LIKE original_table_template");
    
    // 遷移數(shù)據(jù)
    jdbcTemplate.update("INSERT INTO " + archiveTable + 
        " SELECT * FROM original_table WHERE create_time < ?", 
        archivePoint.atStartOfDay());
    
    // 刪除原數(shù)據(jù)
    jdbcTemplate.update("DELETE FROM original_table WHERE create_time < ?", 
        archivePoint.atStartOfDay());
}

六、踩坑與經(jīng)驗(yàn)總結(jié)

6.1 遇到的典型問題

1.跨分頁(yè)查詢問題 :

解決方案:使用Elasticsearch等中間件預(yù)聚合

2.分布式事務(wù)問題 :

解決方案:避免跨分表事務(wù),或引入Seata等框架

3.全局唯一ID問題 :

解決方案:使用雪花算法(Snowflake)生成ID

6.2 性能對(duì)比數(shù)據(jù)

指標(biāo)分表前分表后
單條查詢平均耗時(shí)320ms45ms
批量寫入QPS1,2003,500
備份時(shí)間6小時(shí)30分鐘

七、未來演進(jìn)方向

  • 分庫(kù)分表 :當(dāng)單機(jī)容量達(dá)到瓶頸時(shí)考慮
  • TiDB遷移 :對(duì)于超大規(guī)模數(shù)據(jù)考慮NewSQL方案
  • 數(shù)據(jù)湖架構(gòu) :將冷數(shù)據(jù)遷移到HDFS等存儲(chǔ)

結(jié)語(yǔ)

MySQL分表是一個(gè)系統(tǒng)工程,需要結(jié)合業(yè)務(wù)特點(diǎn)選擇合適的分片策略。本文介紹的按時(shí)間分表方案,在保證業(yè)務(wù)連續(xù)性的前提下,成功將4億數(shù)據(jù)表的查詢性能提升了7倍。

以上就是MySQL按時(shí)間維度對(duì)億級(jí)數(shù)據(jù)表進(jìn)行平滑分表的詳細(xì)內(nèi)容,更多關(guān)于MySQL分表的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 為什么說MySQL單表數(shù)據(jù)不要超過500萬行

    為什么說MySQL單表數(shù)據(jù)不要超過500萬行

    在本篇文章里小編給大家整理了一篇關(guān)于為什么說MySQL單表數(shù)據(jù)不要超過500萬行的相關(guān)內(nèi)容,有興趣的朋友們閱讀下吧。
    2019-06-06
  • 一步步教你配置MySQL遠(yuǎn)程訪問

    一步步教你配置MySQL遠(yuǎn)程訪問

    這篇文章主要給大家介紹了配置MySQL遠(yuǎn)程訪問的相關(guān)資料,文中介紹的非常詳細(xì),相信對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。
    2017-04-04
  • MySQL按月自動(dòng)設(shè)置表分區(qū)的實(shí)現(xiàn)

    MySQL按月自動(dòng)設(shè)置表分區(qū)的實(shí)現(xiàn)

    本文主要介紹了MySQL按月自動(dòng)設(shè)置表分區(qū)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-03-03
  • MySql中的IFNULL、NULLIF和ISNULL用法詳解

    MySql中的IFNULL、NULLIF和ISNULL用法詳解

    在做項(xiàng)目中發(fā)現(xiàn)MySql里的isnull和mssql里的有點(diǎn)不同。接下來小編通過本文給大家介紹MySql中的IFNULL、NULLIF和ISNULL用法詳解的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-09-09
  • windows安裝MySQL到D盤的超詳細(xì)步驟

    windows安裝MySQL到D盤的超詳細(xì)步驟

    MySQL數(shù)據(jù)庫(kù)作為關(guān)系型數(shù)據(jù)庫(kù)中的佼佼者,因其體積小,速度快,成本低,不僅受到了市場(chǎng)的極大追捧,也受到了廣大程序員的青睞,下面這篇文章主要給大家介紹了關(guān)于windows安裝MySQL到D盤的超詳細(xì)步驟,需要的朋友可以參考下
    2023-03-03
  • mysql設(shè)置默認(rèn)值無效問題及解決

    mysql設(shè)置默認(rèn)值無效問題及解決

    這篇文章主要介紹了mysql設(shè)置默認(rèn)值無效問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • SQLyog錯(cuò)誤號(hào)碼2058最新解決辦法

    SQLyog錯(cuò)誤號(hào)碼2058最新解決辦法

    這篇文章主要給大家介紹了關(guān)于SQLyog錯(cuò)誤號(hào)碼2058的最新解決辦法,使用sqlyog連接數(shù)據(jù)庫(kù)過程中可能會(huì)出現(xiàn)2058錯(cuò)誤,出現(xiàn)的原因是因?yàn)镸YSQL8.0對(duì)密碼的加密方式進(jìn)行了改變,需要的朋友可以參考下
    2023-08-08
  • mysql binlog如何恢復(fù)數(shù)據(jù)到某一時(shí)刻

    mysql binlog如何恢復(fù)數(shù)據(jù)到某一時(shí)刻

    這篇文章主要介紹了mysql binlog如何恢復(fù)數(shù)據(jù)到某一時(shí)刻問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • MySQL數(shù)據(jù)庫(kù)表的CRUD操作

    MySQL數(shù)據(jù)庫(kù)表的CRUD操作

    這篇文章主要介紹了MySQL數(shù)據(jù)庫(kù)表的CRUD操作,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-05-05
  • MySQL?數(shù)據(jù)庫(kù)范式化設(shè)計(jì)理論總結(jié)

    MySQL?數(shù)據(jù)庫(kù)范式化設(shè)計(jì)理論總結(jié)

    這篇文章主要介紹了MySQL?數(shù)據(jù)庫(kù)范式設(shè)計(jì)理論總結(jié),數(shù)據(jù)庫(kù)的規(guī)劃化范式設(shè)計(jì),在邏輯結(jié)構(gòu)上可以讓結(jié)構(gòu)更加細(xì)粒度,容易理解,下文我們就來了解具體的內(nèi)容介紹吧
    2022-04-04

最新評(píng)論