去哪面试:1Wtps高并发,MySQL 热点行 问题, 怎么解决?

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 去哪面试:1Wtps高并发,MySQL 热点行 问题, 怎么解决?

本文的 原始地址 ,传送门

本文的 原始地址 ,传送门

说在前面

在45岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团、小米、 去哪儿的面试资格,遇到很多很重要的面试题::

  • 1Wtps高并发,MySQL热点行 问题, 怎么解决?
  • MySQL 转账 热点行 问题, 怎么解决?

最近有小伙伴在面试 去哪儿,又遇到了相关的面试题。小伙伴懵了,因为没有遇到过,所以支支吾吾的说了几句,面试官不满意,面试挂了。

所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。

当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V171版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,回复:领电子书

问题分析:mysql 热点行 问题 ,到底有多么严重

结合互联网真实的高并发场景(比如双十一、秒杀活动),来看看 mysql热点问题 ,到底有多么严重。

为什么热点操作会卡死?

‌1. 锁的“独木桥”问题‌

一把myql记录锁,就是一条只能过一个人的独木桥,一堆人抢着过桥,后面的人只能排队。

MySQL的热点行更新就是 “独木桥” 逻辑:‌ 转账 修改,就是大家 挤上独木桥, 去同一行数据(比如账户A扣钱,账户B加钱),每个事务都要给这行数据加锁 ,导致所有操作必须排队。

真实案例‌:

某电商平台在双十一期间,因用户频繁充值, 用户充值一般都是到同一个账户(比如 一个 公共的平台红包账户)。

突发流量场景, 导致 平台红包账户 的 余额行成为热点,TPS从正常的1000提升到 10000,甚至 10W,系统几乎瘫痪。

‌2. 死锁检测的“资源内耗”问题——‌ 雪上加霜

当多个事务互相等待对方释放锁时,就会死锁。

真实案例‌:

某社交平台的支付系统,在死锁检测开启时,CPU利用率高达90%,关闭死锁检测后降到40%,但代价是超时事务增加(需要业务层重试)。

MySQL有一个死锁检测机制,(类似 “交警”)负责处理这种情况,但交警自己也要消耗资源。

假设10个人同时转账给同一个人,事务1锁了行A等行B,事务2锁了行B等行A,此时交警(死锁检测)需要判断谁该回滚。

如果每秒有1000个事务,交警需要检查1000×1000=100万次可能的死锁组合,CPU直接飙到100%。

‌3. 业务层不断重试——恶性循环‌

当数据库扛不住高并发时,业务层的重试机制(比如Java代码里的事务重试)会让问题更严重。

真实案例‌:

某银行系统在促销活动中,因未限制重试次数,导致10%的失败请求触发了3次重试,实际请求量膨胀到130%,数据库彻底宕机。

用户点了一次转账,接口超时 → 前端自动重试 → 重复请求打到数据库 → 锁冲突更多 → 更多超时 → 更多重试 → 最终数据库崩溃。

热点行 根因分析 梳理

问题 现象 解决方案 成本 效果
锁竞争严重 转账超时、系统卡死 拆分子账户 提升5-10倍
死锁检测耗CPU CPU 90%、响应慢 关闭死锁检测 风险可控
重试导致雪崩 数据库崩溃 限制重试次数 快速止损
硬件瓶颈 花钱就能解决,但太贵 用云数据库 立竿见影

热点行 问题,是一个 共性问题

热点行问题不仅出现在转账场景,几乎所有高并发更新同一行的操作都会中招:

  • 库存扣减‌:秒杀活动中,热点商品库存行, 会 被频繁更新。
  • 计数器‌:热点文章的点赞数更新 、热点视频的 播放量更新。
  • 账户积分‌:用户积分集中兑换。

热点‌行问题的四大 解决方案

