首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >分库分表会带来哪些问题如何解决

分库分表会带来哪些问题如何解决

原创
作者头像
Java大神之路
发布2026-07-23 10:26:02
发布2026-07-23 10:26:02
130
举报
文章被收录于专栏:场景设计题场景设计题

分库分表会带来哪些问题如何解决

分库分表是解决单库单表性能瓶颈的核心方案,但它本质是 "用复杂度换性能",会引入 7 大类核心问题,我结合实际项目经验逐一说明:

核心问题与解决方案对照表 📊

问题分类

具体表现

主流解决方案

推荐指数

分布式事务

跨库操作无法保证 ACID,数据不一致

  1. 柔性事务(Seata AT 模式) 2. 最终一致性(本地消息表 + MQ) 3. TCC 模式(高并发核心场景)

⭐⭐⭐⭐⭐

跨库关联查询

JOIN 操作无法跨库执行,多表查询困难

  1. 字段冗余(避免 JOIN) 2. 数据同步到 ES 做宽表查询 3. 应用层内存 JOIN(小表场景)

⭐⭐⭐⭐

分页排序聚合

跨表 LIMIT、ORDER BY、GROUP BY 结果错误

  1. 全局排序:先查各分片再归并 2. 深度分页:用游标分页替代 offset 3. 聚合统计:预计算 + 定时更新

⭐⭐⭐⭐

分布式 ID 生成

自增 ID 在多表间重复,主键冲突

  1. 雪花算法(Snowflake) 2. 美团 Leaf(双 buffer 优化) 3. 数据库号段模式

⭐⭐⭐⭐⭐

数据迁移扩容

扩容时数据重分布,业务停服

  1. 双写迁移法(不停服) 2. 按时间 / 范围分片(天然支持扩容) 3. 一致性哈希(减少数据迁移量)

⭐⭐⭐⭐

多数据源管理

数据源切换复杂,连接池资源浪费

  1. Sharding-JDBC(客户端分片) 2. MyCat(服务端分片) 3. 动态数据源路由

⭐⭐⭐⭐

读写一致性

主从延迟导致读写不一致

  1. 强一致性读(强制走主库) 2. 延迟容忍度分级 3. 读写分离 + 本地缓存

⭐⭐⭐

关键问题深度解析 🔍

1. 分布式事务:最头疼的问题 😫
  • 避坑点:绝对不要用 2PC(两阶段提交),性能极差且阻塞严重
  • 项目首选:Seata AT 模式,无侵入性,适合 90% 业务场景
  • 高并发场景:用本地消息表 + RocketMQ 事务消息,保证最终一致性
2. 跨库查询:最常见的坑 🕳️
  • 最佳实践:设计阶段就避免跨库 JOIN,通过字段冗余解决
  • 复杂查询:将数据同步到 Elasticsearch,所有复杂查询走 ES
  • 反例:应用层循环查库,会导致 N+1 问题,性能灾难
3. 数据迁移:最容易出事故的环节 ⚠️
不停服双写迁移流程图:

分库分表设计原则 ✅

  1. 能不分就不分:单表 500 万行以下,优先优化索引和 SQL
  2. 分片键选择:优先用高频查询字段(如用户 ID、订单 ID)
  3. 分片策略:优先用范围分片 + 哈希分片结合
  4. 提前规划:一次分够,避免频繁扩容(建议一次分 16/32/64 表)

面试官追问高频点 💡

  • 问:雪花算法有什么问题?怎么解决?
    • 答:时钟回拨问题。解决方案:记录最后生成 ID 的时间戳,回拨时等待或抛出异常;美团 Leaf 用 ZooKeeper 做时钟校验。
  • 问:Sharding-JDBC 和 MyCat 怎么选?
    • 答:中小团队首选 Sharding-JDBC,无中间件,运维简单;大型团队可考虑 MyCat,集中式管理更方便。

