MySQL 5.7 表分区使用说明(视频系统实战)

文章目录

    • [1. 表分区的基本概念和原理](#1. 表分区的基本概念和原理)
    • [2. MySQL 5.7.18 支持的分区类型与语法](#2. MySQL 5.7.18 支持的分区类型与语法)
      • [2.1 RANGE 分区](#2.1 RANGE 分区)
      • [2.2 LIST 分区](#2.2 LIST 分区)
      • [2.3 HASH 分区](#2.3 HASH 分区)
      • [2.4 KEY 分区](#2.4 KEY 分区)
    • [3. 实际应用示例(结合视频系统表结构)](#3. 实际应用示例(结合视频系统表结构))
      • [3.1 现有短视频评论表结构(单表)](#3.1 现有短视频评论表结构(单表))
      • [3.2 视频评论表:按 `video_id` HASH 分区示例](#3.2 视频评论表:按 video_id HASH 分区示例)
      • [3.3 视频点赞表:按 `video_id` HASH 分区](#3.3 视频点赞表:按 video_id HASH 分区)
    • [4. 分区键的选择原则和最佳实践](#4. 分区键的选择原则和最佳实践)
    • [5. 分区维护操作(添加、删除、合并)](#5. 分区维护操作(添加、删除、合并))
      • [5.1 RANGE 分区维护(视频订单表示例)](#5.1 RANGE 分区维护(视频订单表示例))
      • [5.2 HASH 分区维护(视频评论表示例)](#5.2 HASH 分区维护(视频评论表示例))
    • [6. 分区与索引的关系及性能影响](#6. 分区与索引的关系及性能影响)
    • [7. 使用过程中的重要注意事项和常见陷阱](#7. 使用过程中的重要注意事项和常见陷阱)
    • [8. 分区与分表策略的区别和选择建议](#8. 分区与分表策略的区别和选择建议)
    • [9. 监控和管理分区表的方法](#9. 监控和管理分区表的方法)
    • [10. 性能优化建议和案例分析(高并发视频场景)](#10. 性能优化建议和案例分析(高并发视频场景))
    • [11. 如何确认表分区是否生效](#11. 如何确认表分区是否生效)

本文基于 MySQL 5.7.18 ,结合当前项目中的 短视频系统 (数据库 short_video、表如 video_commentvideo_likevideo_collect 等),说明如何在高并发视频场景下正确使用 MySQL 表分区,并给出实用 SQL 示例和性能建议。


1. 表分区的基本概念和原理

  • 什么是表分区(Partitioning)

    • 逻辑上仍然是一张表:对应用来说,表名不变,SQL 写法不变。
    • 物理上拆成多个分区:每个分区类似一张"子表/子文件",数据按某个规则(分区键)分布到不同分区文件中。
  • 核心作用

    • 提升大表访问性能 :通过 分区裁剪(Partition Pruning),只扫描命中的分区而不是全表。
    • 提高维护效率 :可以通过 DROP PARTITION / TRUNCATE PARTITION 快速删除或清空一段数据(比如历史数据)。
    • 缓解单表数据量过大问题:整体行数不变,但每个分区的数据量更小,索引更小,缓存命中率更高。
  • 与索引的关系(关键点)

    • 分区后,每个分区有自己的 本地索引,优化器会先"选分区",再在分区内走索引。
    • MySQL 5.7 的硬约束:所有唯一索引(包括主键)都必须包含分区键,否则分区表的 DDL 会失败。

2. MySQL 5.7.18 支持的分区类型与语法

2.1 RANGE 分区

  • 适用场景:按时间区间或数值区间做冷热数据拆分,例如视频订单、充值记录按日期分区。

  • 示例:按订单时间 RANGE 分区

sql 复制代码
CREATE TABLE video_order (
    id          BIGINT(20) NOT NULL,
    user_id     BIGINT(20) NOT NULL,
    video_id    BIGINT(20) NOT NULL,
    order_no    VARCHAR(64) NOT NULL,
    amount      DECIMAL(10,2) NOT NULL,
    status      TINYINT(1) NOT NULL,
    order_time  DATETIME NOT NULL,
    PRIMARY KEY (id, order_time),          -- PK 必须包含分区键
    KEY idx_user_time (user_id, order_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE (TO_DAYS(order_time)) (
    PARTITION p2024 VALUES LESS THAN (TO_DAYS('2025-01-01')),
    PARTITION p2025 VALUES LESS THAN (TO_DAYS('2026-01-01')),
    PARTITION pmax  VALUES LESS THAN MAXVALUE
);

注意:RANGE 分区表达式必须返回整数,常见写法为 TO_DAYS(order_time)YEAR(order_time) 等。

2.2 LIST 分区

  • 适用场景 :按 离散枚举值 分区,比如不同的视频业务线、内容类型、地区。

  • 示例:按内容类型 LIST 分区

sql 复制代码
CREATE TABLE video_content_stat (
    id           BIGINT(20) NOT NULL,
    content_type TINYINT(1) NOT NULL,   -- 1-短视频,2-图片,3-直播
    pv_count     BIGINT(20) NOT NULL,
    uv_count     BIGINT(20) NOT NULL,
    stat_date    DATE NOT NULL,
    PRIMARY KEY (id, content_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY LIST (content_type) (
    PARTITION p_short_video VALUES IN (1),
    PARTITION p_image       VALUES IN (2),
    PARTITION p_live        VALUES IN (3)
);

2.3 HASH 分区

  • 适用场景 :按 哈希均匀打散数据,适合没有明显时间维度、写入非常密集的表,如视频点赞、评论、收藏等。

  • 示例:按 video_id HASH 分区的视频评论表

sql 复制代码
CREATE TABLE video_comment_p (
    id              BIGINT(20) NOT NULL,
    video_id        BIGINT(20) NOT NULL,
    user_id         BIGINT(20) NOT NULL,
    parent_id       BIGINT(20) DEFAULT 0,
    root_id         BIGINT(20) DEFAULT 0,
    reply_to_user_id BIGINT(20) DEFAULT 0,
    content         TEXT NOT NULL,
    like_count      INT(11) DEFAULT 0,
    reply_count     INT(11) DEFAULT 0,
    status          TINYINT DEFAULT 1,
    create_time     DATETIME NOT NULL,
    update_time     DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted         TINYINT DEFAULT 0,
    PRIMARY KEY (id, video_id),           -- 主键包含分区键
    KEY idx_video_id (video_id),
    KEY idx_user_id (user_id),
    KEY idx_parent_id (parent_id),
    KEY idx_root_id (root_id),
    KEY idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY HASH(video_id)
PARTITIONS 16;

PARTITION BY HASH(expr) 中 expr 必须是整数表达式,分区数量 PARTITIONS N 一般选择 16 / 32 等 2 的幂。

2.4 KEY 分区

  • 与 HASH 类似,但使用 MySQL 内置 hash 函数,对字段本身做 hash,不要求是整数。

  • 适用场景 :需要对字符串字段分区时,如按 order_nouser_uuid

  • 示例:按订单号 KEY 分区

sql 复制代码
CREATE TABLE video_order_key (
    id         BIGINT(20) NOT NULL,
    user_id    BIGINT(20) NOT NULL,
    order_no   VARCHAR(64) NOT NULL,
    order_time DATETIME NOT NULL,
    PRIMARY KEY (id, order_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY KEY (order_no)
PARTITIONS 16;

3. 实际应用示例(结合视频系统表结构)

3.1 现有短视频评论表结构(单表)

当前 short_video 数据库中,video_comment 为单表(见 src/main/resources/init.sql):

sql 复制代码
CREATE TABLE video_comment (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    video_id BIGINT NOT NULL,
    user_id BIGINT NOT NULL,
    parent_id BIGINT DEFAULT 0,
    root_id BIGINT DEFAULT 0,
    reply_to_user_id BIGINT DEFAULT 0,
    content TEXT NOT NULL,
    like_count INT DEFAULT 0,
    reply_count INT DEFAULT 0,
    status TINYINT DEFAULT 1,
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted TINYINT DEFAULT 0,
    INDEX idx_video_id (video_id),
    INDEX idx_user_id (user_id),
    INDEX idx_parent_id (parent_id),
    INDEX idx_root_id (root_id),
    INDEX idx_like_count (like_count),
    INDEX idx_create_time (create_time),
    INDEX idx_video_like_time (video_id, like_count DESC, create_time DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

当数据量上亿级时,单表访问和维护成本会明显升高,可以通过 HASH 分区 等分区策略来解决。

3.2 视频评论表:按 video_id HASH 分区示例

如果只使用 MySQL 分区(不引入或不依赖中间件),可以设计为:

sql 复制代码
CREATE TABLE video_comment_p (
    id BIGINT(20) NOT NULL,
    video_id BIGINT(20) NOT NULL,
    user_id BIGINT(20) NOT NULL,
    parent_id BIGINT(20) DEFAULT 0,
    root_id BIGINT(20) DEFAULT 0,
    reply_to_user_id BIGINT(20) DEFAULT 0,
    content TEXT NOT NULL,
    like_count INT(11) DEFAULT 0,
    reply_count INT(11) DEFAULT 0,
    status TINYINT DEFAULT 1,
    create_time DATETIME NOT NULL,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted TINYINT DEFAULT 0,
    PRIMARY KEY (id, video_id),                   -- PK 包含分区键
    KEY idx_video_id (video_id),
    KEY idx_user_id (user_id),
    KEY idx_parent_id (parent_id),
    KEY idx_root_id (root_id),
    KEY idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY HASH(video_id)
PARTITIONS 16;
  • 访问模式:
    • 热门场景是"查看某个视频的评论列表",SQL 通常包含 WHERE video_id = ?,与分区键一致。
    • 同一个视频的所有评论都落在同一个分区,统计、排序都在单分区内完成。

3.3 视频点赞表:按 video_id HASH 分区

可参考 video_like 的行数预估(见《单库分表优化方案.md》):

sql 复制代码
CREATE TABLE video_like_p (
    id BIGINT(20) NOT NULL,
    user_id BIGINT(20) NOT NULL,
    video_id BIGINT(20) NOT NULL,
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id, video_id),                   -- PK + 分区键
    UNIQUE KEY uk_user_video (user_id, video_id),
    KEY idx_video_id (video_id),
    KEY idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY HASH(video_id)
PARTITIONS 16;
  • 查询示例:
sql 复制代码
-- 查询某个视频的点赞列表(命中一个分区)
SELECT * FROM video_like_p
WHERE video_id = 12345
ORDER BY create_time DESC
LIMIT 50;

4. 分区键的选择原则和最佳实践

  1. 必须命中高频查询条件

    • 用户维度:user_id(如"我点赞的视频"、"我收藏的视频")。
    • 视频维度:video_id(如"某个视频的评论/点赞/收藏列表")。
  2. 分布要尽量均匀,避免数据倾斜

    • user_id % Nvideo_id % N 这类哈希一般比较均匀。
    • 使用 HASH/KEY 分区时,不要选取值域很小的枚举字段,否则容易集中到少数分区。
  3. 必须包含在所有唯一索引和主键中

    • MySQL 5.7 分区表硬约束:主键和所有唯一索引的列集合必须包含分区键。
    • 示例:如果按 video_id 分区,主键应为 (id, video_id)(video_id, id),唯一键 uk_user_video (user_id, video_id) 也包含 video_id
  4. 避免使用频繁变化的字段

    • 分区键不宜为 statusdeleted 等状态字段,防止频繁跨分区移动数据。
  5. RANGE 分区优先使用时间字段

    • 视频订单、充值记录、埋点日志等,适合按 order_timelog_time 做 RANGE 分区,方便历史归档。

5. 分区维护操作(添加、删除、合并)

5.1 RANGE 分区维护(视频订单表示例)

添加新分区(新增一年)

sql 复制代码
ALTER TABLE video_order
ADD PARTITION (
    PARTITION p2026 VALUES LESS THAN (TO_DAYS('2027-01-01'))
);

合并历史分区

sql 复制代码
ALTER TABLE video_order
REORGANIZE PARTITION p2024, p2025 INTO (
    PARTITION p_history VALUES LESS THAN (TO_DAYS('2026-01-01'))
);

删除历史分区(快速清理旧数据)

sql 复制代码
ALTER TABLE video_order
DROP PARTITION p_history;

以上操作为 DDL,执行期间会锁表,应放在业务低峰期或在备库上操作后切换。

5.2 HASH 分区维护(视频评论表示例)

增加分区数量

sql 复制代码
-- 从16分区增加到24分区(追加 8 个分区)
ALTER TABLE video_comment_p
ADD PARTITION PARTITIONS 8;

减少分区数量

sql 复制代码
-- 从24分区合并回16分区(合并 8 个分区)
ALTER TABLE video_comment_p
COALESCE PARTITION 8;

HASH/KEY 分区的扩缩分区会触发数据重新分布,是重度 DDL,务必提前评估耗时和锁表影响。


6. 分区与索引的关系及性能影响

  1. 本地索引(Local Index)

    • MySQL 5.7 只有本地索引:每个分区内有独立 B+ 树索引,只覆盖本分区数据。
    • 唯一约束在"分区键 + 其他字段"维度上保证唯一性。
  2. 分区裁剪 + 索引结合

    • 查询条件包含分区键时,优化器可以先裁剪分区,再在分区内使用索引:
    sql 复制代码
    EXPLAIN PARTITIONS
    SELECT * FROM video_comment_p
    WHERE video_id = 12345
    ORDER BY create_time DESC
    LIMIT 20;
    • 如果 partitions 列只显示一个分区,说明分区裁剪已生效。
  3. 缺少分区键会导致扫描所有分区

    sql 复制代码
    -- 不包含 video_id,可能扫描所有分区
    SELECT * FROM video_comment_p WHERE user_id = 1001;
    • 虽然 user_id 有索引,但仍然需要在每个分区的 idx_user_id 上做索引扫描,总体代价较大。
  4. 高并发下的效果

    • 优点:单分区索引更小,缓存更容易命中;随机 IO 减少。
    • 风险:分区过多时,元数据和文件句柄开销增大,一般建议分区数控制在几十级别(16 / 32 / 64)。

7. 使用过程中的重要注意事项和常见陷阱

  1. 分区键不在主键/唯一索引中(硬错误)

    sql 复制代码
    CREATE TABLE t (
        id BIGINT NOT NULL PRIMARY KEY,
        video_id BIGINT NOT NULL,
        UNIQUE KEY uk_video (video_id)
    )
    PARTITION BY HASH(video_id)
    PARTITIONS 16;
    • 上述建表会失败,必须改为:
    sql 复制代码
    PRIMARY KEY (id, video_id)
  2. 查询未包含分区键导致全分区扫描

    • 一定要在接口规范里要求:访问分区表时,where 条件必须尽量包含分区键(video_iduser_id)。
  3. 对分区键使用函数包装,导致无法裁剪

    • 如果分区键为 TO_DAYS(order_time),而查询写成:
    sql 复制代码
    WHERE DATE(order_time) = '2025-01-12'
    • 优化器可能无法根据表达式反推命中分区,进而扫描多个分区。
    • 推荐写法:
    sql 复制代码
    WHERE order_time >= '2025-01-12 00:00:00'
      AND order_time <  '2025-01-13 00:00:00';
  4. 在线改分区是重度操作

    • ALTER TABLE ... PARTITION BY ... 往往会重建整表,锁表时间长。
    • 实战建议:
      • 新建分区表 + 双写 + 切流 / rename;
      • 或在备库改造后,通过主备切换上线。

8. 分区与分表策略的区别和选择建议

  1. MySQL 表分区

    • 单库内一张逻辑表拆成多个分区,所有分区都在同一个 MySQL 实例、同一个库中。
    • 适合:单机资源还比较充裕,主要问题是单表过大、历史数据清理困难。
  2. 单库分表策略(拆成多张物理表)

    • video_likevideo_comment 等拆成 video_like_0 ~ video_like_15 多张物理表,由应用或中间层按照路由规则负责 SQL 路由和结果归并。
    • 当前项目的短视频系统已经采用单库分表方案(见《单库分表优化方案.md》),适合:
      • 总数据量大(如点赞 3 亿、评论 1 亿);
      • 未来有分库扩展需求。
  3. 选择建议(结合当前项目)

    • 对于核心的 点赞 / 评论 / 收藏 等高并发表,可以优先采用单库分表策略,方便未来平滑扩容到多库。
    • 对于 订单、日志、埋点等时间序列数据 ,可以在单库内采用 RANGE 分区 ,利用 DROP PARTITION 做快速归档。

9. 监控和管理分区表的方法

  1. 查看分区元信息
sql 复制代码
SELECT
    TABLE_SCHEMA, TABLE_NAME, PARTITION_NAME,
    PARTITION_METHOD, PARTITION_EXPRESSION,
    TABLE_ROWS, DATA_LENGTH/1024/1024 AS data_mb
FROM information_schema.PARTITIONS
WHERE TABLE_SCHEMA = 'short_video'
  AND TABLE_NAME   = 'video_comment_p';
  1. 查看建表语句
sql 复制代码
SHOW CREATE TABLE video_comment_p\G
  1. 分析执行计划命中哪些分区
sql 复制代码
EXPLAIN PARTITIONS
SELECT * FROM video_comment_p
WHERE video_id = 12345
ORDER BY create_time DESC
LIMIT 20;

10. 性能优化建议和案例分析(高并发视频场景)

  1. 保持单分区数据量在可控范围

    • 参考已有经验:单表/单分区数据量超过 1000 万行后,查询和写入性能会明显下降。
    • 视频点赞 3 亿、评论 1 亿等场景下,将数据打散到 16 个分区/分表可以明显缓解单点压力。
  2. 结合缓存和异步写入

    • 热点计数(点赞数、评论数)推荐放在 Redis 中,异步回写 MySQL。
    • 批量写入或峰值写入可以通过 MQ 异步落库,减轻单分区的写入压力。
  3. 案例:视频评论表从单表到 HASH 分区

    • 假设单表 video_comment 1 亿行,其中某些热门视频有 10 万级评论:
      • 在单表中,这些数据和其他视频混在一个大索引里,热点视频的查询会和冷门数据争用索引和缓存。
      • 使用 PARTITION BY HASH(video_id) 后:
        • 总数据仍为 1 亿,但拆成 16 个分区,每个 ~625 万行;
        • 查询 WHERE video_id = ? 时只访问一个分区的索引树,延迟大幅下降。
  4. 案例:视频订单表使用 RANGE 分区做历史归档

    • 清理一年前订单:
      • 传统方式:DELETE FROM video_order WHERE order_time < '2025-01-01',大量 undo/redo、锁表严重;

      • 使用 RANGE 分区后,只需:

        sql 复制代码
        ALTER TABLE video_order DROP PARTITION p2024;

        本质是删除一个分区表空间,速度极快,对线上影响较小。


11. 如何确认表分区是否生效

从以下三个维度检查,基本可以确认"表是否是分区表 & 查询是否命中分区裁剪":

  1. 确认表本身已经是分区表

    • 方式一:查询 information_schema.PARTITIONS 中是否有记录:
    sql 复制代码
    SELECT
        TABLE_SCHEMA, TABLE_NAME, PARTITION_NAME,
        PARTITION_METHOD, PARTITION_EXPRESSION,
        TABLE_ROWS
    FROM information_schema.PARTITIONS
    WHERE TABLE_SCHEMA = 'short_video'
      AND TABLE_NAME   = 'video_comment_p';
    • 如果 PARTITION_NAME 不为 NULL,且能看到多行记录,说明该表已经按 PARTITION_METHOD(如 HASH/RANGE)成功分区。
    • 方式二:SHOW CREATE TABLE 中是否有 PARTITION BY ...
    sql 复制代码
    SHOW CREATE TABLE video_comment_p\G
    • 在输出中看到类似:
    sql 复制代码
    PARTITION BY HASH (video_id)
    PARTITIONS 16

    即可确认该表的 DDL 分区配置已经生效。

  2. 确认查询是否命中分区裁剪(EXPLAIN PARTITIONS)

    • 对典型查询使用 EXPLAIN PARTITIONS 查看 partitions 列:
    sql 复制代码
    EXPLAIN PARTITIONS
    SELECT * FROM video_comment_p
    WHERE video_id = 12345
    ORDER BY create_time DESC
    LIMIT 20;
    • 如果 partitions 列只显示 一个或少数几个分区名 (例如 p0),说明优化器根据 video_id 成功做了分区裁剪。
    • 对比一个没有分区键条件的查询:
    sql 复制代码
    EXPLAIN PARTITIONS
    SELECT * FROM video_comment_p
    WHERE user_id = 1001;
    • 如果 partitions 列显示为 p0,p1,...,p15(或 NULL 表示所有分区),说明需要扫描所有分区,即使有 user_id 索引,也没有命中分区裁剪。
  3. 用边界数据做简单验证(可选)

    • 对于 RANGE 分区(如 video_order 按时间分区),可以插入一条位于分区边界附近的数据,然后用 EXPLAIN PARTITIONS 验证其查询命中哪个分区:
    sql 复制代码
    INSERT INTO video_order (id, user_id, video_id, order_no, amount, status, order_time)
    VALUES (1, 1001, 2001, 'T202601120001', 9.90, 1, '2026-01-12 10:00:00');
    
    EXPLAIN PARTITIONS
    SELECT * FROM video_order
    WHERE id = 1
      AND order_time = '2026-01-12 10:00:00';
    • 观察 partitions 列应只命中对应年份的分区(如 p2026),如果与预期不符,需重新检查 PARTITION BY 的表达式和各分区的 VALUES LESS THAN 定义。
相关推荐
被摘下的星星5 小时前
MySQL count()函数的用法
数据库·mysql
素玥6 小时前
实训5 python连接mysql数据库
数据库·python·mysql
喵了几个咪7 小时前
如何在 Superset Docker 容器中安装 MySQL 驱动
mysql·docker·容器·superset
Chasing__Dreams8 小时前
Mysql--基础知识点--95--为什么避免使用长事务
数据库·mysql
数据知道9 小时前
claw-code 源码分析:OmX `$team` / `$ralph`——把 AI 辅助开发从偶发灵感变成可重复流水线
数据库·人工智能·mysql·ai·claude code·claw code
__土块__9 小时前
大厂后端一面模拟:从线程安全到分布式缓存的连环追问
jvm·redis·mysql·spring·java面试·concurrenthashmap·大厂后端
做个文艺程序员10 小时前
深入 MySQL 内核:MVCC、Buffer Pool 与高并发场景下的极限调优
数据库·mysql·adb
数厘10 小时前
2.4MySQL安装配置指南(电商数据分析专用)
数据库·mysql·数据分析
一江寒逸11 小时前
零基础从入门到精通MySQL(下篇):精通篇——吃透索引底层、锁机制与性能优化,成为MySQL实战高手
数据库·mysql·性能优化
爱码小白11 小时前
数据库多表命名的通用规范
数据库·python·mysql