‌方案1:绕过独木桥——最终一致性‌

  • 场景‌:用Redis缓存余额,异步更新到数据库。
  • 效果‌:Redis单机吞吐量5万+/秒,远高于MySQL。
  • 代价‌:可能出现短暂的不一致(比如余额显示延迟)。

‌方案2:排队上桥——请求合并‌

  • 场景‌:在Java应用层用队列(比如RocketMQ)缓冲请求,每隔100ms批量处理一次转账。
  • 效果‌:将100次写操作合并成1次(update balance=balance-100),减少锁竞争。
  • 代价‌:延迟增加(用户可能看到转账处理中)。

‌方案3:把独木桥拆成多个桥——分治思想‌

  • 场景‌:支付宝红包账户拆分成100个子账户(比如按用户ID尾号分)。
  • 效果‌:热点行压力分散到100行,并发能力提升10倍(假设均匀分布)。
  • 代价‌:业务逻辑变复杂(需要路由到子账户),对账麻烦。

‌方案4:升级‌更宽的桥——投钱

  • 场景‌:使用阿里云 POLARDB(高并发优化的MySQL),或改用TiDB(分布式数据库)。
  • 效果‌:POLARDB 热点行并发能力可达1万+ TPS,是普通MySQL的10倍。
  • 代价‌:成本高(1个POLARDB实例≈10台普通MySQL服务器的价格)。

短期-中期-长期 解决方案:

  • 短期:先关死锁检测 + 限制重试次数 + 限流 ,能扛过活动高峰。成本最低,但是用户体验差。
  • 中期:最终一致性方案设计:Redis缓存库存 + RocketMQ异步同步 。成本较低,用户体验好点。
  • 长期:把红包账户拆成100个子账户,代码改造成本中等,效果最好。
  • 土豪方案:直接上阿里云POLARDB,1小时搞定,但每年多花50万。

中期方案 :Redis缓存 + RocketMQ异步 最终一致性方案设计

利用RocketMQ实现可靠异步消息传递,将Redis库存变更异步同步到MySQL,实现 最终一致性方案。

1、Redis缓存 + RocketMQ异步 分层架构 设计

2. 关键设计点

  • 消息可靠性‌:使用RocketMQ事务消息确保操作不丢失
  • 顺序保证‌:同一SKU的库存变更消息按顺序消费
  • 幂等设计‌:通过唯一请求ID避免重复消费

3. 数据结构设计

RocketMQ消息体‌(JSON格式):

 {  
  "requestId": "20231107123456_1001", // 唯一请求ID  
  "skuId": 1001,  
  "delta": -2,                        // 库存变化量(扣减为负,回滚为正)  
  "opTime": 1699372800000,            // 操作时间戳  
  "version": 1699372800000            // 版本号(Redis同步时间戳)  
}

中期方案核心代码实现(Java + SpringBoot + Redis + RocketMQ)

1. 库存扣减与消息发送(生产者)

public class SeckillService {
    // Redis扣减并发送MQ消息
    public boolean deductWithMQ(Long skuId, int num) {
        String stockKey = "sku_stock:" + skuId;
        String versionKey = "sku_version:" + skuId;

        // 1. Redis原子扣减
        Long stock = redisTemplate.opsForValue().decrement(stockKey, num);
        if (stock < 0) {
            redisTemplate.opsForValue().increment(stockKey, num); // 回滚
            return false;
        }

        // 2. 发送事务消息
        TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(
            "SeckillTopic",
            MessageBuilder.withPayload(buildStockMessage(skuId, -num)).build(),
            null
        );
        return result.getSendStatus() == SendStatus.SEND_OK;
    }

构建库存 变更消息



    private StockMessage buildStockMessage(Long skuId, int delta) {
        return new StockMessage(
            UUID.randomUUID().toString(),
            skuId,
            delta,
            System.currentTimeMillis(),
            (Long) redisTemplate.opsForValue().get("sku_version:" + skuId)
        );
    }


}

