MySQL之读写分离与分库分表

📚 本文收录于「bksczm」的系列专栏

|-----------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
| Linux | C/C++ |
| MySQL | 面试题 |
| Redis ||

一、ShardingSphere 定位

ShardingSphere-Proxy 对应用层伪装成一个 MySQL 服务 (默认端口 3307)。应用把它当成一个普通 MySQL 连接,SQL 全部发给它,由它负责:

  • 读写分离路由:写走主库、读走从库;
  • 分片路由:按分片算法把 SQL 路由到真实的库/表;
  • 结果归并、分布式主键生成、分布式事务协调。

它的角色和之前讲的"网关服务器"一致:一个无状态的 SQL 路由/负载均衡层。

安装参考:Shardingsphere安装-CSDN博客


二、读写分离

2.1 读写分离的思想

  • 主库(Master):处理所有写操作(INSERT/UPDATE/DELETE/DDL);
  • 从库(Slave):处理读操作(SELECT),多个从库分担读压力;
  • 中间通过 binlog relaylog 主从复制保证数据同步。

ShardingSphere 负责把 SQL 按类型分流,应用无需关心主从地址。

2.2 配置文件 config-readwrite-splitting.yaml(逐行注释)

复制代码
# ─────────────────────────────────────────────────────
# 逻辑数据库名:应用连接 Proxy 后 USE 的库名,由 Proxy 虚拟出来
# ─────────────────────────────────────────────────────
databaseName: readwrite_splitting_db

# ─────────────────────────────────────────────────────
# dataSources:声明所有「真实 MySQL 实例」的连接池
# ─────────────────────────────────────────────────────
dataSources:
  write_ds:                                          # 自定义数据源名(主库/写库)
    url: jdbc:mysql://127.0.0.1:3306/test_db?        # JDBC 地址:协议://IP:端口/库名
         characterEncoding=utf8&                     # 连接字符编码
         serverTimezone=Asia/Shanghai&               # 时区
         allowPublicKeyRetrieval=true&               # 8.0 允许获取公钥
         useSSL=false                                # 内网关闭 SSL
    username: root                                   # 连接用户名
    password: 'xxxxxxxxxxxxxx'                       # 密码(含 @、. 等特殊字符必须加引号)
    connectionTimeoutMilliseconds: 30000             # 建立连接超时(毫秒)
    idleTimeoutMilliseconds: 60000                   # 空闲连接回收时间(毫秒)
    maxLifetimeMilliseconds: 1800000                 # 连接最大存活时间(30 分钟)
    maxPoolSize: 50                                  # 连接池最大连接数
    minPoolSize: 1                                   # 连接池最小空闲连接数
  read_ds_0:                                         # 从库 1(容器映射端口 53307)
    url: jdbc:mysql://127.0.0.1:53307/test_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
    username: backup
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
  read_ds_1:                                         # 从库 2(容器映射端口 53308,不能与从库1重复)
    url: jdbc:mysql://127.0.0.1:53308/test_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
    username: backup
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1

# ─────────────────────────────────────────────────────
# rules:规则区(读写分离、分片、加密等可叠加)
# ─────────────────────────────────────────────────────
rules:
  - !READWRITE_SPLITTING                             # 声明:读写分离规则
    dataSources:
      readwrite_ds:                                  # 逻辑读写分离数据源名(对外引用)
        staticStrategy:                              # 静态策略:主从角色固定
          writeDataSourceName: write_ds              # 写请求 → write_ds
          readDataSourceNames:                       # 读请求 → 从下面列表中选
            - read_ds_0
            - read_ds_1
          loadBalancerName: random                   # 引用负载均衡算法
    loadBalancers:                                   # 负载均衡算法定义区
      random:                                        # 算法名(与上面对应)
        type: RANDOM                                 
    # 类型:RANDOM / ROUND_ROBIN / WEIGHT

⚠️ YAML 格式红线:全部用空格缩进,禁止 Tab;每层 2 个空格;冒号后面必须有一个空格。内容一样却报错时,十有八九是格式问题,建议手敲一遍。