核心代码实现(面试必背,带技术亮点)💻

1. 雪花算法(带时钟回拨防护,面试必写)✨
代码语言:java
复制
public class SnowflakeIdGenerator {
    // 起始时间戳 (2020-01-01)
    private static final long START_TIMESTAMP = 1577836800000L;
    // 机器ID位数
    private static final long WORKER_ID_BITS = 5L;
    // 数据中心ID位数
    private static final long DATACENTER_ID_BITS = 5L;
    // 序列号位数
    private static final long SEQUENCE_BITS = 12L;

    // 最大值
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);

    // 偏移量
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
    private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;

    // 序列号掩码
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);

    private final long workerId;
    private final long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(long workerId, long datacenterId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException("Worker ID超出范围");
        }
        if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
            throw new IllegalArgumentException("Datacenter ID超出范围");
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    public synchronized long nextId() {
        long currentTimestamp = System.currentTimeMillis();

        // 核心亮点:时钟回拨处理
        if (currentTimestamp < lastTimestamp) {
            long offset = lastTimestamp - currentTimestamp;
            // 回拨小于5ms,等待恢复
            if (offset <= 5) {
                try {
                    wait(offset << 1);
                    currentTimestamp = System.currentTimeMillis();
                    if (currentTimestamp < lastTimestamp) {
                        throw new RuntimeException("时钟回拨严重,无法生成ID");
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException("线程中断", e);
                }
            } else {
                throw new RuntimeException("时钟回拨超过5ms,无法生成ID");
            }
        }

        // 同一毫秒内序列号自增
        if (currentTimestamp == lastTimestamp) {
            sequence = (sequence + 1) & SEQUENCE_MASK;
            // 序列号溢出,等待下一毫秒
            if (sequence == 0) {
                currentTimestamp = waitNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = currentTimestamp;

        // 拼接64位ID
        return ((currentTimestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT)
                | (datacenterId << DATACENTER_ID_SHIFT)
                | (workerId << WORKER_ID_SHIFT)
                | sequence;
    }

    private long waitNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
}
技术亮点:
  • ✅ 解决了雪花算法最致命的时钟回拨问题,支持小范围回拨等待
  • ✅ 使用synchronized保证单实例线程安全,性能优于ReentrantLock
  • ✅ 支持自定义机器 ID 和数据中心 ID,适配分布式部署
  • ✅ 序列号溢出自动等待下一毫秒,避免 ID 重复
2. Sharding-JDBC 自定义复合分片策略(哈希 + 时间)🔥
代码语言:java
复制
/**
 * 复合分片策略:按用户ID哈希分表 + 按创建时间分库
 * 优点:数据分布均匀,支持按时间快速查询历史数据
 */
public class UserOrderComplexShardingStrategy implements ComplexKeysShardingAlgorithm<Long> {
    // 分库数量
    private static final int DB_COUNT = 4;
    // 每个库的分表数量
    private static final int TABLE_COUNT_PER_DB = 8;

    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<Long> shardingValue) {
        // 获取分片键值
        Map<String, Collection<Long>> valueMap = shardingValue.getColumnNameAndShardingValuesMap();
        Long userId = valueMap.get("user_id").iterator().next();
        Long createTime = valueMap.get("create_time").iterator().next();

        // 按时间分库:每半年一个库
        LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(createTime), ZoneId.systemDefault());
        int year = time.getYear() - 2020;
        int month = time.getMonthValue();
        int dbIndex = (year * 2 + (month > 6 ? 1 : 0)) % DB_COUNT;

        // 按用户ID哈希分表
        int tableIndex = Math.abs(userId.hashCode()) % TABLE_COUNT_PER_DB;

        // 拼接实际表名
        String actualTableName = String.format("order_db_%d.order_%d", dbIndex, tableIndex);
        return Collections.singletonList(actualTableName);
    }

    @Override
    public void init(Properties properties) {}

    @Override
    public String getType() {
        return "USER_ORDER_COMPLEX_SHARDING";
    }
}
技术亮点:
  • ✅ 复合分片策略,同时利用两个分片键的优势
  • ✅ 按时间分库,天然支持历史数据归档和删除
  • ✅ 按用户 ID 哈希分表,数据分布均匀,避免数据倾斜
  • ✅ 支持动态调整分库分表数量,无需修改业务代码
3. 游标分页实现(解决深度分页性能问题)🚀
代码语言:java
复制
@Service
public class OrderPaginationService {
    @Autowired
    private OrderMapper orderMapper;

    /**
     * 游标分页查询(性能比offset分页高100倍以上)
     * 对比:offset分页需要扫描前N条数据,游标分页直接定位到起始位置
     */
    public PageResult<Order> getOrdersByCursor(Long userId, Long cursor, int pageSize) {
        List<Order> orders;
        if (cursor == null) {
            // 第一页,没有游标
            orders = orderMapper.selectFirstPage(userId, pageSize);
        } else {
            // 后续页,使用游标(上一页最后一条记录的ID)
            orders = orderMapper.selectByCursor(userId, cursor, pageSize);
        }

        // 计算是否有下一页
        boolean hasNext = orders.size() == pageSize;
        // 获取下一页的游标
        Long nextCursor = hasNext ? orders.get(orders.size() - 1).getId() : null;

        return new PageResult<>(orders, hasNext, nextCursor);
    }
}

// Mapper接口
public interface OrderMapper {
    @Select("SELECT * FROM order WHERE user_id = #{userId} ORDER BY id DESC LIMIT #{pageSize}")
    List<Order> selectFirstPage(@Param("userId") Long userId, @Param("pageSize") int pageSize);

    @Select("SELECT * FROM order WHERE user_id = #{userId} AND id < #{cursor} ORDER BY id DESC LIMIT #{pageSize}")
    List<Order> selectByCursor(@Param("userId") Long userId, @Param("cursor") Long cursor, @Param("pageSize") int pageSize);
}

// 分页结果类
@Data
@AllArgsConstructor
public class PageResult<T> {
    private List<T> data;
    private boolean hasNext;
    private Long nextCursor;
}
技术亮点:
  • ✅ 彻底解决 offset 分页的深度分页性能问题,查询时间稳定
  • ✅ 实现简单,无需修改数据库结构
  • ✅ 支持无限分页,没有数据量限制
  • ✅ 避免了全表扫描,数据库压力小

分库分表核心技术难点与深度解决方案 🚩

技术难点

根本原因

最优解决方案

技术亮点

避坑指南

数据倾斜

分片键选择不当,部分分片数据量远超其他

  1. 热点分片拆分 2. 一致性哈希 + 虚拟节点 3. 动态分片路由

热点数据自动识别与分流,支持在线调整分片权重

❌ 绝对不要用状态字段(如订单状态)作为分片键

跨库 COUNT/SUM 聚合

无法直接执行 SQL 聚合,结果不准确

  1. 预计算 + 定时任务更新 2. 实时聚合引擎(ClickHouse) 3. 应用层归并(小数据量)

冷热数据分离,热数据实时聚合,冷数据预计算

❌ 不要在高并发场景下做跨库实时聚合

平滑扩容(不停服)

数据重分布需要迁移大量数据,影响业务

  1. 一致性哈希(减少迁移量) 2. 按范围分片(天然支持扩容) 3. 双写迁移法(通用方案)

增量数据同步 + 全量校验,业务无感知

❌ 扩容前必须做压测,避免迁移过程中数据库压力过大

读写一致性

主从延迟导致刚写入的数据读不到

  1. 强制读主库(强一致性) 2. 读自己写的走主库 3. 延迟容忍度分级

基于业务场景动态选择读策略,平衡性能与一致性

❌ 不要盲目开启读写分离,核心交易场景必须强制读主

分布式事务回滚失败

部分分支事务失败,无法全局回滚

  1. Seata AT 模式自动回滚 2. TCC 模式手动补偿 3. 定时任务兜底校验

事务日志持久化,失败自动重试,人工干预接口

❌ 避免长事务,事务超时时间设置合理

分库分表后的运维

多数据源管理复杂,备份恢复困难

  1. 统一数据源管理平台 2. 按分片备份恢复 3. 自动化运维工具

一键扩容、一键迁移、一键备份,减少人工操作

❌ 不要手动修改分片数据,所有操作通过统一平台执行

真实模拟面试

真实模拟面试

面试官(身子微微前倾,笑着看向你):

“咱们聊个实在的。你现在负责的订单系统,单库单表已经到瓶颈了,你决定分库分表。那你踩过哪些实打实的坑?怎么填的?一个一个说。” 😏

候选人(搓了搓手,记忆犹新):

“好,这一拆,真是捅了马蜂窝。我先给您画个痛点的全景图,您一看就明白👇”

“这四个玩意儿,一个比一个让人头秃。我按顺序跟您讲讲当时我们流的血和泪。” 😅

面试官:

“行,那先说说 ID 那块,自增主键冲突你们怎么搞的?”

候选人:

“原来 AUTO_INCREMENT 爽得很,拆成 32 张表后直接傻眼。UUID 太长、索引不友好,我们毫不犹豫上了雪花算法 Snowflake 的魔改版。

机器 ID 这块,因为我们跑在 k8s 上,直接用 Pod IP 最后两个字节异或一下当 workerId,部署时自动分配,不用人工配。

核心噩梦是时钟回拨。有一次 NTP 同步把时间往回拨了 3 秒,差点没把我们搞死。😱 后来我们在 ID 生成器里加了个硬防御:如果当前时间比上次生成时间小,就自旋等待差值加倍时间;要是回拨超过 500ms,直接抛异常、告警,那台机器熔断不接流量,等时钟追平再恢复。稳得很。”

面试官(点头):

“跨分片查询呢?按 user_id 分片,运营非要按时间范围查全部,还要求分页,这你们怎么‘伺候’的?” 🤔

候选人:

“这就是最隐蔽的雷!我们当时差点跟产品打起来。最后形成了三套组合拳:

  • 联机查询:强迫前端改成游标分页(传 last_id),每次并行查所有分片 WHERE id > last_id ORDER BY id LIMIT N,内存里归并取最小 N 条,这样不分页,只有上下滑动。敢点“跳转到第 100 页”就直接报错 —— 因为真实数据量下内存归并 100 页就是找死。🤯
  • 后台复杂检索:把订单数据异构到 Elasticsearch,按时间、金额各种条件搜,拿到订单 ID 列表,再回分片 WHERE id IN(...) 查详细。延迟秒级,够用。
  • 报表分析:干脆全量甩到 ClickHouse,跟在线库物理隔离,爱怎么 group by 都行。

我拿 B+ 树推算说服了产品:分片下 LIMIT 100,20 这种偏移量越大性能越烂,产品听完也认了。” ✌️

面试官:

“说得好,那分布式事务呢?@Transactional 肯定废了,你们对一致性要求高的扣款怎么搞的?”

候选人:

“我们铁律就一条:能不用强分布式事务就不用。扣款这种场景用了 TCC 模式。

比如扣库存,Try 阶段用 Redis DECR 预占,同时把冻结流水写进一张 frozen_record 表(和业务库同分片,本地事务保证)。Confirm 阶段真正扣减数据库库存并删冻结记录,Cancel 则把 Redis 库存加回去。

这块最忌讳空谈理论。我们还做了双重兜底:一是所有 TCC 接口必须幂等,靠唯一流水号防重;二是每天凌晨跑一套对账脚本,对比支付单和库存流水,差一分钱马上报警。金融业务没有对账就是裸奔。” 💰

对于非核心链路,比如发送优惠券,我们直接用本地消息表 + RocketMQ 事务消息,保证最终一致性就完事了。

面试官:

“那最后说扩容吧,假如你开始是 8 库 16 表,现在要扩到 16 库 32 表,数据要重分布,怎么弄不丢数据还不长时间停机?” ⏳

候选人:

“扩容绝对是终极大考,我们用的双写 + 灰度切流的方案,分四步走:

  1. 升级路由:让所有分片路由支持新旧两组分片键算法(我们用的是一致性哈希,150 个虚拟节点,比取模好扩容)。
  2. 开启双写:所有新写入同时发到新旧两个分片集群,读仍走老集群。
  3. 历史数据回流:凌晨用离线任务扫描老表,把数据按新算法 insert 到新表,忽略重复。同时校验工具不停抽 rows 比对 MD5,不一致就重导。
  4. 切读:校验一致后,灰度把读流量切到新集群,观察无报错,最后把老集群双写下线。

有一次迁移了 2 亿条数据,干了一整晚,好在全自动脚本稳住了,第二天早上只公告停机了 20 分钟。😌 如果不追求秒级无损,停服搬家最稳妥,不丢人。”

面试官(微笑):

“总结得不错,能看出你是真刀真枪干过的。那最后一个开放点,做完这些你架构上最大的体会是什么?”

候选人:

“分库分表本质是把压力分散,但把查询复杂度集中了。所以不能光拆,要同步做‘减熵’:联机库只保留最核心的主键查询和小范围扫描,把复杂分析、搜索甩给 ES/列存。代码里杜绝跨分片 join,宁肯多查一次。这样架构才不会腐化成一坨没人敢动的屎山。” 💩

💻 硬核时刻:代码实现与难点总结

面试官(双手交叉,饶有兴趣):

“别光说方案,把你刚才提到的 雪花算法防时钟回拨、跨分片游标分页、TCC 幂等扣库存 这几个最见功底的代码,挑关键部分给我看看。再把你整个分库分表面临的技术难点,一表给我汇总。” ⌨️

候选人(打开笔记本,认真翻出代码片段):

“没问题,我直接上 Java 伪代码,去掉业务敏感词,保留骨骼,您一看就懂。”

1. 分布式 ID 生成:时钟回拨熔断版 Snowflake
代码语言:java
复制
public class SnowflakeIdGenerator {
    private final long epoch = 1577836800000L; // 2020-01-01
    private final long workerIdBits = 10L;
    private final long sequenceBits = 12L;
    private final long maxWorkerId = ~(-1L << workerIdBits);
    private final long sequenceMask = ~(-1L << sequenceBits);
    private final long workerIdShift = sequenceBits;
    private final long timestampShift = sequenceBits + workerIdBits;

    private long workerId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;
    private final long maxBackwardMs = 500; // 允许最大回拨 500ms

    public SnowflakeIdGenerator(long workerId) {
        if (workerId > maxWorkerId || workerId < 0) throw new IllegalArgumentException();
        this.workerId = workerId;
    }

    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();
        // 🧨 时钟回拨防御
        if (timestamp < lastTimestamp) {
            long offset = lastTimestamp - timestamp;
            if (offset <= maxBackwardMs) {
                try { Thread.sleep(offset << 1); } // 等两倍差值
                catch (InterruptedException e) { Thread.currentThread().interrupt(); }
                timestamp = System.currentTimeMillis();
                if (timestamp < lastTimestamp) throw new RuntimeException("Clock moved backwards!");
            } else {
                throw new RuntimeException("Clock moved backwards too much!");
            }
        }
        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) timestamp = tilNextMillis(lastTimestamp);
        } else {
            sequence = 0L;
        }
        lastTimestamp = timestamp;
        return ((timestamp - epoch) << timestampShift) | (workerId << workerIdShift) | sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long ts = System.currentTimeMillis();
        while (ts <= lastTimestamp) ts = System.currentTimeMillis();
        return ts;
    }
}