RocketMQ事务监听器(确保本地操作与消息发送一致)


    @RocketMQTransactionListener
    public class TransactionListenerImpl implements RocketMQLocalTransactionListener {
        @Override
        public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
            // 若Redis扣减成功,则提交消息
            return RocketMQLocalTransactionState.COMMIT;
        }

        @Override
        public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
            // 无需二次检查(Redis操作已成功)
            return RocketMQLocalTransactionState.COMMIT;
        }
    }

2. 消息消费 写入 MySQL

@RocketMQMessageListener(
    topic = "SeckillTopic",
    consumerGroup = "StockSyncConsumer",
    selectorExpression = "*",
    consumeMode = ConsumeMode.ORDERLY // 保证同一SKU顺序消费
)
public class StockSyncConsumer implements RocketMQListener<StockMessage> {

    @Override
    public void onMessage(StockMessage message) {
        // 1. 幂等检查(通过requestId去重)
        if (redisTemplate.opsForValue().get("mq_idempotent:" + message.getRequestId()) != null) {
            return;
        }

        // 2. 版本控制(避免旧消息覆盖新数据)
        Long currentVersion = seckillSkuMapper.getVersion(message.getSkuId());
        if (message.getVersion() <= currentVersion) {
            return;
        }

        // 3. 更新MySQL(带版本号的乐观锁)
        int rows = seckillSkuMapper.updateStock(
            message.getSkuId(),
            message.getDelta(),
            message.getVersion()
        );

        // 4. 更新成功则记录幂等标识
        if (rows > 0) {
            redisTemplate.opsForValue().set(
                "mq_idempotent:" + message.getRequestId(), 
                "1", 
                5, TimeUnit.MINUTES
            );
        } else {
            // 失败重试(RocketMQ自带重试机制)
            throw new RuntimeException("Sync failed, retry later");
        }
    }
}

// 数据库操作Mapper
@Update("UPDATE seckill_sku SET stock = stock + #{delta}, version = #{version} " +
        "WHERE sku_id = #{skuId} AND version < #{version}")
int updateStock(
    @Param("skuId") Long skuId,
    @Param("delta") int delta,
    @Param("version") Long version
);

3. 补偿机制设计

 // 监听死信队列(同步失败超过16次的消息)
@RocketMQMessageListener(
    topic = "%DLQ%StockSyncConsumer",
    consumerGroup = "StockSyncDLQConsumer"
)
public class StockSyncDLQConsumer implements RocketMQListener<StockMessage> {

    public void onMessage(StockMessage message) {
        // 1. 记录异常日志并告警
        log.error("库存同步失败: {}", message);
        alertService.notify("库存同步异常", message.toString());

        // 2. 人工介入检查(示例:自动对比Redis与MySQL库存)
        Long redisStock = redisTemplate.opsForValue().get("sku_stock:" + message.getSkuId());
        Integer dbStock = seckillSkuMapper.getStock(message.getSkuId());
        if (!redisStock.equals(dbStock)) {
            // 自动修复(以Redis为准)
            seckillSkuMapper.forceUpdateStock(
                message.getSkuId(), 
                redisStock, 
                System.currentTimeMillis()
            );
        }
    }
}

中期方案 潜在问题与优化

消息顺序与版本控制

  • 现象‌:网络延迟导致旧版本消息覆盖新版本
  • 解决‌:消费者端增加版本号校验(仅处理更高版本的消息)

Redis与MySQL数据偏差

  • 监控‌:实时对比关键SKU库存(误差>5%触发告警)
  • 修复‌:定时任务全量同步(兜底策略,每天凌晨执行)

消息堆积风险

  • 扩容‌:根据堆积量动态增加Consumer实例
  • 降级‌:堆积超过阈值时,暂停非核心SKU的秒杀

中期方案 ‌分布式锁优化‌

// 热点SKU扣减时增加本地锁(减少Redis压力)
private final Map<Long, ReentrantLock> skuLocks = new ConcurrentHashMap<>();

