mysql分表如何实现

MySQL 分表实现方案

MySQL 分表主要有三种实现方式:应用层代码分表、MySQL 分区表(Partition)和使用分库分表中间件。下面详细介绍每种实现方法。

一、应用层代码分表

1. 水平分表实现

-- 创建分表(按用户ID最后一位分10张表)
CREATE TABLE `user_0` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL,
  `name` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB;

-- 创建user_1到user_9类似结构的表...

应用代码中实现路由逻辑(以PHP为例):

function getTableName($userId) {
    return 'user_' . ($userId % 10);
}

function insertUser($user) {
    $table = getTableName($user['user_id']);
    $sql = "INSERT INTO {$table} (user_id, name) VALUES (:user_id, :name)";
    // 执行SQL...
}

2. 垂直分表实现

-- 主表
CREATE TABLE `user_base` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

-- 扩展表(存储不常用信息)
CREATE TABLE `user_ext` (
  `user_id` bigint(20) NOT NULL,
  `address` varchar(255) DEFAULT NULL,
  `description` text,
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB;

二、MySQL 分区表(Partition)

MySQL 原生支持的分区功能,对应用透明:

1. RANGE 分区(按范围)

CREATE TABLE sales (
    id INT NOT NULL,
    sale_date DATE NOT NULL,
    amount DECIMAL(10,2)
) PARTITION BY RANGE(YEAR(sale_date)) (
    PARTITION p_2020 VALUES LESS THAN (2021),
    PARTITION p_2021 VALUES LESS THAN (2022),
    PARTITION p_2022 VALUES LESS THAN (2023),
    PARTITION p_max VALUES LESS THAN MAXVALUE
);

2. HASH 分区(均匀分布)

CREATE TABLE user_hash (
    id INT NOT NULL,
    username VARCHAR(30),
    created DATE
) PARTITION BY HASH(id) 
PARTITIONS 10;

3. LIST 分区(离散值)

CREATE TABLE employees (
    id INT NOT NULL,
    name VARCHAR(30),
    store_id INT
) PARTITION BY LIST(store_id) (
    PARTITION pNorth VALUES IN (1, 3, 5),
    PARTITION pSouth VALUES IN (2, 4, 6),
    PARTITION pWest VALUES IN (7, 8, 9)
);

三、使用分库分表中间件

1. ShardingSphere 配置示例

# 配置分片规则
shardingRule:
  tables:
    t_order:
      actualDataNodes: ds_${0..1}.t_order_${0..15}
      tableStrategy:
        inline:
          shardingColumn: order_id
          algorithmExpression: t_order_${order_id % 16}
      databaseStrategy:
        inline:
          shardingColumn: user_id
          algorithmExpression: ds_${user_id % 2}

2. MyCat 配置示例

<!-- schema.xml -->
<table name="user" primaryKey="id" dataNode="dn1,dn2,dn3" rule="mod-long" />

<!-- rule.xml -->
<tableRule name="mod-long">
    <rule>
        <columns>id</columns>
        <algorithm>mod-long</algorithm>
    </rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
    <property name="count">3</property>
</function>

分表后的查询处理

  1. 单表查询:直接路由到具体分表
SELECT * FROM user_3 WHERE user_id = 123;
  1. 多表查询:使用UNION ALL合并结果
SELECT * FROM user_0 WHERE name LIKE '张%'
UNION ALL
SELECT * FROM user_1 WHERE name LIKE '张%'
...
UNION ALL
SELECT * FROM user_9 WHERE name LIKE '张%'
  1. 使用中间件:中间件自动处理SQL路由和结果合并

注意事项

  1. 分表字段选择要谨慎,尽量选择查询频繁且分布均匀的字段
  2. 避免跨分片事务,尽量保证事务在单分片内完成
  3. 考虑全局唯一ID生成方案(雪花算法、UUID等)
  4. 分表后索引设计要针对分片后的查询模式优化
  5. 监控各分表数据量和性能,避免数据倾斜

分表能显著提升大表性能,但会增加系统复杂度,建议在单表数据量达到千万级别后再考虑分表。

MySQL 分区表(Partition)中的1. RANGE 分区(按范围)方法,是要列出未来的年份,来分表吗

MySQL RANGE 分区关于未来年份的处理策略

对于 MySQL 的 RANGE 分区(按范围)方法,是否需要预先列出未来年份来分表,这是一个很好的问题。以下是详细解答:

核心原则

不需要预先创建未来所有年份的分区,但需要合理规划分区策略以应对未来数据增长。

推荐做法

1. 使用 MAXVALUE 分区作为兜底

CREATE TABLE sales (
    id INT NOT NULL,
    sale_date DATE NOT NULL,
    amount DECIMAL(10,2)
) PARTITION BY RANGE(YEAR(sale_date)) (
    PARTITION p_2020 VALUES LESS THAN (2021),
    PARTITION p_2021 VALUES LESS THAN (2022),
    PARTITION p_2022 VALUES LESS THAN (2023),
    PARTITION p_future VALUES LESS THAN MAXVALUE  -- 兜底分区
);

2. 动态添加新分区(推荐方案)

-- 初始创建表时
CREATE TABLE sales (
    id INT NOT NULL,
    sale_date DATE NOT NULL,
    amount DECIMAL(10,2)
) PARTITION BY RANGE(YEAR(sale_date)) (
    PARTITION p_hist VALUES LESS THAN (2023),  -- 历史数据分区
    PARTITION p_2023 VALUES LESS THAN (2024),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

-- 当需要2024年分区时(通常在年底执行)
ALTER TABLE sales REORGANIZE PARTITION p_future INTO (
    PARTITION p_2024 VALUES LESS THAN (2025),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

最佳实践建议

  1. 提前1-2年分区:保持1-2年的未来分区即可,无需创建多年后的分区

  2. 建立分区维护机制

    • 每年年底自动添加下一年分区
    • 可以通过事件或外部脚本实现
  3. 监控分区使用情况

    SELECT PARTITION_NAME, TABLE_ROWS 
    FROM INFORMATION_SCHEMA.PARTITIONS 
    WHERE TABLE_NAME = 'sales';
    
  4. 合并旧分区:对于历史数据可以考虑合并

    ALTER TABLE sales REORGANIZE PARTITION p_2020,p_2021 INTO (
        PARTITION p_hist_2020_2021 VALUES LESS THAN (2022)
    );
    
  5. 分区命名规范:使用有意义的名称如 p_2023p_2023_q1

为什么不需要预先创建多年分区?

  1. 存储效率:空分区也会占用系统资源
  2. 管理复杂度:过多分区增加维护难度
  3. 灵活性:业务需求可能变化,过早分区可能不符合未来实际需求
  4. 性能考虑:分区数量过多可能影响查询性能

通过动态添加分区的方式,既能满足数据增长需求,又能保持分区表的灵活性。

创建有分区的表时为什么会有历史数据分区,刚创建的表不是没有数据吗

关于新建分区表中历史数据分区的解释

您提出了一个很好的观察点:为什么在刚创建的分区表中会包含历史数据分区,即使表还没有任何数据? 这确实需要详细解释。

核心原因

历史数据分区在新建表时就存在,主要是出于以下几个设计考虑:

1. 数据迁移准备

  • 即使新表刚创建时没有数据,设计时通常考虑未来可能需要导入历史数据
  • 预先创建历史分区可以避免以后重组分区带来的性能开销
  • 例如从旧表迁移数据到新分区表时,历史数据可以直接放入对应分区

2. 统一的分区管理策略

  • 保持分区策略的一致性(过去、现在、未来)
  • 避免因遗漏历史分区导致数据无法正确归类
  • 使应用程序无需关心表是新建还是已存在

3. 分区维护便利性

-- 有了历史分区后,可以方便地整体管理旧数据
ALTER TABLE sales DROP PARTITION p_hist;  -- 快速删除所有历史数据
ALTER TABLE sales REORGANIZE PARTITION p_hist...  -- 重组历史分区

4. 查询优化考虑

  • 即使没有数据,优化器也能基于分区结构生成更好的执行计划
  • 对于时间范围查询,明确的历史分区边界有助于分区裁剪(partition pruning)

实际示例说明

假设我们2023年创建销售表,但需要包含2023年之前的数据:

-- 2023年新建表时的合理分区设计
CREATE TABLE sales (
    id INT NOT NULL,
    sale_date DATE NOT NULL,
    amount DECIMAL(10,2)
) PARTITION BY RANGE(YEAR(sale_date)) (
    PARTITION p_hist VALUES LESS THAN (2023),  -- 历史数据分区(空)
    PARTITION p_2023 VALUES LESS THAN (2024),  -- 当前年度分区
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

-- 可能的使用场景:
-- 1. 导入2021-2022年的历史数据 → 进入p_hist
-- 2. 2023年新数据 → 进入p_2023
-- 3. 2024年后新数据 → 进入p_future(之后可重组为p_2024)

为什么不是错误设计?

  1. 零开销原则:空分区只占用极少量元数据空间,不影响性能
  2. 防御性设计:预防"历史数据突然需要导入"的情况
  3. 未来证明:避免以后通过ALTER TABLE重组分区,可能锁表影响生产环境

替代方案对比

如果不预先创建历史分区,未来需要添加时会面临:

-- 需要重组现有分区(可能锁表且耗时)
ALTER TABLE sales REORGANIZE PARTITION p_2023 INTO (
    PARTITION p_hist VALUES LESS THAN (2023),
    PARTITION p_2023 VALUES LESS THAN (2024)
);

因此,即使当前没有历史数据,预先创建历史分区仍是推荐做法,这体现了良好的数据库设计前瞻性。

如何对已有数据的表,进行分区分表

对已有数据的表进行分区分表的完整方案

对已有数据的表进行分区分表需要谨慎操作,以下是详细步骤和注意事项:

一、分区/分表前的准备工作

  1. 备份数据

    -- 创建临时备份表
    CREATE TABLE original_table_backup LIKE original_table;
    INSERT INTO original_table_backup SELECT * FROM original_table;
    
  2. 分析表结构和查询模式

    • 确定合适的分区键(如时间字段、ID等)
    • 检查现有索引和约束条件
  3. 评估数据量

    SELECT COUNT(*) FROM original_table;
    SELECT MIN(partition_column), MAX(partition_column) FROM original_table;
    

二、分区方案实施

方案A:直接修改原表(适合中小型表)

-- 示例:按时间范围分区
ALTER TABLE original_table 
PARTITION BY RANGE (YEAR(create_time)) (
    PARTITION p_2020 VALUES LESS THAN (2021),
    PARTITION p_2021 VALUES LESS THAN (2022),
    PARTITION p_2022 VALUES LESS THAN (2023),
    PARTITION p_current VALUES LESS THAN MAXVALUE
);

方案B:创建新分区表后迁移数据(推荐大型表)

  1. 创建分区表结构

    CREATE TABLE partitioned_table (
      id INT NOT NULL,
      create_time DATETIME NOT NULL,
      -- 其他字段...
      PRIMARY KEY (id, create_time)  -- 分区键必须包含在主键中
    ) ENGINE=InnoDB
    PARTITION BY RANGE (TO_DAYS(create_time)) (
      PARTITION p_hist VALUES LESS THAN (TO_DAYS('2023-01-01')),
      PARTITION p_2023 VALUES LESS THAN (TO_DAYS('2024-01-01')),
      PARTITION p_future VALUES LESS THAN MAXVALUE
    );
    
  2. 分批次迁移数据

    -- 使用存储过程分批迁移
    DELIMITER //
    CREATE PROCEDURE migrate_data()
    BEGIN
      DECLARE done INT DEFAULT FALSE;
      DECLARE batch_size INT DEFAULT 10000;
      DECLARE max_id INT;
      
      SELECT MAX(id) INTO max_id FROM original_table;
      
      WHILE max_id > 0 DO
        INSERT INTO partitioned_table 
        SELECT * FROM original_table 
        WHERE id BETWEEN max_id - batch_size AND max_id;
        
        SET max_id = max_id - batch_size - 1;
        COMMIT;
      END WHILE;
    END //
    DELIMITER ;
    
    CALL migrate_data();
    

三、分表方案实施

水平分表步骤

  1. 创建分表结构

    -- 创建10个分表(示例按ID哈希)
    CREATE TABLE original_table_0 LIKE original_table;
    CREATE TABLE original_table_1 LIKE original_table;
    -- ...创建到original_table_9
    
  2. 迁移数据到分表

    INSERT INTO original_table_0 SELECT * FROM original_table WHERE MOD(id, 10) = 0;
    INSERT INTO original_table_1 SELECT * FROM original_table WHERE MOD(id, 10) = 1;
    -- ...其他分表
    
  3. 创建视图或使用中间件路由

    CREATE VIEW original_table AS
    SELECT * FROM original_table_0 UNION ALL
    SELECT * FROM original_table_1 UNION ALL
    -- ...其他分表
    

四、切换方案

  1. 应用无停机切换

    -- 1. 停止写入
    -- 2. 同步增量数据
    -- 3. 重命名表
    RENAME TABLE original_table TO original_table_old, 
                 partitioned_table TO original_table;
    -- 4. 恢复写入
    
  2. 使用双写过渡期

    • 应用同时写入新旧表
    • 验证新表数据一致性后逐步迁移读操作

五、注意事项

  1. 索引调整:分区键必须包含在所有唯一索引中

  2. 外键约束:分区表不支持外键,需要改为应用层控制

  3. 事务处理:跨分区事务性能较差

  4. 监控维护

    -- 检查分区使用情况
    SELECT PARTITION_NAME, TABLE_ROWS 
    FROM INFORMATION_SCHEMA.PARTITIONS 
    WHERE TABLE_NAME = 'partitioned_table';
    
  5. 性能测试:在生产环境实施前充分测试

六、推荐工具

  1. pt-online-schema-change:Percona的在线表结构变更工具
  2. gh-ost:GitHub的在线DDL工具
  3. ShardingSphere:分库分表中间件

通过以上方法,您可以安全地对已有数据的表进行分区分表操作,建议在低峰期执行,并做好完善的备份和回滚方案。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容