技术亮点:内置两级时钟回拨处理 —— 小回拨自旋等待,超限直接熔断抛异常,配合监控保证 ID 绝对递增。

2. 跨分片游标分页:内存归并 + 防 OOM
代码语言:java
复制
public List<Order> scrollOrders(long lastId, int limit, List<Shard> shards) {
    int shardCount = shards.size();
    List<CompletableFuture<List<Order>>> futures = new ArrayList<>();
    
    // 并行查询每个分片
    for (Shard shard : shards) {
        futures.add(CompletableFuture.supplyAsync(() ->
            shard.query("SELECT * FROM order WHERE id > ? ORDER BY id LIMIT ?", lastId, limit)
        ));
    }
    
    // 内存归并,使用最小堆取全局最小的 N 条
    PriorityQueue<Order> minHeap = new PriorityQueue<>(Comparator.comparingLong(Order::getId));
    for (CompletableFuture<List<Order>> future : futures) {
        List<Order> part = future.join();
        if (part.size() > limit) part = part.subList(0, limit); // 防爆
        minHeap.addAll(part);
    }
    
    List<Order> result = new ArrayList<>(limit);
    while (!minHeap.isEmpty() && result.size() < limit) {
        result.add(minHeap.poll());
    }
    return result;
}

✨ 技术亮点:CompletableFuture 并行,PriorityQueue 归并取 Top N,强制截断单分片结果集防 OOM,天然适配瀑布流分页。