public boolean deductWithLock(Long skuId, int num) {
    skuLocks.putIfAbsent(skuId, new ReentrantLock());
    ReentrantLock lock = skuLocks.get(skuId);
    try {
        if (lock.tryLock(10, TimeUnit.MILLISECONDS)) {
            return deductWithMQ(skuId, num);
        }
        return false;
    } finally {
        lock.unlock();
    }
}

中期方案 效果验证

1 压测对比

场景 TPS 平均延迟 数据一致性延迟
纯MySQL 200 500ms 0
Redis+RocketMQ 45,000 8ms 300ms~2s

2 监控指标

  • RocketMQ消息堆积量
  • 同步成功率(99.99%以上为正常)
  • Redis与MySQL库存差异率

方案总结:Redis缓存 + RocketMQ异步 最终一致性方案设计

45岁老架构师尼恩提示: 由于扣减红包、转账等,和 秒杀差不多,下面以秒杀场景为例,进行方案介绍。

以秒杀场景为例。

  • 用户抢购时,先在Redis完成极速扣减(微秒级响应)
  • 扣减成功后,通过RocketMQ可靠消息异步同步到MySQL
  • 同一商品的库存变更按顺序处理,通过版本号避免数据覆盖
  • 万一同步失败,先自动重试,最终由人工兜底修复
阶段 技术栈 设计要点
扣减 Redis+Lua+本地锁 原子操作、热点数据分散锁
消息生产 RocketMQ事务消息 保证本地操作与消息发送的原子性
消息消费 顺序消费+幂等+版本控制 避免乱序和重复消费
补偿 死信队列+自动修复 系统自愈能力建设

60分 (菜鸟级) 答案

尼恩提示,讲完 缓存+异步 , 可以得到 60分了。

但是要直接拿到大厂offer,或者 offer 直提,需要 120分答案。

尼恩带大家继续,挺进 120分,让面试官 口水直流。

中期改进,请求合并方案设计,秒杀库存批量扣减

目标‌:通过消息队列缓冲请求,合并多次扣减操作,减少数据库锁竞争,提升吞吐量。

请求合并方案设计 架构设计

1 核心流程

2 关键设计点
  • 请求缓冲‌:使用RocketMQ事务消息确保请求不丢失
  • 合并窗口‌:每100ms或每积累100个请求触发一次批量处理
  • 异步响应‌:前端轮询或WebSocket通知处理结果
3 数据库表结构
CREATE TABLE seckill_sku (  
    sku_id BIGINT PRIMARY KEY,  
    stock INT COMMENT '剩余库存',  
    version INT COMMENT '乐观锁版本号'  
);

中期改进 核心代码实现(Java + SpringBoot + RocketMQ)

1. 请求接收层(带本地缓存合并)
@Component  
public class RequestBuffer {  
    // 合并窗口:100ms  
    private static final long BUFFER_WINDOW = 100;   
    // 本地缓存(Key: SKU_ID, Value: 待扣减数量累计)  
    private final Map<Long, Integer> buffer = new ConcurrentHashMap<>();  
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);  

    @PostConstruct  
    public void init() {  
        // 定时触发批量处理  
        scheduler.scheduleAtFixedRate(this::flushBuffer, BUFFER_WINDOW, BUFFER_WINDOW, TimeUnit.MILLISECONDS);  
    }  

    // 接收单个请求(立即返回"处理中"状态)  
    public String handleRequest(Long userId, Long skuId) {  
        String requestId = generateRequestId(userId, skuId);  
        // 发送事务消息到RocketMQ(确保消息落盘)  
        rocketMQTemplate.sendMessageInTransaction(  
            "SeckillTopic",   
            MessageBuilder.withPayload(new RequestItem(requestId, skuId, 1)).build(),  
            null  
        );  
        return requestId;  
    }  

    // 事务消息监听器(本地事务执行)  
    @RocketMQTransactionListener  
    public class TransactionListenerImpl implements RocketMQLocalTransactionListener {  
        @Override  
        public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {  
            RequestItem item = (RequestItem) msg.getPayload();  
            buffer.compute(item.getSkuId(), (k, v) -> v == null ? 1 : v + 1);  
            return RocketMQLocalTransactionState.COMMIT;  
        }  

        @Override  
        public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {  
            return RocketMQLocalTransactionState.COMMIT;  
        }  
    }  
}
2. 批量处理服务(消费者逻辑)
@RocketMQMessageListener(topic = "SeckillTopic", consumerGroup = "SeckillConsumer")  
public class BatchConsumer implements RocketMQListener<List<RequestItem>> {  