JDBC URL 各段含义

复制代码
jdbc:mysql://127.0.0.1:3306/test_db?characterEncoding=utf8&serverTimezone=...
└──┬──┘ └──┬──┘ └────┬────┘ └─┬─┘ └─┬──┘ └──────────┬───────────────┘
 协议      子协议      主机IP    端口   库名           连接参数
  • jdbc:mysql:使用 JDBC 协议连接 MySQL;
  • 127.0.0.1:本机回环地址(不是公网 IP),跨主机连接时填真实 IP;
  • ? 后面是连接参数(字符集、时区、SSL 等)。

2.3 负载均衡算法

算法 含义 适用
ROUND_ROBIN 轮询,读请求依次分给各从库 机器性能相同,最常用
RANDOM 随机 配置简单
WEIGHT 按权重比例分发 从库性能不同

WEIGHT 配置示例:

复制代码
    loadBalancers:
      weight:
        type: WEIGHT
        props:
          read_ds_0: 2.0
          read_ds_1: 1.0

表示总体上按 2:1 分配(不是"先给 0 发两条、再给 1 发一条")。

2.4 连接与测试

复制代码
# 连接 ShardingSphere-Proxy(注意是 3307,不是 3306;-h 小写)
mysql -h 127.0.0.1 -P 3307 -uroot -p

另开一个终端监控日志:

复制代码
tail -f logs/stdout.log

验证:

  • 执行 INSERT,日志显示路由到 write_ds(主库);
  • 执行 SELECT,日志显示路由到某个 read_ds(从库)。

2.5 主从延迟问题

异步复制下,刚写入主库的数据可能还没同步到从库,此时读从库会读到旧数据。处理策略:

  • 写后立即读的强一致场景(支付后跳订单页)强制走主库;
  • 开启半同步复制缩短延迟;
  • 监控从库延迟,延迟过大时摘除该从库。

三、分库分表核心概念

ShardingSphere 把分库分表统称为分片(Sharding)。

3.1 垂直分库

一个库里表太多、业务太杂,按业务关联性拆库,部署到不同服务器:

  • 例:用户库 db_user、订单库 db_order、商品库 db_goods;
  • 效果:既缓解压力,又真正扩容(不同服务器)。

3.2 垂直分表

一个表字段太多、存在大字段(如商品描述 TEXT),把不常用大字段拆成 1:1 扩展表:

  • t_goods(id, name, price) 与 t_goods_desc(goods_id, description);
  • 通过主键 goods_id 对应;
  • 效果:主表行变小、B+ 树更矮、单页能放更多行;但仍在同一服务器,不扩容。

3.3 水平分表

表行数太多,按分片键 + 算法把数据行拆到多张结构相同的表:

  • 例:user_id % 2 = 0 进 goods_0,= 1 进 goods_1;
  • 仍在同一服务器,不扩容,解决单表行数过多。

3.4 水平分库

把数据行拆到不同服务器上的同名库:

  • 例:goods_0 在机器 A,goods_1 在机器 B;
  • 效果:性能与容量双提升。

3.5 四种方式对比

方式 拆分对象 是否扩容 解决问题
垂直分库 按业务拆库 是 业务耦合、整体压力
垂直分表 拆字段 否 大字段、行过大
水平分表 拆数据行 否 单表行数过多
水平分库 拆数据行到不同机器 是 单表大 + 容量

分库基本针对服务器层面;分表常在单服务器内操作。


四、垂直分库配置

config-sharding.yaml:

复制代码
databaseName: sharding_db

dataSources:
  server_user:
    url: jdbc:mysql://127.0.0.1:3306/test_db_id?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
    username: root
    password: '@Czm388512369.'
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
  server_order:
    url: jdbc:mysql://192.168.80.138:53311/test_db_desc?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
    username: backup
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1

