MySQL按時間維度對億級數(shù)據(jù)表進行平滑分表
引言
在互聯(lián)網(wǎng)應用快速發(fā)展的今天,數(shù)據(jù)量呈現(xiàn)爆炸式增長。作為后端開發(fā)者,我們常常會遇到單表數(shù)據(jù)量過億導致的性能瓶頸問題。本文將以一個真實的4億數(shù)據(jù)表分表案例為基礎(chǔ),詳細介紹如何在不影響線上業(yè)務的情況下,完成按時間維度分表的完整過程,包含架構(gòu)設計、具體實施方案、Java代碼適配以及注意事項等全方位內(nèi)容。
一、為什么我們需要分表
1.1 單表數(shù)據(jù)量過大的問題
當MySQL單表數(shù)據(jù)量達到4億級別時,會面臨諸多挑戰(zhàn):
- 索引膨脹,B+樹層級加深,查詢效率下降
- 備份恢復時間呈指數(shù)級增長
- DDL操作(如加字段、改索引)鎖表時間不可接受
- 高頻寫入導致鎖競爭加劇
1.2 分表方案選型
常見的分表策略有:
- 水平分表 :按行拆分,如按ID范圍、哈希、時間等
- 垂直分表 :按列拆分,將不常用字段分離
- 分區(qū)表 :MySQL內(nèi)置分區(qū)功能
本文選擇 按時間水平分表 ,因為:
- 業(yè)務查詢大多帶有時間條件
- 天然符合數(shù)據(jù)冷熱特征
- 便于歷史數(shù)據(jù)歸檔
二、分表前的準備工作
2.1 數(shù)據(jù)評估分析
-- 分析數(shù)據(jù)時間分布
SELECT
DATE_FORMAT(create_time, '%Y-%m') AS month,
COUNT(*) AS count
FROM original_table
GROUP BY month
ORDER BY month;
2.2 分表命名規(guī)范設計
制定明確的分表命名規(guī)則:
- 主表:
original_table - 月度分表:
original_table_202301 - 年度分表:
original_table_2023 - 歸檔表:
archive_table_2022
2.3 應用影響評估
檢查所有涉及該表的SQL:
- 是否都有時間條件
- 是否存在跨時間段的復雜查詢
- 事務是否涉及多表關(guān)聯(lián)
三、分表實施方案詳解
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; -- 當前表作為最新數(shù)據(jù)
3.2 方案二:觸發(fā)器過渡方案
對于不能停機的關(guān)鍵業(yè)務表:
-- 創(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應用層適配
4.1 動態(tài)表名路由
實現(xià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分表適配
方案一:動態(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());
}
方案二:插件攔截(高級)
實現(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();
}
}
五、分表后的運維管理
5.1 自動建表策略
使用Spring Scheduler實現(xiàn)每月自動建表:
@Scheduled(cron = "0 0 0 1 * ?") // 每月1號執(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)驗總結(jié)
6.1 遇到的典型問題
1.跨分頁查詢問題 :
解決方案:使用Elasticsearch等中間件預聚合
2.分布式事務問題 :
解決方案:避免跨分表事務,或引入Seata等框架
3.全局唯一ID問題 :
解決方案:使用雪花算法(Snowflake)生成ID
6.2 性能對比數(shù)據(jù)
| 指標 | 分表前 | 分表后 |
|---|---|---|
| 單條查詢平均耗時 | 320ms | 45ms |
| 批量寫入QPS | 1,200 | 3,500 |
| 備份時間 | 6小時 | 30分鐘 |
七、未來演進方向
- 分庫分表 :當單機容量達到瓶頸時考慮
- TiDB遷移 :對于超大規(guī)模數(shù)據(jù)考慮NewSQL方案
- 數(shù)據(jù)湖架構(gòu) :將冷數(shù)據(jù)遷移到HDFS等存儲
結(jié)語
MySQL分表是一個系統(tǒng)工程,需要結(jié)合業(yè)務特點選擇合適的分片策略。本文介紹的按時間分表方案,在保證業(yè)務連續(xù)性的前提下,成功將4億數(shù)據(jù)表的查詢性能提升了7倍。
以上就是MySQL按時間維度對億級數(shù)據(jù)表進行平滑分表的詳細內(nèi)容,更多關(guān)于MySQL分表的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
為什么說MySQL單表數(shù)據(jù)不要超過500萬行
在本篇文章里小編給大家整理了一篇關(guān)于為什么說MySQL單表數(shù)據(jù)不要超過500萬行的相關(guān)內(nèi)容,有興趣的朋友們閱讀下吧。2019-06-06
MySql中的IFNULL、NULLIF和ISNULL用法詳解
在做項目中發(fā)現(xiàn)MySql里的isnull和mssql里的有點不同。接下來小編通過本文給大家介紹MySql中的IFNULL、NULLIF和ISNULL用法詳解的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-09-09
mysql binlog如何恢復數(shù)據(jù)到某一時刻
這篇文章主要介紹了mysql binlog如何恢復數(shù)據(jù)到某一時刻問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06
MySQL?數(shù)據(jù)庫范式化設計理論總結(jié)
這篇文章主要介紹了MySQL?數(shù)據(jù)庫范式設計理論總結(jié),數(shù)據(jù)庫的規(guī)劃化范式設計,在邏輯結(jié)構(gòu)上可以讓結(jié)構(gòu)更加細粒度,容易理解,下文我們就來了解具體的內(nèi)容介紹吧2022-04-04