    @Override  
    public void onMessage(List<RequestItem> items) {  
        // 按SKU分组合并扣减数量  
        Map<Long, Integer> deductMap = items.stream()  
            .collect(Collectors.groupingBy(  
                RequestItem::getSkuId,  
                Collectors.summingInt(RequestItem::getDeductNum)  
            ));  

        // 批量更新数据库  
        deductMap.forEach((skuId, totalDeduct) -> {  
            SeckillSku sku = seckillSkuMapper.selectById(skuId);  
            if (sku.getStock() >= totalDeduct) {  
                int rows = seckillSkuMapper.deductStockWithVersion(  
                    skuId, totalDeduct, sku.getVersion()  
                );  
                if (rows > 0) {  
                    // 成功:通知前端  
                    notifySuccess(skuId, totalDeduct);  
                } else {  
                    // 失败:触发补偿逻辑  
                    handleConflict(skuId, totalDeduct);  
                }  
            } else {  
                // 库存不足:部分退款  
                handlePartialRefund(skuId, totalDeduct, sku.getStock());  
            }  
        });  
    }  

    // 带乐观锁的批量扣减SQL  
    @Update("UPDATE seckill_sku SET stock = stock - #{deductNum}, version = version + 1 " +  
            "WHERE sku_id = #{skuId} AND version = #{version}")  
    int deductStockWithVersion(  
        @Param("skuId") Long skuId,  
        @Param("deductNum") int deductNum,  
        @Param("version") int version  
    );  
}
3. 前端异步查询接口
@RestController  
public class ResultController {  
    @GetMapping("/result")  
    public String getResult(@RequestParam String requestId) {  
        // 查询Redis中该请求的处理状态  
        String status = redisTemplate.opsForValue().get(requestId);  
        return status != null ? status : "processing";  
    }  
}

中期改进:潜在问题与优化

1 合并导致延迟

  • 现象‌:用户需等待100ms~200ms才能得到结果
  • 优化‌:动态调整合并窗口(例如:10ms内请求量>50则立即触发)

2 批量操作部分失败

  • 场景‌:合并扣减100件,但库存只剩80件
  • 解决‌:按时间戳顺序部分成功,其余请求自动退款

3 消息堆积

  • 监控‌:实时监控RocketMQ堆积量,触发自动扩容
  • 降级‌:堆积超过阈值时,切换为直接扣减模式

中期改进:效果验证

1 ‌压测数据对比

  • 未合并‌:1000并发下,TPS约200,95%响应时间>500ms
  • 合并后‌:1000并发下,DB侧的 TPS提升至5000+,95%响应时间<200ms