rules:
  - !SHARDING
    tables:
      user:                                        # 逻辑表名称(自定义)
        actualDataNodes: server_user.t_user        # 数据源名.真实表名
      order:
        actualDataNodes: server_order.t_order
  • actualDataNodes:数据源名.真实表名,指定逻辑表数据落在哪个真实节点;
  • 连接逻辑库 USE sharding_db 后,对 user 的 SQL 路由到用户库,对 order 的 SQL 路由到订单库。

只在读写分离文件里配多个写库也能"间接"实现垂直分库,参考附录!!!,但 SHARDING 规则提供了自动路由、分片算法、绑定表、主键生成等完整能力,生产应在分片规则中做(或两条规则叠加,见第九章)。


五、水平分片配置(包括bind、broadcast、keygenerator)

假设:先按 user_id 分库(2 个库),再按 goods_id 分表(每个库 2 张表),共 4 个真实节点。

5.1 建真实表

复制代码
-- 实例0(test_db_id0)
CREATE DATABASE IF NOT EXISTS test_db_id0;
USE test_db_id0;
CREATE TABLE IF NOT EXISTS goods_id_gd0 (id INT PRIMARY KEY, user_id INT NOT NULL, goods_id VARCHAR(20) NOT NULL);
CREATE TABLE IF NOT EXISTS goods_id_gd1 (id INT PRIMARY KEY, user_id INT NOT NULL, goods_id VARCHAR(20) NOT NULL);

-- 实例1(test_db_id1)
CREATE DATABASE IF NOT EXISTS test_db_id1;
USE test_db_id1;
CREATE TABLE IF NOT EXISTS goods_id_gd0 (id INT PRIMARY KEY, user_id INT NOT NULL, goods_id VARCHAR(20) NOT NULL);
CREATE TABLE IF NOT EXISTS goods_id_gd1 (id INT PRIMARY KEY, user_id INT NOT NULL, goods_id VARCHAR(20) NOT NULL);

5.2 完整配置与逐行注释

复制代码
databaseName: sharding_db                            # 逻辑库名

dataSources:
  server_user0:                                      # 物理数据源 0
    url: jdbc:mysql://127.0.0.1:3306/test_db_id0?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
    username: root
    password: '@Czm388512369.'
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
  server_user1:                                      # 物理数据源 1
    url: jdbc:mysql://192.168.80.138:53311/test_db_id1?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
    username: backup
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1

rules:
  - !SHARDING
    tables:
      t_goods:                                       # 逻辑表名(应用 SQL 里写的表名)
        # 行表达式,展开后是 4 个真实节点:
        # server_user0.goods_id_gd0 / server_user0.goods_id_gd1
        # server_user1.goods_id_gd0 / server_user1.goods_id_gd1
        actualDataNodes: server_user${0..1}.goods_id_gd${0..1}

        databaseStrategy:                            # 分库策略(选库)
          standard:                                  # 标准分片:单分片键,支持 =、IN、范围
            shardingColumn: user_id                  # 分片键
            shardingAlgorithmName: alg_mod           # 引用算法

        tableStrategy:                               # 分表策略(选表)
          standard:
            shardingColumn: goods_id
            shardingAlgorithmName: alg_hash_mod

        keyGenerateStrategy:                         # 分布式主键
          column: id
          keyGeneratorName: snowflake

    bindingTables:                                   # 绑定表
      - t_goods,t_goods_item
    broadcastTables:                                 # 广播表
      - t_dict

    shardingAlgorithms:                              # 分片算法定义
      alg_mod:
        type: MOD                                    # 取模(分片键须为数值)
        props:
          sharding-count: 2                          # 分片值 % 2
      alg_hash_mod:
        type: HASH_MOD                               # 先哈希再取模(字符串也能用)
        props:
          sharding-count: 2
      alg_inline:
        type: INLINE                                 # 行表达式
        props:
          algorithm-expression: server_user${user_id % 2}

    keyGenerators:
      snowflake:
        type: SNOWFLAKE
        props:
          worker-id: 1

5.3 行表达式

  • ${0..1}:连续枚举;${[a,b,c]}:离散枚举;
  • server_user${0..1}.goods_id_gd${0..1} 是两侧枚举的笛卡尔组合,得到 4 个节点。