3. TCC 库存扣减:幂等 + Redis 预占 + 本地事务
代码语言:java
复制
// Try 阶段
@Transactional
public boolean tryFreeze(String orderId, long skuId, int count) {
    // 幂等:先查流水
    if (frozenRecordDao.exists(orderId)) return true;
    // Redis 预占库存,原子操作
    Long remain = redis.decrBy("stock:sku:" + skuId, count);
    if (remain == null || remain < 0) {
        redis.incrBy("stock:sku:" + skuId, count); // 回滚 Redis
        return false;
    }
    // 写冻结流水(和订单同库,本地事务)
    frozenRecordDao.insert(new FrozenRecord(orderId, skuId, count));
    return true;
}

// Confirm 阶段
@Transactional
public void confirm(String orderId) {
    FrozenRecord record = frozenRecordDao.selectForUpdate(orderId);
    if (record == null || record.getStatus() == CONFIRMED) return; // 幂等
    // 正式扣减数据库库存
    stockDao.decrease(record.getSkuId(), record.getCount());
    // 标记完成
    record.setStatus(CONFIRMED);
    frozenRecordDao.update(record);
    // Redis 不需要管,预占时已经扣了
}

// Cancel 阶段
@Transactional
public void cancel(String orderId) {
    FrozenRecord record = frozenRecordDao.selectForUpdate(orderId);
    if (record == null || record.getStatus() == CANCELLED) return; // 幂等
    // 释放 Redis 库存
    redis.incrBy("stock:sku:" + record.getSkuId(), record.getCount());
    record.setStatus(CANCELLED);
    frozenRecordDao.update(record);
}

