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 定义。
相关推荐
齐鲁大虾2 小时前
SQL Server 和 MySQL的区别
数据库·mysql
东方巴黎~Sunsiny3 小时前
mysql大表空间整理注意点
数据库·mysql
AllData公司负责人5 小时前
AllData数据中台-数据同步平台集成开源项目Seatunnel-Web,完成Mysql到Doris同步流程
数据库·mysql·开源
萧曵 丶6 小时前
MySQL InnoDB 实现 MVCC 原理
数据库·mysql·mvcc
万粉变现经纪人6 小时前
如何解决 pip install mysqlclient 报错 ‘mysql_config’ not found 问题
数据库·python·mysql·pycharm·bug·pandas·pip
lkbhua莱克瓦246 小时前
进阶-SQL优化
java·数据库·sql·mysql·oracle
alonewolf_996 小时前
MySQL 8.0 主从复制原理深度剖析与实战全解(异步、半同步、GTID、MGR)
数据库·mysql·adb
Mr. Cao code7 小时前
MySQL数据卷实战:持久化存储秘籍
数据库·mysql·docker·容器