5.4 分片策略

策略 适用场景
STANDARD 单分片键,处理精确(=、IN)和范围(>、<、BETWEEN)
COMPLEX 多分片键
HINT 不依赖 SQL,代码手动指定路由
NONE 不强制

5.5 分片算法

算法 说明
MOD 分片值直接取模(数值)
HASH_MOD 先哈希再取模(字符串可用)
INLINE Groovy 表达式,最灵活
INTERVAL 按时间区间(按月/天)
BOUNDARY_RANGE / VOLUME_RANGE 按范围/容量
CLASS 自定义算法类

六、绑定表(bind)完整场景

6.1 构造场景

t_order 和 t_order_item 都按 order_id 分 2 个库(偶数→ds0,奇数→ds1)。

这样子做也能够确保有关数据在同一个库中,避免跨库事务

ds0:

复制代码
-- t_order
order_id=100 user_id=1 amount=100
order_id=102 user_id=2 amount=200
-- t_order_item
item_id=1 order_id=100 goods='手机'
item_id=2 order_id=100 goods='耳机'
item_id=3 order_id=102 goods='电脑'

ds1:

复制代码
-- t_order
order_id=101 user_id=3 amount=50
order_id=103 user_id=4 amount=80
-- t_order_item
item_id=4 order_id=101 goods='书'
item_id=5 order_id=103 goods='键盘'

全量关联查询:

复制代码
SELECT o.order_id, i.goods
FROM t_order o
JOIN t_order_item i ON o.order_id = i.order_id;

6.2 没有配置 bindingTables(笛卡尔积路由)

Proxy 不知道两表分片规则相同,把路由集合做笛卡尔积:t_order 集合 {ds0,ds1} × t_order_item 集合 {ds0,ds1} = 4 条 SQL:

编号 位置 结果
① ds0.t_order × ds0.t_order_item 有效本地 JOIN
② ds1.t_order × ds1.t_order_item 有效本地 JOIN
③ ds0 × ds1 跨库 JOIN,MySQL 无法执行,数据拉到内存归并
④ ds1 × ds0 跨库 JOIN,MySQL 无法执行,数据拉到内存归并

带 WHERE o.order_id=100 时:t_order 能确定到 ds0,但 t_order_item 未被声明绑定,仍向 ds0、ds1 都发,产生 2 条 SQL(ds1 白跑)。

6.3 配置绑定表后

复制代码
    bindingTables:
      - t_order,t_order_item

Proxy 知道同一 order_id 的订单和明细必在同一节点:

  • 全量 JOIN:只下发 2 条(ds0、ds1 各本地 JOIN);
  • 带 order_id=100:精确路由到 ds0,只下发 1 条。

6.4 对比

情形 全量 JOIN 带 order_id=100 跨库 JOIN
未绑定 4 条(2×2) 2 条 有,内存归并
绑定 2 条(本地) 1 条(精确) 无

七、广播表完整场景

本质上就是在每一个库中都备份一个相同的表,以避免跨库事务

7.1 构造场景

t_dict 是订单状态字典表(5 行,几乎不变),t_order 是分片表:

复制代码
status_code=1 status_name='待支付'
status_code=2 status_name='已支付'
status_code=3 status_name='已发货'
status_code=4 status_name='已完成'

查询:

复制代码
SELECT o.order_id, d.status_name
FROM t_order o JOIN t_dict d ON o.status = d.status_code;

7.2 没有广播表(字典只在 ds0)

  • ds0 能本地 JOIN;
  • ds1 没有 t_dict,无法本地 JOIN,Proxy 只能把 ds0 的字典和 ds1 的订单拉到内存归并;
  • 且在 ds0 新增状态(如"已退款")后,ds1 不知道,字典副本不一致。

7.3 配置广播表后

复制代码
    broadcastTables:
      - t_dict
  • 查询:t_dict 在 ds0、ds1 各有一份完整副本,每个库都能本地 JOIN;

  • 写入自动广播 :

    复制代码
    INSERT INTO t_dict VALUES (5, '已退款');

    Proxy 自动向所有节点下发:

    复制代码
    → ds0: INSERT INTO t_dict VALUES (5,'已退款');
    → ds1: INSERT INTO t_dict VALUES (5,'已退款');
  • 单表查询 :SELECT * FROM t_dict 随便路由到一个节点即可。