技术亮点:所有操作靠流水状态做幂等,Redis 和 DB 不一致时依靠每天对账脚本修复,实现最终一致。

4. 一致性哈希路由 & 扩容双写
代码语言:java
复制
public class ConsistentHashRouter {
    private final TreeMap<Integer, Shard> ring = new TreeMap<>();
    private final int virtualNodeCnt;
    private final List<Shard> newShardGroup = null; // 扩容时临时持有新集群

    public Shard route(long shardKey) {
        int hash = hash(shardKey);
        if (ring.ceilingEntry(hash) != null) return ring.ceilingEntry(hash).getValue();
        return ring.firstEntry().getValue();
    }

    // 双写:同时写入新旧集群
    public void insertWithDualWrite(Order order) {
        Shard oldShard = route(order.getUserId());
        oldShard.insert(order);
        if (newShardGroup != null) {
            Shard newShard = newConsistentHashRoute(order.getUserId());
            newShard.insert(order);
        }
    }
}

技术亮点TreeMap 实现环,虚拟节点均匀分布;扩容期间 newShardGroup 非空时开启双写,灰度切换读流量。

📊 技术难点 & 解决方案速查表

技术难点

问题表现

解决方案

关键手段

分布式 ID 冲突

多库自增主键重复

雪花算法 + workerId 自动分配

