Mysql优化:同库分表

一、同库分表的核心场景与优势

适用场景

  • 单表数据量超500万行,但业务暂不适合跨库拆分。
  • 需优化单表查询性能(如分页查询耗时过长),但不想引入跨库复杂度。

核心优势

  • 避免跨库开销:所有分表在同一数据库内,无需处理跨库JOIN、分布式事务。
  • 简化运维:单库备份、监控更便捷

二、分表策略与分片规则设计

(一)三大主流分片策略
  1. 取模分片(最常用)

    • 规则:通过分片键 % 分表数计算目标表名。
    • 案例:订单表按order_id % 10拆分为10张表(order_0order_9)。
    • 适用场景:高频查询基于分片键(如按order_id查询订单)。
  2. 范围分片(时序数据首选)

    • 规则:按时间、ID范围划分(如按年份分表:order_2023order_2024)。
    • 案例:日志表按月份分表,每月生成新表log_202506log_202507
    • 优势:天然支持时序查询(如查2025年6月日志直接定位log_202506表)。
  3. 哈希分片(数据均匀分布)

    • 规则:对分片键哈希后取模(如hash(user_id) % 10)。
    • 适用场景:需确保数据均匀分布,且无明显查询维度。
(二)分片键选择原则
  • 必须高频查询:若分片键为user_id,则高频查询需包含user_id(如SELECT * FROM order WHERE user_id=123)。
  • 避免跨表JOIN:分片后若需关联查询,需提前设计冗余字段(如订单表冗余user_name避免JOIN用户表)。

三、分表实现的三种技术方案

(一)方案一:应用层手动分表
  1. 核心逻辑:在代码中动态拼接表名,直接操作分表。
  2. 示例(Java代码)
   // 按user_id取模分表
   String tableSuffix = String.valueOf(user_id % 10);
   String sql = "SELECT * FROM user_" + tableSuffix + " WHERE id = ?";
   PreparedStatement ps = connection.prepareStatement(sql);
  1. 优缺点
    • 优点:无额外依赖,适合简单场景(如分表数固定且逻辑简单)。
    • 缺点:代码侵入性强,分表逻辑散落在各处,维护成本高。
(二)方案二:MyBatis插件分表(半自动化)
  1. 实现方式:通过MyBatis拦截器修改SQL表名。
  2. 配置示例(MyBatis拦截器)
   @Intercepts({@Signature(method = "prepare", type = StatementHandler.class)})
   public class TableShardingInterceptor implements Interceptor {
       @Override
       public Object intercept(Invocation invocation) {
           // 获取原始SQL
           String sql = ((BoundSql) invocation.getArgs()[0].getBoundSql()).getSql();
           // 解析分片键(如从SQL参数中获取user_id)
           int userId = getUserIdFromParams(...);
           // 计算表后缀
           String tableSuffix = String.valueOf(userId % 10);
           // 替换表名
           String newSql = sql.replace("user", "user_" + tableSuffix);
           // 执行新SQL
           ...
       }
   }
(三)方案三:中间件自动分表
  1. Sharding-JDBC配置示例
   spring:
     shardingsphere:
       datasource:
         names: master
         master:
           driver-class-name: com.mysql.cj.jdbc.Driver
           url: jdbc:mysql://localhost:3306/test_db
           username: root
           password: 123456
       sharding:
         tables:
           user:
             actual-data-nodes: master.user_$->{0..9}  # 分10张表
             table-strategy:
               inline:
                 sharding-column: user_id
                 algorithm-expression: user_$->{user_id % 10}
         binding-tables: [user]  # 绑定表,优化关联查询
  1. 优势
    • 透明分表:应用无需关心表名拼接,SQL按原表名书写。
    • 功能完善:支持分表后分页、排序、聚合查询自动合并结果。

四、分表后的分页查询问题

(一)应用层手动分页查询
(二)数据库层分页:存储过程+临时表
(三)中间件自动优化(Sharding-JDBC)
示例:Spring Boot集成Sharding-JDBC实现分页查询

1.添加依赖(Maven)

<!-- ShardingSphere-JDBC -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>5.3.2</version>
</dependency>

<!-- MyBatis-Plus(可选,简化CRUD) -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

2.配置Sharding-JDBC分片规则

spring:
  shardingsphere:
    mode:
      type: Memory  # 内存模式,适合开发测试
    datasource:
      names: ds0  # 数据源名称
      ds0:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test_db?useSSL=false
        username: root
        password: root
    rules:
      sharding:
        tables:
          user:  # 逻辑表名
            actual-data-nodes: ds0.user_$->{0..3}  # 物理表:user_0到user_3
            table-strategy:
              standard:
                sharding-column: user_id  # 分片键
                sharding-algorithm-name: user-mod  # 分片算法
        sharding-algorithms:
          user-mod:
            type: MOD  # 取模算法
            props:
              sharding-count: 4  # 分4张表
        props:
          # 归并引擎类型:STREAM(流式)/MEMORY(内存)
          merge.engine.type: STREAM
          # 大分页查询优化(超过此阈值自动优化)
          query.with.cipher.column.threshold: 1000
          # 结果集最大行数(防止OOM)
          max-rows: 100000
    props:
      sql-show: true  # 打印SQL
      sql-simple: false  # 复杂SQL格式化

3.User实体类

@Data
@TableName("user")  // 逻辑表名
public class User {
    private Long id;
    private String username;
    private Integer age;
    private Long userId;  // 分片键
}

4.Mybatis接口

public interface UserMapper extends BaseMapper<User> {
    // 使用MyBatis-Plus的分页查询
    IPage<User> selectByPage(Page<User> page, @Param("age") Integer age);
}
<select id="selectByPage" resultType="User">
    SELECT * FROM user WHERE age > #{age}
    ORDER BY user_id  <!-- 排序字段建议包含分片键 -->
</select>

5.UserService

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public IPage<User> getUsersByPage(Integer pageNum, Integer pageSize, Integer age) {
        // 创建分页对象
        Page<User> page = new Page<>(pageNum, pageSize);
        
        // 执行分页查询(自动路由到各分表)
        return userMapper.selectByPage(page, age);
    }
}

6.UserController

@RestController
@RequestMapping("/users")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping
    public IPage<User> listUsers(
            @RequestParam(defaultValue = "1") Integer page,
            @RequestParam(defaultValue = "10") Integer size,
            @RequestParam(required = false) Integer age) {
        return userService.getUsersByPage(page, size, age);
    }
}

五、分表数据迁移与增量同步

(一)全量数据迁移
(二)增量数据同步
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值