2 ‌监控指标

  • RocketMQ消息堆积量
  • 数据库锁等待时间(SHOW ENGINE INNODB STATUS
  • 用户可见延迟分布(90%用户<200ms,99%用户<300ms)

‌中期改进 总结‌:

请求合并方案通过异步化与批量处理,将高频单行更新转化为低频批量操作,显著降低锁竞争,适用于允许短暂延迟的场景。

代码实现需重点关注‌消息可靠性‌、‌合并策略‌和‌部分失败补偿‌。

80分 (高手级) 答案

尼恩提示,讲完 请求合并方案设计,秒杀库存批量扣减 架构 , 可以得到 80分了。

但是要直接拿到大厂offer,或者 offer 直提,需要 120分答案。

尼恩带大家继续,挺进 120分,让面试官 口水直流。

长期方案:分治架构,秒杀库存拆分(SKU分桶)

将一个SKU的库存分散到N个子SKU,降低单行锁竞争,提升并发能力。

  • 场景‌:秒杀库一个sku库存 拆分成100个子sku库存 (比如按sku库存 ID尾号分)。
  • 效果‌:热点行压力分散到100行,并发能力提升10倍(假设均匀分布)。
  • 代价‌:业务逻辑变复杂(需要路由到子 sku库存 ),对账麻烦。

1、长期方案 架构设计

分桶规则
  • ‌路由策略:根据用户ID或订单ID的哈希值取模,决定请求落到哪个子库存。

    例如:子SKU编号 = userId % 100

  • 库存分配‌:总库存 = 子SKU1库存 + 子SKU2库存 + ... + 子SKUn库存

2、长期方案 组件分层

3、长期方案 数据库表设计

//主SKU表(记录总库存和子库存分配)  
CREATE TABLE main_sku (  
    sku_id BIGINT PRIMARY KEY,  
    total_stock INT COMMENT '总库存',  
    sub_sku_count INT COMMENT '子SKU数量(分桶数)',  
    version INT COMMENT '版本号(乐观锁)'  
);  

//子SKU表(每个子库存独立记录)  
CREATE TABLE sub_sku (  
    sub_sku_id BIGINT PRIMARY KEY,  
    main_sku_id BIGINT COMMENT '关联主SKU',  
    stock INT COMMENT '当前库存',  
    version INT COMMENT '版本号(乐观锁)'  
);

4、长期方案 核心代码实现(Java + SpringBoot)

路由层逻辑:决定请求落到哪个子SKU

public class RoutingService {  
    // 根据用户ID哈希取模路由  
    public Long getSubSkuId(Long mainSkuId, Long userId, int subSkuCount) {  
        int hash = userId.hashCode() & Integer.MAX_VALUE; // 避免负数  
        int mod = hash % subSkuCount;  
        return mainSkuId * 1000 + mod; // 生成子SKU ID(规则可自定义)  
    }  
}

扣减库存逻辑(带乐观锁)

@Transactional  
public boolean deductStock(Long subSkuId, int deductNum) {  
    // 1. 查询子SKU当前库存和版本号  
    SubSku subSku = subSkuMapper.selectById(subSkuId);  
    if (subSku == null || subSku.getStock() < deductNum) {  
        return false; // 库存不足  
    }  

    // 2. 尝试扣减(带版本号校验)  
    int rows = subSkuMapper.deductStockWithVersion(  
        subSkuId, deductNum, subSku.getVersion()  
    );  

    // 3. 更新成功判定  
    return rows > 0;  
}  

// MyBatis Mapper接口方法  
@Update("UPDATE sub_sku SET stock = stock - #{deductNum}, version = version + 1 " +  
        "WHERE sub_sku_id = #{subSkuId} AND version = #{version}")  
int deductStockWithVersion(  
    @Param("subSkuId") Long subSkuId,  
    @Param("deductNum") int deductNum,  
    @Param("version") int version  
);

对账任务:定期校验子SKU总和

@Scheduled(cron = "0 0/5 * * * ?") // 每5分钟执行一次  
public void reconcileStock() {  
    // 1. 查询所有主SKU  
    List<MainSku> mainSkus = mainSkuMapper.selectAll();  

    for (MainSku mainSku : mainSkus) {  
        // 2. 计算所有子SKU库存总和  
        Integer totalSubStock = subSkuMapper.sumStockByMainSku(mainSku.getSkuId());  
        if (totalSubStock == null) totalSubStock = 0;  

        // 3. 对比主SKU总库存  
        if (!totalSubStock.equals(mainSku.getTotalStock())) {  
            // 触发告警 & 自动修复(例如:调整子SKU库存)  
            alarmService.sendAlert("库存不一致告警: SKU=" + mainSku.getSkuId());  
            adjustSubStocks(mainSku, totalSubStock);  
        }  
    }  
}

长期方案 潜在问题与优化

子SKU分配不均

  • 现象‌:某些子SKU提前卖光,其他子SKU有剩余。
  • 解决‌:动态调整路由策略(例如:根据子SKU剩余库存权重分配请求)。

对账延迟导致超卖

  • 现象‌:对账任务未运行时,可能总库存已超卖。
  • 解决‌:在扣减子SKU前,增加总库存校验(例如:Redis缓存总剩余库存)。

热点子SKU二次竞争

  • 现象‌:某个子SKU仍然成为热点(例如:用户ID尾号集中)。
  • 解决‌:增加分桶数量(如从100调整到1000),或引入二级哈希。

长期方案 效果验证

1‌ 压测对比

  • 单行库存‌:1000并发下,TPS约200,95%响应时间>500ms。
  • 分桶100子SKU‌:1000并发下,TPS提升至1800+,95%响应时间<50ms。

2 监控指标

  • 子SKU锁等待时间(SHOW ENGINE INNODB STATUS)。
  • 对账任务执行成功率与修复次数。

总结‌:
分治方案通过拆分热点行,将并发压力分散到多个子SKU,显著提升系统吞吐量,但需额外处理路由逻辑与数据一致性。
代码实现的关键点在于‌路由算法‌、‌乐观锁扣减‌和‌对账机制‌的设计。

土豪方案:直接上 云

土豪方案:直接上阿里云POLARDB,1小时搞定,但每年多花50万。

费用估算对比分析(POLARDB vs TiDB vs MySQL)

维度 普通MySQL(自建) POLARDB MySQL版‌5 TiDB(分布式架构)
计算节点成本 约800元/月/4核8G 约8,000元/月/8核64G 约12,000元/月/3节点(8核16G/节点)
存储成本 0.3元/GB/月 0.8元/GB/月(SSD) 1.2元/GB/月(分布式存储)
网络成本 0.8元/GB(出流量) 同左 同左 + 跨节点流量费(约0.2元/GB)
运维成本 高(需DBA团队) 低(全托管服务) 中(需分布式架构维护)

120分殿堂答案 (塔尖级):

尼恩提示,讲到 到了这里, 可以得到 120分了。 去哪儿的大厂offer, 这会就到手了。

终于 逆天改命啦。

遇到问题,找老架构师取经

借助此文,尼恩给解密了一个高薪的 秘诀,大家可以 放手一试。保证 屡试不爽,涨薪 100%-200%。

后面,尼恩java面试宝典回录成视频, 给大家打造一套进大厂的塔尖视频。

通过这个问题的深度回答,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。

在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,里边有大量的大厂真题、面试难题、架构难题。

很多小伙伴刷完后, 吊打面试官, 大厂横着走。

在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

另外,如果没有面试机会,可以找尼恩来改简历、做帮扶。

遇到职业难题,找老架构取经, 可以省去太多的折腾,省去太多的弯路。

尼恩指导了大量的小伙伴上岸,前段时间,刚指导一个40岁+被裁小伙伴,拿到了一个年薪100W的offer。

狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由” 。

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
4月前
|
存储 SQL 关系型数据库
MySQL进阶突击系列(03) MySQL架构原理solo九魂17环连问 | 给大厂面试官的一封信
本文介绍了MySQL架构原理、存储引擎和索引的相关知识点,涵盖查询和更新SQL的执行过程、MySQL各组件的作用、存储引擎的类型及特性、索引的建立和使用原则,以及二叉树、平衡二叉树和B树的区别。通过这些内容,帮助读者深入了解MySQL的工作机制,提高数据库管理和优化能力。
|
3月前
|
存储 关系型数据库 MySQL
美团面试:MySQL为什么 不用 Docker部署?
45岁老架构师尼恩在读者交流群中分享了关于“MySQL为什么不推荐使用Docker部署”的深入分析。通过系统化的梳理,尼恩帮助读者理解为何大型MySQL数据库通常不使用Docker部署,主要涉及性能、管理复杂度和稳定性等方面的考量。文章详细解释了有状态容器的特点、Docker的资源隔离问题以及磁盘IO性能损耗,并提供了小型MySQL使用Docker的最佳实践。此外,尼恩还介绍了Share Nothing架构的优势及其应用场景,强调了配置管理和数据持久化的挑战。最后,尼恩建议读者参考《尼恩Java面试宝典PDF》以提升技术能力,更好地应对面试中的难题。
|
5月前
|
SQL 关系型数据库 MySQL
大厂面试官:聊下 MySQL 慢查询优化、索引优化?
MySQL慢查询优化、索引优化,是必知必备,大厂面试高频,本文深入详解,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验分享。
大厂面试官:聊下 MySQL 慢查询优化、索引优化?
|
2月前
|
SQL 关系型数据库 MySQL
京东面试:MySQL MVCC是如何实现的?如何通过MVCC实现读已提交、可重复读隔离级别的?
1.请解释什么是MVCC,它在数据库中的作用是什么? 2.在MySQL中,MVCC是如何实现的?请简述其工作原理。 3.MVCC是如何解决读-写和写-写冲突的? 4.在并发环境中,当多个事务同时读取同一行数据时,MVCC是如何保证每个事务看到的数据版本是一致的? 5.MVCC如何帮助提高数据库的并发性能?
京东面试:MySQL MVCC是如何实现的?如何通过MVCC实现读已提交、可重复读隔离级别的?
|
2月前
|
算法 NoSQL 应用服务中间件
阿里面试:10WQPS高并发,怎么限流?这份答案让我当场拿了offer
在 Nacos 的配置管理界面或通过 Nacos 的 API,创建一个名为(与配置文件中 dataId 一致)的配置项,用于存储 Sentinel 的流量控制规则。上述规则表示对名为的资源进行流量控制,QPS 阈值为 10。resource:要保护的资源名称。limitApp:来源应用,default表示所有应用。grade:限流阈值类型,1 表示 QPS 限流,0 表示线程数限流。count:限流阈值。strategy:流控模式,0 为直接模式,1 为关联模式,2 为链路模式。
阿里面试:10WQPS高并发,怎么限流?这份答案让我当场拿了offer
|
3月前
|
NoSQL 关系型数据库 MySQL
招行面试:高并发写,为什么不推荐关系数据?
资深架构师尼恩针对高并发场景下为何不推荐使用关系数据库进行数据写入进行了深入剖析。文章详细解释了关系数据库(如MySQL)在高并发写入时的性能瓶颈,包括存储机制和事务特性带来的开销,并对比了NoSQL数据库的优势。通过具体案例和理论分析,尼恩为读者提供了系统化的解答,帮助面试者更好地应对类似问题,提升技术实力。此外,尼恩还分享了多个高并发系统的解决方案及优化技巧,助力开发者在面试中脱颖而出。 文章链接:[原文链接](https://mp.weixin.qq.com/s/PKsa-7eZqXDg3tpgJKCAAw) 更多技术资料和面试宝典可关注【技术自由圈】获取。
|
3月前
|
存储 SQL 关系型数据库
MySQL 面试题
MySQL 的一些基础面试题
|
5月前
|
SQL 算法 关系型数据库
面试:什么是死锁,如何避免或解决死锁;MySQL中的死锁现象,MySQL死锁如何解决
面试:什么是死锁,死锁产生的四个必要条件,如何避免或解决死锁;数据库锁,锁分类,控制事务;MySQL中的死锁现象,MySQL死锁如何解决
|
8月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
5月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!

热门文章

最新文章