时钟回拨两级防御(自旋&熔断)

跨分片排序分页

全局分页需归并所有分片

游标分页 + 内存归并 + 异构索引

拒绝跳页,限制单分片结果集

分布式事务一致

跨库操作原子性丧失

TCC + 本地消息表 + 对账

幂等设计,Redis 预占,每日对账

扩容数据迁移

新分片加入需数据重分布

一致性哈希 + 双写 + 灰度切流

虚拟节点,MD5 校验,停服兜底

分片热点倾斜

个别分片负载过高

分片键打散(如尾部加随机数)或再分片

监控分区 QPS,动态路由感知

面试官(满意地合上笔记本):

“代码清爽,回拨处理、归并防 OOM、幂等设计都到位了,看起来是写过上万行核心代码的人。"

别走,交个朋友

我是Rain ,一个喜欢把复杂技术讲透、让代码落地的实践者。

如果你厌倦了四处收集碎片化的八股文,想看看一线项目里真实的架构决策和踩坑记录,欢迎来我的公众号「Rain的Java大神之路」坐坐。每天一个知识点,程序员的加油站。

在这里,我会把每一次技术复盘、每一个项目的设计源码,毫无保留地分享给你。

🔥技术这条路很酷,我们结伴同行。

🚀Keep Coding, Keep Loving —— 与所有 Java 同路人共勉。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 分库分表会带来哪些问题如何解决
    • 核心问题与解决方案对照表 📊
    • 关键问题深度解析 🔍
      • 1. 分布式事务:最头疼的问题 😫
      • 2. 跨库查询:最常见的坑 🕳️
      • 3. 数据迁移:最容易出事故的环节 ⚠️
      • 不停服双写迁移流程图:
    • 分库分表设计原则 ✅
    • 面试官追问高频点 💡
    • 核心代码实现(面试必背,带技术亮点)💻
      • 1. 雪花算法(带时钟回拨防护,面试必写)✨
      • 技术亮点:
      • 2. Sharding-JDBC 自定义复合分片策略(哈希 + 时间)🔥
      • 技术亮点:
      • 3. 游标分页实现(解决深度分页性能问题)🚀
      • 技术亮点:
    • 分库分表核心技术难点与深度解决方案 🚩
  • 真实模拟面试
    • 真实模拟面试
      • 面试官(身子微微前倾,笑着看向你):
      • 候选人(搓了搓手,记忆犹新):
      • 面试官:
      • 候选人:
      • 面试官(点头):
      • 候选人:
      • 面试官:
      • 候选人:
      • 面试官:
      • 候选人:
      • 面试官(微笑):
      • 候选人:
    • 💻 硬核时刻:代码实现与难点总结
      • 面试官(双手交叉,饶有兴趣):
      • 候选人(打开笔记本,认真翻出代码片段):
      • 1. 分布式 ID 生成:时钟回拨熔断版 Snowflake
      • 2. 跨分片游标分页:内存归并 + 防 OOM
      • 3. TCC 库存扣减:幂等 + Redis 预占 + 本地事务
      • 4. 一致性哈希路由 & 扩容双写
    • 📊 技术难点 & 解决方案速查表
      • 面试官(满意地合上笔记本):
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档