7.4 选用条件

特征 说明
数据量小 全量副本,太大浪费存储
读多写少 写要广播,写频繁成本高
经常被 JOIN/查询 广播收益就在本地 JOIN
一致性要求高 Proxy 自动维护副本一致

典型:字典表、配置表、地区表。


八、统一序列号(分布式主键)

8.1 为什么分库分表后需要

分库/分表后,每个库各自自增,会产生重复主键(库 0 和库 1 都从 1 开始),因此必须有全局唯一 ID 方案。

8.2 雪花算法(Snowflake,ShardingSphere 默认)

64 位 long 型 ID,分四段:

复制代码
 1 bit     41 bit 时间戳        10 bit 工作机器        12 bit 序列号
┌──┬────────────────────┬────────────────┬──────────────────┐
│ 0│   毫秒级时间戳       │ 5位数据中心+5位机器│ 同毫秒内自增序列   │
└──┴────────────────────┴────────────────┴──────────────────┘
  • 1 位符号:固定 0,保证正数;
  • 41 位时间戳 :毫秒级,可用约 69 年;
  • 10 位工作机器:5 位数据中心 + 5 位机器,共 1024 个节点;
  • 12 位序列号 :同一毫秒可生成 4096 个 ID。

优点:本地生成无网络开销、性能高、整体趋势递增(对 B+ 树索引友好)。

8.3 时钟回拨

机器时钟因 NTP 校时往回拨时可能生成重复 ID。(这也要求了我们不能够轻易更换时区)

处理:记录上次时间戳,回拨很小则等待追上,回拨较大则报错拒绝。

8.4 其他方案

方案 优缺点
UUID 简单但无序、太长,B+ 树插入性能差
数据库号段(美团 Leaf) 批量取号,压力小
Redis INCR 简单高性能,依赖 Redis 高可用
百度 UidGenerator 雪花变种 + RingBuffer,极高性能

8.5 配置

复制代码
        keyGenerateStrategy:
          column: id
          keyGeneratorName: snowflake
    keyGenerators:
      snowflake:
        type: SNOWFLAKE
        props:
          worker-id: 1

插入时不指定 id,Proxy 自动填充雪花 ID。


九、读写分离 + 多集群垂直分库(其余组合可以参考)

9.1 架构形态

多个业务集群,每个集群内部是一主多从的读写分离,集群之间是垂直分库:

复制代码
                    ShardingSphere-Proxy :3307(逻辑库 mall_db)
                                  │
          ┌───────────────────────┴───────────────────────┐
          ▼                                                 ▼
   用户库集群 user_ds                                  订单库集群 order_ds
   ├─ user_master  写  3306                            ├─ order_master 写  53311
   ├─ user_slave0  读  53307                           └─ order_slave0 读  53312
   └─ user_slave1  读  53308
          │                                                 │
   t_user / t_user_log                                 t_order / t_order_item
                                                       t_goods / t_goods_desc(垂直分表)

核心思路 :先用 READWRITE_SPLITTING 把每个集群的物理主从封装成一个逻辑数据源(user_ds、order_ds),再用 SHARDING 的 actualDataNodes 引用这些逻辑数据源------两条规则叠加,路由两层级联。

9.2 完整配置

复制代码
databaseName: mall_db

dataSources:
  # ───── 用户库集群(一主两从) ─────
  user_master:
    url: jdbc:mysql://127.0.0.1:3306/db_user?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
    username: root
    password: '123456'
  user_slave0:
    url: jdbc:mysql://127.0.0.1:53307/db_user?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
    username: root
    password: '123456'
  user_slave1:
    url: jdbc:mysql://127.0.0.1:53308/db_user?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
    username: root
    password: '123456'

  # ───── 订单库集群(一主一从) ─────
  order_master:
    url: jdbc:mysql://127.0.0.1:53311/db_order?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
    username: root
    password: '123456'
  order_slave0:
    url: jdbc:mysql://127.0.0.1:53312/db_order?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
    username: root
    password: '123456'

