首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >万字长文详解 MyCat 分表分库:从 0 到 1 构建高可用订单系统

万字长文详解 MyCat 分表分库:从 0 到 1 构建高可用订单系统

作者头像
果酱带你啃java
发布2026-04-14 12:45:52
发布2026-04-14 12:45:52
460
举报

引言:当订单表突破千万级,数据库的 "生死抉择"

凌晨三点,电商平台的数据库监控告警突然响起。你登录系统发现,订单查询接口响应时间从正常的 50ms 飙升到了 5 秒以上,数据库连接池频繁耗尽,后台管理系统几乎无法操作。

查看数据库指标,订单表数据量已经达到 1.2 亿行,单个表文件大小超过 100GB。每次简单的分页查询都需要扫描数十万行数据,索引维护成本越来越高,数据库服务器的 CPU 利用率长期维持在 95% 以上。

这不是虚构的场景,而是每个快速成长的电商平台都会面临的 "数据库瓶颈"。根据 MySQL 官方性能测试报告(https://dev.mysql.com/doc/refman/8.0/en/performance-schema.html),当单表数据量超过 1000 万行时,查询性能会出现显著下降。

分表分库技术是解决这一问题的核心方案,而 MyCat 作为国内最流行的分布式数据库中间件,凭借其简单易用、功能全面的特点,已成为众多企业的首选。本文将带你全面掌握 MyCat 在订单系统中的分表分库实战,从理论到实践,包含完整可运行代码和最佳实践。

一、MyCat 核心原理:分布式数据库的 "智能路由器"

1.1 什么是 MyCat?

MyCat 是一款基于 Java 开发的分布式数据库中间件,它模拟 MySQL 协议,实现了数据库的分表分库、读写分离、高可用等功能。应用程序无需修改代码,只需将连接指向 MyCat,即可实现对后端多个数据库的透明访问。

MyCat 的核心价值在于:

  • 对应用透明:应用程序几乎无需修改
  • 功能全面:支持分表分库、读写分离、数据迁移等
  • 性能优异:经过大量企业验证的高性能中间件
  • 社区活跃:国内使用最广泛的分布式数据库中间件之一

1.2 MyCat 核心概念

在使用 MyCat 之前,我们需要理解几个核心概念:

  • 逻辑库(schema)MyCat 中定义的数据库,对应应用程序看到的数据库
  • 逻辑表(table)MyCat 中定义的表,对应应用程序看到的表
  • 数据节点(dataNode)实际存储数据的数据库和表,如db1.order_0
  • 分片键(shardingKey)用于分片的字段,如user_id
  • 分片规则(rule)数据如何分配到不同节点的规则
  • 全局序列(global sequence)生成全局唯一 ID 的机制

阿里巴巴《Java 开发手册(嵩山版)》明确指出:"分库分表时,表名的命名最好能体现分片规则,如 t_order_00 到 t_order_15,便于阅读和理解"。

1.3 MyCat 与其他中间件的对比

目前主流的分表分库中间件有 MyCat、ShardingSphere、TDDL 等,它们的对比:

特性

MyCat

ShardingSphere-JDBC

ShardingSphere-Proxy

部署方式

独立服务

嵌入应用

独立服务

协议支持

MySQL

多数据库

多数据库

学习成本

中等

较高

中等

性能

良好

优秀

良好

适用场景

中小团队、快速部署

开发能力强的团队

多语言支持场景

MyCat 的优势在于部署简单、运维方便,对应用无侵入,特别适合中小团队快速实现分表分库。

二、订单系统分表分库设计:从业务到技术的映射

2.1 订单表结构设计

一个典型的电商订单表应包含以下字段:

代码语言:javascript
复制
CREATE TABLE `order_tbl` (
  `id` bigint NOT NULL COMMENT '订单ID',
  `order_no` varchar(64) NOT NULL COMMENT '订单编号',
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额',
  `pay_amount` decimal(10,2) NOT NULL COMMENT '实付金额',
  `freight` decimal(10,2) NOT NULL COMMENT '运费',
  `order_status` tinyint NOT NULL COMMENT '订单状态:0-待付款,1-待发货,2-已发货,3-已完成,4-已取消',
  `payment_time` datetime DEFAULT NULL COMMENT '支付时间',
  `delivery_time` datetime DEFAULT NULL COMMENT '发货时间',
  `receive_time` datetime DEFAULT NULL COMMENT '确认收货时间',
  `comment_time` datetime DEFAULT NULL COMMENT '评价时间',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
代码语言:javascript
复制

2.2 分片策略设计

订单表的分片策略需要结合业务查询模式来设计。常见的查询场景:

  1. 根据用户 ID 查询订单列表
  2. 根据订单号查询订单详情
  3. 根据时间范围查询订单

基于这些场景,我们设计复合分片策略

  • 分库策略按用户 ID 范围分片,共 4 个库
  • 分表策略在每个库内按订单创建时间(月份)分片

这种策略的优势:

  • 满足按用户 ID 查询的高效性,避免跨库查询
  • 符合订单的时间特性,便于历史数据归档
  • 数据分布均匀,避免热点问题

2.3 分片粒度确定

单表数据量的合理阈值是分表分库设计的关键。根据测试和经验:

  • 对于订单表这种读写频繁、索引较多的表,单表数据量建议控制在 500 万以内
  • 若查询简单且索引优化良好,可放宽到 1000 万

假设平台日均订单 10 万,每月约 300 万订单,按 4 个库分片,每个库每月约 75 万订单,正好在合理阈值内。

2.4 全局 ID 生成策略

分表分库后,传统的自增 ID 无法保证全局唯一,需要全局 ID 生成策略。MyCat 支持多种全局 ID 生成方式:

  1. 本地文件方式基于本地文件的自增 ID,性能高但不适合分布式部署
  2. 数据库方式基于数据库的自增 ID,可靠性高但性能一般
  3. 时间戳方式包含时间戳的 ID,有序且包含时间信息
  4. 分布式 ID 生成器如雪花算法(Snowflake)

对于订单 ID,推荐使用雪花算法,它生成的 64 位 ID 包含时间戳、机器 ID 等信息,既保证唯一性,又便于定位数据所在分片。

三、MyCat 环境搭建:从安装到配置

3.1 环境准备

3.1.1 软件版本选择
  • JDK:17.0.9
  • MyCat:1.6.7.6(最新稳定版)
  • MySQL:8.0.34
  • Spring Boot:3.2.0
  • MyBatis-Plus:3.5.5
  • Lombok:1.18.30
  • Swagger3:2.1.0
  • commons-lang3:3.14.0
3.1.2 MySQL 数据库初始化

首先创建分库分表所需的数据库和表结构:

代码语言:javascript
复制
-- 创建4个订单库
CREATE DATABASE IF NOT EXISTS order_db_0 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS order_db_1 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS order_db_2 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS order_db_3 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建存储过程批量创建表
DELIMITER $$
CREATE PROCEDURE create_order_tables(IN db_suffix INT)
BEGIN
    DECLARE month_str VARCHAR(6);
    SET month_str = '202310';
    CALL create_order_table_by_month(db_suffix, month_str);

    SET month_str = '202311';
    CALL create_order_table_by_month(db_suffix, month_str);

    SET month_str = '202312';
    CALL create_order_table_by_month(db_suffix, month_str);
END$$
CREATE PROCEDURE create_order_table_by_month(IN db_suffix INT, IN month_str VARCHAR(6))
BEGIN
    SET @db_name = CONCAT('order_db_', db_suffix);
    SET @table_name = CONCAT('order_tbl_', month_str);
    SET @sql = CONCAT(
        'CREATE TABLE IF NOT EXISTS ', @db_name, '.', @table_name, ' (
            id bigint NOT NULL COMMENT \'订单ID\',
            order_no varchar(64) NOT NULL COMMENT \'订单编号\',
            user_id bigint NOT NULL COMMENT \'用户ID\',
            total_amount decimal(10,2) NOT NULL COMMENT \'订单总金额\',
            pay_amount decimal(10,2) NOT NULL COMMENT \'实付金额\',
            freight decimal(10,2) NOT NULL COMMENT \'运费\',
            order_status tinyint NOT NULL COMMENT \'订单状态:0-待付款,1-待发货,2-已发货,3-已完成,4-已取消\',
            payment_time datetime DEFAULT NULL COMMENT \'支付时间\',
            delivery_time datetime DEFAULT NULL COMMENT \'发货时间\',
            receive_time datetime DEFAULT NULL COMMENT \'确认收货时间\',
            comment_time datetime DEFAULT NULL COMMENT \'评价时间\',
            create_time datetime NOT NULL COMMENT \'创建时间\',
            update_time datetime NOT NULL COMMENT \'更新时间\',
            PRIMARY KEY (id),
            KEY idx_user_id (user_id),
            KEY idx_create_time (create_time)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=\'订单表\''
    );
    PREPARE stmt FROM @sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
END$$
DELIMITER ;
-- 为每个库创建表
CALL create_order_tables(0);
CALL create_order_tables(1);
CALL create_order_tables(2);
CALL create_order_tables(3);
-- 创建全局序列表(用于MyCat生成全局ID)
CREATE DATABASE IF NOT EXISTS mycat_seq_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE mycat_seq_db;
CREATE TABLE IF NOT EXISTS MYCAT_SEQUENCE (
  name VARCHAR(50) NOT NULL COMMENT '序列名称',
  current_value BIGINT NOT NULL COMMENT '当前值',
  increment INT NOT NULL DEFAULT 100 COMMENT '步长',
  PRIMARY KEY (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MyCat全局序列表';
-- 初始化订单ID序列
INSERT INTO MYCAT_SEQUENCE (name, current_value, increment) 
VALUES ('ORDER_ID', 1000000, 100) ON DUPLICATE KEY UPDATE current_value = 1000000;
代码语言:javascript
复制

3.2 MyCat 安装与配置

3.2.1 MyCat 安装
  1. 下载 MyCat:从 MyCat 官方网站(http://www.mycat.org.cn/)下载 1.6.7.6 版本
  2. 解压安装包:tar -zxvf Mycat-server-1.6.7.6-release-20220524173824-linux.tar.gz -C /opt/
  3. 配置环境变量:
代码语言:javascript
复制
echo 'export MYCAT_HOME=/opt/mycat' >> /etc/profile
echo 'export PATH=$PATH:$MYCAT_HOME/bin' >> /etc/profile
source /etc/profile
代码语言:javascript
复制

3.2.2 MyCat 核心配置

MyCat 的核心配置文件位于/opt/mycat/conf目录下,主要包括:

  1. schema.xml定义逻辑库、逻辑表、数据节点等
  2. rule.xml定义分片规则
  3. server.xml定义用户、权限等

schema.xml 配置:

代码语言:javascript
复制
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
    <!-- 定义逻辑库 -->
    <schema name="ORDER_DB" checkSQLschema="false" sqlMaxLimit="100">
        <!-- 定义订单逻辑表 -->
        <table name="order_tbl" 
               dataNode="dn$0-3" 
               rule="order_table_rule" 
               primaryKey="id"
               autoIncrement="true">
        </table>

        <!-- 订单详情表,与订单表使用相同分片规则 -->
        <table name="order_item_tbl" 
               dataNode="dn$0-3" 
               rule="order_table_rule" 
               primaryKey="id"
               autoIncrement="true">
        </table>

        <!-- 字典表,作为全局表在所有节点都存在 -->
        <table name="dict_tbl" 
               dataNode="dn$0-3" 
               type="global" 
               primaryKey="id">
        </table>
    </schema>

    <!-- 定义数据节点 -->
    <dataNode name="dn0" dataHost="localhost1" database="order_db_0" />
    <dataNode name="dn1" dataHost="localhost1" database="order_db_1" />
    <dataNode name="dn2" dataHost="localhost1" database="order_db_2" />
    <dataNode name="dn3" dataHost="localhost1" database="order_db_3" />

    <!-- 定义数据主机 -->
    <dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
              writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"  slaveThreshold="100">
        <heartbeat>select 1</heartbeat>
        <!-- 写库配置 -->
        <writeHost host="hostM1" url="jdbc:mysql://127.0.0.1:3306?useSSL=false&amp;serverTimezone=Asia/Shanghai" 
                   user="root" password="root">
            <!-- 读库配置 -->
            <readHost host="hostS1" url="jdbc:mysql://127.0.0.1:3307?useSSL=false&amp;serverTimezone=Asia/Shanghai" 
                      user="root" password="root" />
        </writeHost>
    </dataHost>
</mycat:schema>
代码语言:javascript
复制

rule.xml 配置:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://io.mycat/">
    <!-- 订单表分片规则 -->
    <tableRule name="order_table_rule">
        <rule>
            <!-- 分库字段 -->
            <columns>user_id</columns>
            <!-- 分库规则 -->
            <algorithm>order_db_inline</algorithm>
        </rule>
        <!-- 分表规则 -->
        <childTableRule name="order_item_tbl" joinKey="order_id" parentKey="id">
            <rule>
                <columns>create_time</columns>
                <algorithm>order_table_inline</algorithm>
            </rule>
        </childTableRule>
    </tableRule>

    <!-- 分库算法:按user_id范围分片 -->
    <function name="order_db_inline" class="io.mycat.route.function.AutoPartitionByLong">
        <property name="mapFile">autopartition-long.txt</property>
        <property name="defaultNode">0</property>
    </function>

    <!-- 分表算法:按create_time月份分片 -->
    <function name="order_table_inline" class="io.mycat.route.function.PartitionByMonth">
        <property name="dateFormat">yyyyMM</property>
        <property name="sBeginDate">202310</property>
        <property name="sEndDate">202412</property>
        <property name="sPartionDay">1</property>
    </function>
</mycat:rule>
代码语言:javascript
复制

创建autopartition-long.txt文件(位于 conf 目录):

代码语言:javascript
复制
# user_id范围到数据节点的映射
0-25000000=0
25000001-50000000=1
50000001-75000000=2
75000001-100000000=3
代码语言:javascript
复制

server.xml 配置(主要部分):

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
    <!-- 全局序列配置 -->
    <system>
        <property name="sequnceHandlerType">1</property> <!-- 1表示使用数据库方式生成序列 -->
        <property name="defaultSqlParser">druidparser</property>
        <property name="charset">utf8mb4</property>
    </system>

    <!-- 用户配置 -->
    <user name="root" defaultAccount="true">
        <property name="password">123456</property>
        <property name="schemas">ORDER_DB</property>
    </user>

    <user name="user">
        <property name="password">user</property>
        <property name="schemas">ORDER_DB</property>
        <property name="readOnly">true</property>
    </user>
</mycat:server>
代码语言:javascript
复制

序列配置(sequence_db_conf.properties):

代码语言:javascript
复制
# 序列名称到数据库的映射
ORDER_ID=mycat_seq_db
代码语言:javascript
复制

3.2.3 启动 MyCat
代码语言:javascript
复制
# 启动MyCat
mycat start

# 查看状态
mycat status

# 查看日志
tail -f /opt/mycat/logs/wrapper.log
代码语言:javascript
复制

MyCat 启动后,默认监听 8066 端口(数据端口)和 9066 端口(管理端口)。

可以通过 MySQL 客户端连接 MyCat 进行测试:

代码语言:javascript
复制
mysql -h 127.0.0.1 -P 8066 -u root -p123456
代码语言:javascript
复制

四、订单系统开发:基于 MyCat 的实战代码

4.1 项目初始化

4.1.1 Maven 依赖配置
代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>

    <groupId>com.jam.order</groupId>
    <artifactId>mycat-order-demo</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>17</java.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <lombok.version>1.18.30</lombok.version>
        <commons-lang3.version>3.14.0</commons-lang3.version>
        <springdoc.version>2.1.0</springdoc.version>
    </properties>

    <dependencies>
        <!-- Spring Boot 核心 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 数据库驱动 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- MyBatis-Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- 工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons-lang3.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>6.1.2</version>
        </dependency>

        <!-- Swagger3 -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>

        <!-- 测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
代码语言:javascript
复制

4.1.2 应用配置文件
代码语言:javascript
复制
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:8066/ORDER_DB?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      idle-timeout: 300000
      connection-timeout: 20000
      max-lifetime: 1800000

mybatis-plus:
  mapper-locations: classpath*:mapper/**/*.xml
  global-config:
    db-config:
      id-type: INPUT  # 手动输入ID,使用MyCat的全局序列
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl

springdoc:
  api-docs:
    path: /api-docs
  swagger-ui:
    path: /swagger-ui.html
    operationsSorter: method
  packages-to-scan: com.jam.order.controller

logging:
  level:
    com.jam.order.mapper: debug
    com.jam.order.service: info
代码语言:javascript
复制

4.2 核心代码实现

4.2.1 实体类
代码语言:javascript
复制
package com.jam.order.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 订单实体类
 *
 * @author 果酱
 */
@Data
@TableName("order_tbl")
@Schema(description = "订单信息")
public class Order {

    /**
     * 订单ID
     */
    @TableId(type = IdType.INPUT)
    @Schema(description = "订单ID")
    private Long id;

    /**
     * 订单编号
     */
    @Schema(description = "订单编号")
    private String orderNo;

    /**
     * 用户ID
     */
    @Schema(description = "用户ID")
    private Long userId;

    /**
     * 订单总金额
     */
    @Schema(description = "订单总金额")
    private BigDecimal totalAmount;

    /**
     * 实付金额
     */
    @Schema(description = "实付金额")
    private BigDecimal payAmount;

    /**
     * 运费
     */
    @Schema(description = "运费")
    private BigDecimal freight;

    /**
     * 订单状态:0-待付款,1-待发货,2-已发货,3-已完成,4-已取消
     */
    @Schema(description = "订单状态:0-待付款,1-待发货,2-已发货,3-已完成,4-已取消")
    private Integer orderStatus;

    /**
     * 支付时间
     */
    @Schema(description = "支付时间")
    private LocalDateTime paymentTime;

    /**
     * 发货时间
     */
    @Schema(description = "发货时间")
    private LocalDateTime deliveryTime;

    /**
     * 确认收货时间
     */
    @Schema(description = "确认收货时间")
    private LocalDateTime receiveTime;

    /**
     * 评价时间
     */
    @Schema(description = "评价时间")
    private LocalDateTime commentTime;

    /**
     * 创建时间
     */
    @Schema(description = "创建时间")
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    @Schema(description = "更新时间")
    private LocalDateTime updateTime;
}
代码语言:javascript
复制

4.2.2 MyCat 全局 ID 工具类
代码语言:javascript
复制
package com.jam.order.util;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * MyCat全局ID工具类
 *
 * @author 果酱
 */
@Slf4j
@Component
public class MyCatSequenceUtil {

    private final JdbcTemplate jdbcTemplate;

    public MyCatSequenceUtil(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    /**
     * 获取MyCat全局序列
     *
     * @param sequenceName 序列名称
     * @return 全局唯一ID
     */
    public Long getSequenceId(String sequenceName) {
        Objects.requireNonNull(sequenceName, "序列名称不能为空");

        try {
            // 调用MyCat的序列函数
            String sql = "SELECT next value for MYCATSEQ_" + sequenceName;
            Long result = jdbcTemplate.queryForObject(sql, Long.class);
            return Objects.requireNonNullElse(result, 0L);
        } catch (Exception e) {
            log.error("获取MyCat全局序列失败,序列名称:{},异常信息:{}", 
                    sequenceName, ExceptionUtils.getStackTrace(e));
            throw new RuntimeException("获取全局ID失败", e);
        }
    }

    /**
     * 获取订单ID
     *
     * @return 订单ID
     */
    public Long getOrderId() {
        return getSequenceId("ORDER_ID");
    }
}
代码语言:javascript
复制

4.2.3 Mapper 接口
代码语言:javascript
复制
package com.jam.order.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.order.entity.Order;
import org.apache.ibatis.annotations.Param;

import java.time.LocalDateTime;
import java.util.List;

/**
 * 订单Mapper接口
 *
 * @author 果酱
 */
public interface OrderMapper extends BaseMapper<Order> {

    /**
     * 根据用户ID和时间范围查询订单
     *
     * @param userId 用户ID
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @return 订单列表
     */
    List<Order> selectByUserIdAndTimeRange(
            @Param("userId") Long userId,
            @Param("startTime") LocalDateTime startTime,
            @Param("endTime") LocalDateTime endTime);

    /**
     * 根据订单状态和时间范围统计订单数量
     *
     * @param orderStatus 订单状态
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @return 订单数量
     */
    Long countByStatusAndTimeRange(
            @Param("orderStatus") Integer orderStatus,
            @Param("startTime") LocalDateTime startTime,
            @Param("endTime") LocalDateTime endTime);
}
代码语言:javascript
复制

对应的 Mapper XML 文件:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jam.order.mapper.OrderMapper">

    <select id="selectByUserIdAndTimeRange" resultType="com.jam.order.entity.Order">
        SELECT * FROM order_tbl
        WHERE user_id = #{userId}
          AND create_time BETWEEN #{startTime} AND #{endTime}
        ORDER BY create_time DESC
    </select>

    <select id="countByStatusAndTimeRange" resultType="java.lang.Long">
        SELECT COUNT(*) FROM order_tbl
        WHERE order_status = #{orderStatus}
          AND create_time BETWEEN #{startTime} AND #{endTime}
    </select>
</mapper>
代码语言:javascript
复制

4.2.4 Service 层
代码语言:javascript
复制
package com.jam.order.service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jam.order.entity.Order;
import com.jam.order.mapper.OrderMapper;
import com.jam.order.util.MyCatSequenceUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

/**
 * 订单服务实现类
 *
 * @author 果酱
 */
@Slf4j
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

    private final MyCatSequenceUtil sequenceUtil;

    public OrderServiceImpl(MyCatSequenceUtil sequenceUtil) {
        this.sequenceUtil = sequenceUtil;
    }

    /**
     * 创建订单
     *
     * @param order 订单信息
     * @return 订单ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createOrder(Order order) {
        // 参数校验
        Objects.requireNonNull(order, "订单信息不能为空");
        Objects.requireNonNull(order.getUserId(), "用户ID不能为空");

        // 获取全局ID
        Long orderId = sequenceUtil.getOrderId();
        order.setId(orderId);

        // 生成订单编号
        String orderNo = generateOrderNo(order.getUserId());
        order.setOrderNo(orderNo);

        // 设置默认值
        LocalDateTime now = LocalDateTime.now();
        order.setCreateTime(now);
        order.setUpdateTime(now);
        order.setOrderStatus(0); // 默认为待付款状态

        // 保存订单
        boolean saveResult = save(order);
        if (!saveResult) {
            log.error("创建订单失败,订单信息:{}", order);
            throw new RuntimeException("创建订单失败");
        }

        log.info("创建订单成功,订单ID:{},订单编号:{}", order.getId(), order.getOrderNo());
        return order.getId();
    }

    /**
     * 生成订单编号
     * 规则:年月日时分秒 + 用户ID后4位 + 随机数
     *
     * @param userId 用户ID
     * @return 订单编号
     */
    private String generateOrderNo(Long userId) {
        StringBuilder orderNo = new StringBuilder();
        // 年月日时分秒(14位)
        orderNo.append(LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
        // 用户ID后4位
        String userIdStr = String.valueOf(userId);
        if (userIdStr.length() >= 4) {
            orderNo.append(userIdStr.substring(userIdStr.length() - 4));
        } else {
            orderNo.append(StringUtils.leftPad(userIdStr, 4, '0'));
        }
        // 3位随机数
        orderNo.append(StringUtils.leftPad(String.valueOf((int) (Math.random() * 1000)), 3, '0'));
        return orderNo.toString();
    }

    /**
     * 根据用户ID查询订单列表
     *
     * @param userId 用户ID
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @return 订单列表
     */
    @Override
    public List<Order> getOrdersByUserId(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
        Objects.requireNonNull(userId, "用户ID不能为空");
        Objects.requireNonNull(startTime, "开始时间不能为空");
        Objects.requireNonNull(endTime, "结束时间不能为空");

        log.info("查询用户订单,用户ID:{},时间范围:{}至{}", userId, startTime, endTime);
        List<Order> orders = baseMapper.selectByUserIdAndTimeRange(userId, startTime, endTime);

        if (CollectionUtils.isEmpty(orders)) {
            log.info("未查询到用户订单,用户ID:{}", userId);
            return List.of();
        }

        return orders;
    }

    /**
     * 更新订单状态
     *
     * @param orderId 订单ID
     * @param orderStatus 订单状态
     * @return 是否更新成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateOrderStatus(Long orderId, Integer orderStatus) {
        Objects.requireNonNull(orderId, "订单ID不能为空");
        Objects.requireNonNull(orderStatus, "订单状态不能为空");

        // 验证订单状态是否合法
        if (orderStatus < 0 || orderStatus > 4) {
            log.error("订单状态不合法:{}", orderStatus);
            throw new IllegalArgumentException("订单状态不合法");
        }

        Order order = new Order();
        order.setId(orderId);
        order.setOrderStatus(orderStatus);
        order.setUpdateTime(LocalDateTime.now());

        // 根据状态更新对应的时间
        switch (orderStatus) {
            case 1: // 待发货,说明已支付
                order.setPaymentTime(LocalDateTime.now());
                break;
            case 2: // 已发货
                order.setDeliveryTime(LocalDateTime.now());
                break;
            case 3: // 已完成
                order.setReceiveTime(LocalDateTime.now());
                break;
            case 4: // 已取消
                break;
            default:
                // 无需处理
        }

        boolean updateResult = updateById(order);
        log.info("更新订单状态,订单ID:{},新状态:{},结果:{}", orderId, orderStatus, updateResult);
        return updateResult;
    }

    /**
     * 统计指定状态的订单数量
     *
     * @param orderStatus 订单状态
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @return 订单数量
     */
    @Override
    public Long countOrdersByStatus(Integer orderStatus, LocalDateTime startTime, LocalDateTime endTime) {
        Objects.requireNonNull(orderStatus, "订单状态不能为空");
        Objects.requireNonNull(startTime, "开始时间不能为空");
        Objects.requireNonNull(endTime, "结束时间不能为空");

        log.info("统计订单数量,状态:{},时间范围:{}至{}", orderStatus, startTime, endTime);
        return baseMapper.countByStatusAndTimeRange(orderStatus, startTime, endTime);
    }
}
代码语言:javascript
复制

4.2.5 Controller 层
代码语言:javascript
复制
package com.jam.order.controller;

import com.jam.order.entity.Order;
import com.jam.order.service.OrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

/**
 * 订单控制器
 *
 * @author 果酱
 */
@Slf4j
@RestController
@RequestMapping("/api/v1/orders")
@Tag(name = "订单管理", description = "订单相关的CRUD操作")
public class OrderController {

    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    /**
     * 创建订单
     *
     * @param order 订单信息
     * @return 订单ID
     */
    @PostMapping
    @Operation(summary = "创建订单", description = "创建新的订单")
    public ResponseEntity<Long> createOrder(@RequestBody Order order) {
        Long orderId = orderService.createOrder(order);
        return ResponseEntity.ok(orderId);
    }

    /**
     * 查询订单详情
     *
     * @param orderId 订单ID
     * @return 订单详情
     */
    @GetMapping("/{orderId}")
    @Operation(summary = "查询订单详情", description = "根据订单ID查询订单详情")
    public ResponseEntity<Order> getOrderDetail(
            @Parameter(description = "订单ID", required = true)
            @PathVariable Long orderId) {
        Order order = orderService.getById(orderId);
        return ResponseEntity.ok(order);
    }

    /**
     * 根据用户ID查询订单列表
     *
     * @param userId 用户ID
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @return 订单列表
     */
    @GetMapping("/user/{userId}")
    @Operation(summary = "查询用户订单", description = "根据用户ID和时间范围查询订单列表")
    public ResponseEntity<List<Order>> getOrdersByUserId(
            @Parameter(description = "用户ID", required = true)
            @PathVariable Long userId,
            @Parameter(description = "开始时间", required = true)
            @RequestParam LocalDateTime startTime,
            @Parameter(description = "结束时间", required = true)
            @RequestParam LocalDateTime endTime) {
        List<Order> orders = orderService.getOrdersByUserId(userId, startTime, endTime);
        return ResponseEntity.ok(orders);
    }

    /**
     * 更新订单状态
     *
     * @param orderId 订单ID
     * @param orderStatus 订单状态
     * @return 是否更新成功
     */
    @PutMapping("/{orderId}/status")
    @Operation(summary = "更新订单状态", description = "根据订单ID更新订单状态")
    public ResponseEntity<Boolean> updateOrderStatus(
            @Parameter(description = "订单ID", required = true)
            @PathVariable Long orderId,
            @Parameter(description = "订单状态:0-待付款,1-待发货,2-已发货,3-已完成,4-已取消", required = true)
            @RequestParam Integer orderStatus) {
        boolean result = orderService.updateOrderStatus(orderId, orderStatus);
        return ResponseEntity.ok(result);
    }

    /**
     * 统计指定状态的订单数量
     *
     * @param orderStatus 订单状态
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @return 订单数量
     */
    @GetMapping("/count")
    @Operation(summary = "统计订单数量", description = "统计指定状态和时间范围内的订单数量")
    public ResponseEntity<Long> countOrdersByStatus(
            @Parameter(description = "订单状态:0-待付款,1-待发货,2-已发货,3-已完成,4-已取消", required = true)
            @RequestParam Integer orderStatus,
            @Parameter(description = "开始时间", required = true)
            @RequestParam LocalDateTime startTime,
            @Parameter(description = "结束时间", required = true)
            @RequestParam LocalDateTime endTime) {
        Long count = orderService.countOrdersByStatus(orderStatus, startTime, endTime);
        return ResponseEntity.ok(count);
    }
}
代码语言:javascript
复制

4.2.6 启动类
代码语言:javascript
复制
package com.jam.order;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 订单服务启动类
 *
 * @author 果酱
 */
@SpringBootApplication
@MapperScan("com.jam.order.mapper")
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}
代码语言:javascript
复制

4.3 测试验证

4.3.1 功能测试

使用 Swagger UI 进行测试,访问地址:http://localhost:8080/swagger-ui.html

  1. 创建订单测试:
代码语言:javascript
复制
{
  "userId": 123456,
  "totalAmount": 999.99,
  "payAmount": 999.99,
  "freight": 0.00,
  "createTime": "2023-10-15T10:30:00"
}
代码语言:javascript
复制

  1. 查询订单测试: 访问接口/api/v1/orders/user/123456?startTime=2023-10-01T00:00:00&endTime=2023-10-31T23:59:59
  2. 验证数据分布: 登录对应的数据库查看数据是否正确分片:
代码语言:javascript
复制
# 查看user_id=123456应该被分到哪个库
# 根据autopartition-long.txt配置,0-25000000=0,所以应该在order_db_0
mysql -h 127.0.0.1 -P 3306 -u root -p order_db_0
select * from order_tbl_202310 where user_id=123456;
代码语言:javascript
复制

4.3.2 性能测试

使用 JMeter 进行性能测试,模拟 100 并发用户创建订单,观察系统响应时间和错误率。

预期结果:

  • 平均响应时间 < 500ms
  • 95% 响应时间 < 1000ms
  • 错误率 = 0

五、MyCat 高级特性:提升系统可用性与性能

5.1 读写分离

MyCat 支持读写分离,将读操作路由到从库,写操作路由到主库,提高系统吞吐量。

配置方式:在 schema.xml 中配置 readHost:

代码语言:javascript
复制
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
          writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"  slaveThreshold="100">
    <heartbeat>select 1</heartbeat>
    <!-- 写库 -->
    <writeHost host="hostM1" url="jdbc:mysql://127.0.0.1:3306" user="root" password="root">
        <!-- 读库1 -->
        <readHost host="hostS1" url="jdbc:mysql://127.0.0.1:3307" user="root" password="root" />
        <!-- 读库2 -->
        <readHost host="hostS2" url="jdbc:mysql://127.0.0.1:3308" user="root" password="root" />
    </writeHost>
</dataHost>
代码语言:javascript
复制

balance 属性说明:

  • 0:不开启读写分离,所有操作都走写库
  • 1:全部的 readHost 与 stand by writeHost 参与 select 语句的负载均衡
  • 2:所有读操作随机在 writeHost 和 readHost 上分发
  • 3:所有读请求随机分发到 writeHost 对应的 readHost 上

5.2 高可用配置

MyCat 支持数据库高可用,当主库宕机时自动切换到备用主库。

代码语言:javascript
复制
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
          writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"  slaveThreshold="100">
    <heartbeat>select 1</heartbeat>
    <!-- 主库1 -->
    <writeHost host="hostM1" url="jdbc:mysql://127.0.0.1:3306" user="root" password="root">
        <readHost host="hostS1" url="jdbc:mysql://127.0.0.1:3307" user="root" password="root" />
    </writeHost>
    <!-- 备用主库2 -->
    <writeHost host="hostM2" url="jdbc:mysql://127.0.0.1:3309" user="root" password="root">
        <readHost host="hostS2" url="jdbc:mysql://127.0.0.1:3310" user="root" password="root" />
    </writeHost>
</dataHost>
代码语言:javascript
复制


switchType 属性说明:

  • -1:不自动切换
  • 1:默认值,自动切换
  • 2:基于 MySQL 主从同步的状态决定是否切换

5.3 全局表与 ER 表

全局表:在所有分片节点都存在的表,适用于数据量小、变动少的字典表。

配置:

代码语言:javascript
复制
<table name="dict_tbl" dataNode="dn$0-3" type="global" primaryKey="id" />
代码语言:javascript
复制

ER 表:基于 ER 关系的子表,与主表使用相同的分片规则,避免跨库关联查询。

配置:

代码语言:javascript
复制
<table name="order_tbl" dataNode="dn$0-3" rule="order_table_rule" primaryKey="id">
    <childTable name="order_item_tbl" joinKey="order_id" parentKey="id" />
</table>
代码语言:javascript
复制

5.4 SQL 拦截与改写

MyCat 支持对 SQL 进行拦截和改写,实现自定义的 SQL 处理逻辑。

配置方式:在 server.xml 中添加:

代码语言:javascript
复制
<system>
    <property name="sqlInterceptor">com.jam.order.interceptor.MySqlInterceptor</property>
</system>
代码语言:javascript
复制

实现自定义拦截器:

代码语言:javascript
复制
package com.jam.order.interceptor;

import io.mycat.MycatException;
import io.mycat.interceptor.SQLInterceptor;
import io.mycat.server.parser.ServerParse;
import io.mycat.session.MycatSession;

/**
 * 自定义SQL拦截器
 *
 * @author 果酱
 */
public class MySqlInterceptor implements SQLInterceptor {

    @Override
    public String interceptSQL(String sql, MycatSession session, int sqlType) throws MycatException {
        // 记录慢查询
        if (sqlType == ServerParse.SELECT) {
            // 可以在这里记录SQL,或者对SQL进行改写
            System.out.println("拦截到查询SQL: " + sql);
        }
        // 禁止全表扫描
        if (sqlType == ServerParse.SELECT && sql.contains("*") && !sql.contains("where")) {
            throw new MycatException("禁止全表扫描的SQL: " + sql);
        }
        return sql;
    }
}
代码语言:javascript
复制

六、性能优化:让 MyCat 发挥最佳性能

6.1 JVM 参数优化

MyCat 基于 Java 开发,合理的 JVM 参数设置对性能至关重要。

修改/opt/mycat/conf/wrapper.conf

代码语言:javascript
复制
# JVM参数设置
wrapper.java.additional.10=-Xms2G
wrapper.java.additional.11=-Xmx2G
wrapper.java.additional.12=-XX:MetaspaceSize=256m
wrapper.java.additional.13=-XX:MaxMetaspaceSize=512m
wrapper.java.additional.14=-XX:+UseG1GC
wrapper.java.additional.15=-XX:G1HeapRegionSize=16m
wrapper.java.additional.16=-XX:G1ReservePercent=25
wrapper.java.additional.17=-XX:InitiatingHeapOccupancyPercent=30
wrapper.java.additional.18=-XX:+HeapDumpOnOutOfMemoryError
wrapper.java.additional.19=-XX:HeapDumpPath=/opt/mycat/logs/heapdump.hprof
代码语言:javascript
复制

建议:

  • 堆内存设置为物理内存的 1/4 到 1/2
  • 使用 G1GC 收集器,适合大堆内存
  • 配置适当的日志和监控参数

6.2 连接池优化

MyCat 和应用程序的连接池都需要优化:

  1. MyCat 连接池优化(server.xml):
代码语言:javascript
复制
<system>
    <property name="processorBufferPoolType">0</property>
    <property name="processorExecutor">64</property>
    <property name="maxStringLiteralLength">65535</property>
    <property name="sequnceHandlerType">1</property>
    <property name="backSocketNoDelay">1</property>
    <property name="frontSocketNoDelay">1</property>
    <property name="processorCheckPeriod">1000</property>
    <property name="dataNodeIdleCheckPeriod">300000</property>
    <property name="dataNodeHeartbeatPeriod">60000</property>
</system>
代码语言:javascript
复制

应用连接池优化

代码语言:javascript
复制
spring:
  datasource:
    hikari:
      maximum-pool-size: 20  # 根据并发量调整
      minimum-idle: 5
      idle-timeout: 300000
      connection-timeout: 20000
      max-lifetime: 1800000

6.3 SQL 优化

MyCat 环境下的 SQL 优化原则:

  1. 避免全表扫描查询必须包含分片键
  2. 控制结果集大小分页查询必须使用 LIMIT
  3. 优化 JOIN 操作尽量使用 ER 表避免跨库 JOIN
  4. 避免复杂 SQL复杂逻辑尽量在应用层实现
  5. 合理使用索引索引必须包含分片键

反例:

代码语言:javascript
复制
-- 不包含分片键,会导致全库扫描
SELECT * FROM order_tbl WHERE order_status = 1;

-- 没有分页,可能返回大量数据
SELECT * FROM order_tbl WHERE user_id = 123456;
代码语言:javascript
复制

正例:

代码语言:javascript
复制
-- 包含分片键user_id
SELECT * FROM order_tbl WHERE user_id = 123456 AND order_status = 1;

-- 使用分页查询
SELECT * FROM order_tbl WHERE user_id = 123456 LIMIT 0, 20;
代码语言:javascript
复制

七、生产环境部署与监控

7.1 部署架构

推荐的生产环境部署架构:

关键部署建议:

  • MyCat 集群化部署,避免单点故障
  • 数据库采用主从架构,确保数据安全
  • 使用 Keepalived 实现 MyCat 高可用
  • 所有服务器分离部署,避免资源竞争

7.2 监控配置

MyCat 提供了多种监控方式:

MyCat 监控中心

代码语言:javascript
复制
# 部署MyCat-web监控
wget http://dl.mycat.org.cn/mycat-web-1.0/Mycat-web-1.0-SNAPSHOT-20170102153329-linux.tar.gz
tar -zxvf Mycat-web-1.0-SNAPSHOT-20170102153329-linux.tar.gz
cd mycat-web
./start.sh

访问监控界面:http://localhost:8082/mycat

  1. Prometheus + Grafana 监控
  • 部署 MyCat 的 Prometheus exporter
  • 配置 Prometheus 收集指标
  • 使用 Grafana 创建监控面板

关键监控指标:

  • 吞吐量:QPS、TPS
  • 响应时间:平均响应时间、95% 响应时间
  • 连接数:当前连接数、最大连接数
  • 错误率:SQL 错误率、连接错误率
  • 后端数据库状态:主从同步延迟、连接数

7.3 运维脚本

MyCat 状态检查脚本

代码语言:javascript
复制
#!/bin/bash
# 检查MyCat是否运行
MYCAT_PID=$(ps -ef | grep mycat | grep -v grep | awk '{print $2}')
if [ -z "$MYCAT_PID" ]; then
    echo "MyCat is not running, starting..."
    /opt/mycat/bin/mycat start
    sleep 5
    # 再次检查
    MYCAT_PID=$(ps -ef | grep mycat | grep -v grep | awk '{print $2}')
    if [ -z "$MYCAT_PID" ]; then
        echo "Failed to start MyCat"
        # 发送告警
        curl -X POST -d "MyCat启动失败" http://alert-service/alert
        exit 1
    else
        echo "MyCat started successfully"
    fi
else
    echo "MyCat is running with PID: $MYCAT_PID"
fi

数据备份脚本

代码语言:javascript
复制
#!/bin/bash
# 备份所有分片数据库
BACKUP_DIR="/data/backup/mysql"
DATE=$(date +%Y%m%d%H%M%S)
DB_SUFFIXES="0 1 2 3"

# 创建备份目录
mkdir -p $BACKUP_DIR/$DATE

# 备份每个数据库
for suffix in $DB_SUFFIXES; do
    db_name="order_db_$suffix"
    echo "Backing up $db_name..."
    mysqldump -h 127.0.0.1 -u root -p'root' -R -E --single-transaction $db_name > $BACKUP_DIR/$DATE/$db_name.sql
    if [ $? -eq 0 ]; then
        echo "$db_name backup successful"
        # 压缩备份文件
        gzip $BACKUP_DIR/$DATE/$db_name.sql
    else
        echo "$db_name backup failed"
    fi
done

# 保留最近30天的备份
find $BACKUP_DIR -type d -mtime +30 -exec rm -rf {} \;
代码语言:javascript
复制

八、常见问题与解决方案

8.1 数据一致性问题

问题:分表分库后,跨库事务难以保证 ACID 特性。

解决方案

  1. 尽量避免跨库事务,通过业务设计将相关数据放在同一个分片
  2. 采用最终一致性方案,如本地消息表、事务消息
  3. 对于强一致性需求,可使用分布式事务框架如 Seata

示例代码(基于本地消息表的最终一致性):

代码语言:javascript
复制
/**
 * 创建订单并扣减库存(最终一致性实现)
 */
@Transactional(rollbackFor = Exception.class)
public Long createOrderWithStock(Order order, List<OrderItem> items) {
    // 1. 创建订单
    Long orderId = createOrder(order);

    // 2. 记录本地消息
    for (OrderItem item : items) {
        LocalMessage message = new LocalMessage();
        message.setMessageId(UUID.randomUUID().toString());
        message.setBusinessType("STOCK_DEDUCT");
        message.setBusinessId(item.getProductId().toString());
        message.setContent(JSON.toJSONString(item));
        message.setStatus(0); // 待发送
        message.setCreateTime(LocalDateTime.now());
        localMessageMapper.insert(message);
    }

    // 3. 异步发送消息扣减库存
    stockMessageSender.sendStockDeductMessage(orderId, items);

    return orderId;
}
代码语言:javascript
复制

8.2 数据迁移与扩容

问题:随着业务增长,需要新增分片或调整分片规则。

解决方案

  1. 使用 MyCat 的 dataDiff 和 dataMigrate 工具进行数据迁移
  2. 采用 "双写" 策略:同时写入旧分片和新分片,验证无误后切换
  3. 迁移过程中通过监控确保数据一致性

数据迁移步骤:

  1. 准备新的分片环境
  2. 配置双写机制,同时写入新旧分片
  3. 使用工具同步历史数据
  4. 验证数据一致性
  5. 切换读流量到新分片
  6. 停止双写,只写入新分片

8.3 性能瓶颈排查

问题:系统响应变慢,需要定位性能瓶颈。

排查步骤

  1. 查看 MyCat 日志,分析慢 SQL
  2. 检查后端数据库性能,是否有慢查询
  3. 监控系统资源:CPU、内存、磁盘 IO、网络
  4. 检查连接池状态,是否有连接耗尽
  5. 分析 MyCat 监控指标,定位瓶颈环节

优化建议:

  • 为频繁查询添加缓存
  • 优化 SQL,避免全表扫描
  • 增加从库分担读压力
  • 调整 JVM 参数,避免频繁 GC
  • 对热点数据进行特殊处理
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-09-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 果酱带你啃java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言:当订单表突破千万级,数据库的 "生死抉择"
  • 一、MyCat 核心原理:分布式数据库的 "智能路由器"
    • 1.1 什么是 MyCat?
    • 1.2 MyCat 核心概念
    • 1.3 MyCat 与其他中间件的对比
  • 二、订单系统分表分库设计:从业务到技术的映射
    • 2.1 订单表结构设计
    • 2.2 分片策略设计
    • 2.3 分片粒度确定
    • 2.4 全局 ID 生成策略
  • 三、MyCat 环境搭建:从安装到配置
    • 3.1 环境准备
      • 3.1.1 软件版本选择
      • 3.1.2 MySQL 数据库初始化
    • 3.2 MyCat 安装与配置
      • 3.2.1 MyCat 安装
      • 3.2.2 MyCat 核心配置
      • 3.2.3 启动 MyCat
  • 四、订单系统开发:基于 MyCat 的实战代码
    • 4.1 项目初始化
      • 4.1.1 Maven 依赖配置
      • 4.1.2 应用配置文件
    • 4.2 核心代码实现
      • 4.2.1 实体类
      • 4.2.2 MyCat 全局 ID 工具类
      • 4.2.3 Mapper 接口
      • 4.2.4 Service 层
      • 4.2.5 Controller 层
      • 4.2.6 启动类
    • 4.3 测试验证
      • 4.3.1 功能测试
      • 4.3.2 性能测试
  • 五、MyCat 高级特性:提升系统可用性与性能
    • 5.1 读写分离
    • 5.2 高可用配置
    • 5.3 全局表与 ER 表
    • 5.4 SQL 拦截与改写
  • 六、性能优化:让 MyCat 发挥最佳性能
    • 6.1 JVM 参数优化
    • 6.2 连接池优化
    • 6.3 SQL 优化
  • 七、生产环境部署与监控
    • 7.1 部署架构
    • 7.2 监控配置
    • 7.3 运维脚本
  • 八、常见问题与解决方案
    • 8.1 数据一致性问题
    • 8.2 数据迁移与扩容
    • 8.3 性能瓶颈排查
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档