rules:
  # ════════ 规则 1:读写分离(把每个集群封装成逻辑数据源) ════════
  - !READWRITE_SPLITTING
    dataSources:
      user_ds:                                         # 用户库集群逻辑数据源
        staticStrategy:
          writeDataSourceName: user_master
          readDataSourceNames:
            - user_slave0
            - user_slave1
        loadBalancerName: round_robin
      order_ds:                                        # 订单库集群逻辑数据源
        staticStrategy:
          writeDataSourceName: order_master
          readDataSourceNames:
            - order_slave0
        loadBalancerName: round_robin
    loadBalancers:
      round_robin:
        type: ROUND_ROBIN

  # ════════ 规则 2:垂直分库(逻辑表 → 逻辑数据源) ════════
  - !SHARDING
    tables:
      # 用户域的表全部落在用户库集群
      t_user:
        actualDataNodes: user_ds.t_user
      t_user_log:
        actualDataNodes: user_ds.t_user_log

      # 订单域的表全部落在订单库集群
      t_order:
        actualDataNodes: order_ds.t_order
      t_order_item:
        actualDataNodes: order_ds.t_order_item

      # 垂直分表:商品主表与大字段扩展表(1:1),都在订单库集群
      t_goods:
        actualDataNodes: order_ds.t_goods
      t_goods_desc:
        actualDataNodes: order_ds.t_goods_desc

    bindingTables:
      - t_order,t_order_item
    broadcastTables:
      - t_dict

9.3 路由验证

SQL 路由过程 实际节点
INSERT INTO t_user ... 分片→user_ds;读写分离→主 user_master:3306
SELECT * FROM t_user ... 分片→user_ds;读写分离→从(轮询) user_slave0:53307
INSERT INTO t_order ... 分片→order_ds;读写分离→主 order_master:53311
SELECT * FROM t_order ... 分片→order_ds;读写分离→从 order_slave0:53312
SELECT * FROM t_dict 广播表,任意节点 随机节点

两级路由:先由 SHARDING 决定"去哪个业务集群",再由 READWRITE_SPLITTING 决定"走该集群的主还是从"。

9.4 关于垂直分表

垂直分表是同一个库内 把大字段拆成 1:1 扩展表(如 t_goods 拆出 t_goods_desc),物理上就是两张独立表,不需要分片算法 ,在同一数据源下分别声明即可(见 9.2);应用层按需查询或用 goods_id 关联。若以后还要水平拆分,再为它们配置相同分片键并加入 bindingTables。


十、CAP 理论

10.1 三要素

要素 含义
C:一致性 所有节点同一时刻看到相同数据,读总返回最新写入
A:可用性 每个非故障节点的请求都能在有限时间内得到响应
P:分区容错性 网络分区(中断/丢消息)时系统仍能工作

10.2 为什么 P 必选

网络分区不可避免,无法选择"不要 P"。实际权衡是分区发生时要 C 还是 A:

  • CP:分区时拒绝部分请求保一致,牺牲可用性;
  • AP:分区时各节点继续服务,允许短暂不一致,恢复后再同步。

10.3 典型系统

类型 系统
CP ZooKeeper、etcd、HBase、MongoDB(默认)
AP Cassandra、Dynamo、Eureka、Redis Cluster

对应学过的组件:

  • MySQL 异步主从复制偏 AP(可能丢已提交数据);
  • 半同步复制向 CP 靠拢;
  • MGR 可配置 CP 能力;
  • Redis Cluster 异步复制偏 AP;
  • ZooKeeper(ZAB)是 CP(选举期间不可用)。

CAP 不是简单"三选二":无分区时 C 和 A 可同时满足,只有分区期间才需取舍。

10.4 BASE 理论(AP 的实践)

  • Basically Available(基本可用:允许变慢、降级);
  • Soft State(软状态:允许中间状态);
  • Eventually Consistent(最终一致性:一段时间后所有节点一致)。

十一、分库分表带来的其他问题

| 问题 | 解决思路 |
| 分布式事务 | XA(2PC,偏 CP) 、Saga(柔性事务,最终一致);ShardingSphere 均支持 |
| 跨库 JOIN | 绑定表、广播表、字段冗余、应用层组装 |
| 跨库分页/排序 | 各节点取数后 Proxy 归并(深分页代价大) |
| 全局唯一 ID | 雪花算法等 |
| 扩容 | 成倍扩容 + 数据迁移,或一致性哈希 |

聚合函数 各节点部分聚合后 Proxy 归并

十二.附录(只利用读写分离实现垂直分库)

复制代码
databaseName: readwrite_splitting_db

dataSources:
  write_ds_0:
    url: jdbc:mysql://127.0.0.1:3306/test_db_id?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
    username: root
    password: '@Czm388512369.'
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
  write_ds_1:
    url: jdbc:mysql://127.0.0.1:53306/test_db_desc?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
    username: backup
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
  read_ds_0:
    url: jdbc:mysql://127.0.0.1:53307/test_db_id?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
    username: backup
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
  read_ds_1:
    url: jdbc:mysql://127.0.0.1:53308/test_db_desc?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
    username: backup
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1

rules:
- !READWRITE_SPLITTING
  dataSources:
    readwrite_ds_0:
      staticStrategy:
        writeDataSourceName: write_ds_0
        readDataSourceNames:
          - read_ds_0
      loadBalancerName: round_robin
    readwrite_ds_1:
      staticStrategy:
        writeDataSourceName: write_ds_1
        readDataSourceNames:
          - read_ds_1
      loadBalancerName: round_robin
  loadBalancers:
    round_robin:
      type: ROUND_ROBIN
维度 当前方案:多套独立读写分离 分片模式:垂直分库 + 读写分离
应用视角 应用感知多个逻辑库,不同业务表需要切换不同库执行 应用只看到一个统一逻辑库,所有表都在这个库下,完全不感知底层拆分
跨库关联 完全不支持跨逻辑库 JOIN,应用必须自己拆两次查询再拼装 支持跨分片的关联查询(由 Proxy 自动改写、分发、归并),应用可以正常写 JOIN
事务支持 跨库事务需要应用自行处理分布式事务 同一个逻辑库内,内置支持 XA、Seata 等分布式事务模式
后续扩展 某张表要做水平分片时,需要整体重构配置,改动成本高 本身就在分片体系内,给单张表加水平分片策略即可,架构平滑演进
配置复杂度 简单直观,规则少,排错容易 配置层级多,分片算法、路由策略复杂,排错成本高

这个方法大多数场景下没有分片模式好,但是也可以了解一下(博主自己突发奇想的)

相关推荐
ClouGence14 分钟前
最基础的异构数据库 DDL 同步,我们踩过哪些坑?
数据库·mysql·postgresql
羑悻的小杀马特1 小时前
Redis持久化三剑客:RDB二进制快照、AOF文本日志、混合模式如何守护你的数据安全?
数据库·redis·缓存·aof·持久化存储·rdb
小呆爱发呆2 小时前
第三章 MySQL 库表操作
数据库·mysql·adb
时间的拾荒人2 小时前
Redis 进阶实战:渐进式遍历、持久化机制与事务详解
数据库·redis·缓存
普通人63 小时前
MySQL_FIND_IN_SET函数详解
android·数据库·mysql
真上帝的左手3 小时前
4. 关系型数据库-MySQL-Canal 进阶
数据库·mysql·canal
xiaoye-duck3 小时前
《C++ 项目:手写大模型接入 SDK》DataManager数据管理模块:SQLite持久化存储
开发语言·数据库·c++·sqlite
codeejun4 小时前
每日一Go·MySQL-14、慢 SQL 产生原因全解
sql·mysql·golang
唐老板4 小时前
uuidv7 来了:为什么别再拿 uuidv4 当主键了
数据库