热点行与并发控制

共 18 题
📑 题目列表 18 题
#
★★★

1. MySQL 8.0 SKIP LOCKED 的支持?

MySQL 8.0 是否支持 SKIP LOCKED?它的作用是什么?

  • SKIP LOCKED 语法
  • MySQL 8.0 的支持
  • 用途

MySQL 8.0 支持 SKIP LOCKED 语法,用于 SELECT ... FOR UPDATE/SHARE 等当前读中。当一行被其他事务锁定时,SKIP LOCKED 会直接跳过这些被锁定的行,而不是等待。它常用于任务队列、行锁竞争下的跳过处理,让多个并发消费者各自处理不同的行,避免互相阻塞。配合 NOWAIT 可区分"跳过"与"立即报错"。SKIP LOCKED 从 MySQL 8.0 开始支持,是处理高并发行竞争的重要特性。

SKIP LOCKED 解决"锁等待"问题:与其让所有消费者排队等同一行,不如跳过被锁的行去处理其他行,提升吞吐。它尤其适合任务队列、批处理等场景。

-- 跳过其他事务已锁定的行,取可立即处理的行
SELECT * FROM task_queue
WHERE status = 'pending'
ORDER BY id
LIMIT 10
FOR UPDATE SKIP LOCKED;
#
★★★

2. PostgreSQL SKIP LOCKED 语法(9.5+)的应用,任务队列?

PostgreSQL 的 SKIP LOCKED 语法(9.5+)有哪些应用?尤其是任务队列场景?

  • PG SKIP LOCKED 语法
  • 任务队列
  • 并发消费

PostgreSQL 从 9.5 起支持 FOR UPDATE SKIP LOCKED,同样用于当前读中跳过被锁定的行。任务队列是典型应用:多个 worker 并发执行 SELECT ... WHERE status='pending' ... FOR UPDATE SKIP LOCKED,每个 worker 各取到不同的行,互不阻塞,实现高效的分发与消费。配合行级锁和 SKIP LOCKED,可以避免"所有 worker 抢同一行"的锁竞争,也避免误把已处理的任务重复领取。这是用数据库实现轻量任务队列的常用模式。

PG 的 SKIP LOCKED 是任务队列的经典实现手段:无锁竞争、无重复领取。相比外部消息队列,它能利用数据库事务的原子性,适合中小规模任务处理。

#
★★★

3. 热点行更新的解决,应用层队列、Redis 预扣减、版本号、CAS?

热点行更新的解决方法有哪些?应用层队列、Redis 预扣减、版本号、CAS 分别如何解决?

  • 应用层队列
  • Redis 预扣减
  • 版本号与 CAS

热点行更新(多个并发请求写同一行,如秒杀减库存)的解决思路是"降低对同一行的并发写压力":应用层队列把热点操作串行化,单线程顺序处理,避免数据库行锁竞争;Redis 预扣减用 Redis 的原子操作(如 DECR)先在高性能的缓存层扣减库存,再异步同步到数据库,分担数据库压力;版本号/CAS 通过乐观锁让冲突以"重试或失败"形式暴露,避免无效等待。它们分别从"串行化、缓存分担、乐观冲突"三个角度解决热点行竞争。

热点行问题的本质是"单行写竞争成为瓶颈"。根源是数据库行锁与 undo 的串行化。解决思路是"别让所有人同时写同一行":队列化、Redis 分担、或乐观冲突快速失败。需结合业务对一致性的要求选择。

#
★★★

4. 热点行(Hot Row)更新的竞争,秒杀、库存扣减、计数器?

热点行(Hot Row)更新的竞争是什么?在秒杀、库存扣减、计数器场景中表现如何?

  • 热点行定义
  • 秒杀/库存/计数器场景
  • 竞争瓶颈

热点行指被大量并发请求同时读写的同一行数据。在秒杀、库存扣减、计数器场景中,所有请求都要更新同一行(如同一个商品的库存字段、同一个计数器的值),导致该行成为并发瓶颈:行锁排队、锁等待、redo log 写入、Buffer Pool 页面竞争都集中在这行上,吞吐受限于单行串行处理能力。多个请求同时 UPDATE ... SET stock=stock-1 会因行锁串行化,但即便如此,高峰期仍会形成大量等待和锁竞争,甚至引发死锁。

热点行的竞争本质是"单一数据点被并发写"。瓶颈不止在行锁,还在缓冲池 mutex、页闩锁与 redo 带宽。解决要"分散热点"(多子账户、Redis、队列)而非依赖单行。

#
★★★

5. READ COMMITTED 下 UPDATE 的当前读语义?

READ COMMITTED 下 UPDATE 的当前读语义是什么?

  • RC 下 UPDATE 为当前读
  • 读最新已提交数据
  • 加锁

在 READ COMMITTED 下,UPDATE 是当前读:它读取最新已提交的数据,并对其命中的行加锁(RC 下加记录锁,不加间隙锁)。因此 UPDATE 不会读到未提交数据,且会基于最新已提交值计算。需要注意:RC 下 UPDATE 的加锁是"语句级"的,即每条 UPDATE 语句读取最新已提交数据并加锁,与其他语句(如 SELECT 快照读)的语义不同。此外 RC 下 UPDATE 的 WHERE 条件判断基于当前最新数据,若行被其他事务修改并在等待中,UPDATE 会等待其提交后再基于新值执行。

当前读(UPDATE/DELETE/INSERT/FOR UPDATE)在 RC 下总是读最新已提交数据并加记录锁。理解"UPDATE 是当前读"是分析 RC 并发行为的基础,也是区分其与快照读(SELECT)的关键。

#
★★★

6. READ COMMITTED 下子查询的可重复性?

READ COMMITTED 下子查询的可重复性如何理解?

  • RC 下子查询
  • 语句级快照
  • 可重复性

在 READ COMMITTED 下,每个语句(包括内部子查询)都基于语句开始时新建的 Read View。因此,一条完整语句内部的子查询在同一语句执行期间看到的是同一快照,结果是一致的(语句级可重复)。但跨语句(不同 SELECT)之间,由于每次语句都新建快照,子查询结果可能不同。所以 RC 下子查询的可重复性是"语句级别"的:同一语句内一致,跨语句不一致。这与 RR 的"事务级"一致不同。

关键区分"语句级快照"与"事务级快照"。RC 保证"单条语句范围内读一致",RR 保证"整个事务范围内读一致"。子查询在单条语句内是稳定的。

#
★★★

7. Redis 分布式锁(SETNX)的应用与 Redlock 算法?

Redis 分布式锁(SETNX)有哪些应用?Redlock 算法是什么?

  • SETNX 分布式锁
  • Redlock 算法
  • 分布式锁的可靠性

Redis 分布式锁用 SET key value NX PX timeout 实现,NX 保证键不存在才设置(互斥),PX 设置过期时间(防死锁)。用于在分布式系统中协调多个节点对共享资源的互斥访问。Redlock 算法由 Redis 作者提出,用于在 Redis 集群(多个独立 Redis 实例)上实现更可靠的分布式锁:向 N 个实例依次尝试获取锁,若在多数(N/2+1)实例上成功且总耗时小于锁有效期,则视为获取成功;释放时向所有实例释放。Redlock 降低了单点故障导致锁失效的风险,但也存在争议(如时钟偏移、GC 停顿导致锁过期)。

分布式锁本质是"分布式互斥 + 过期时间防死锁"。SETNX 是基础实现,Redlock 是提升容错的多实例方案。要理解其局限(网络分区、时钟、GC 停顿)以及锁的过期与续约问题。

#
★★★

8. 分布式锁的超时与续约(Lease)?

分布式锁的超时与续约(Lease)机制是什么?为什么需要续约?

  • 锁过期时间
  • 续约(Lease)
  • 防死锁与防锁失效

分布式锁需要设置过期时间(Lease),防止持有者崩溃后锁永远无法释放(死锁)。但过期时间过长,若持有者崩溃又无法及时释放会让其他节点等待过久;过短则持有者业务未完成锁就过期,其他节点可能同时拿到锁破坏互斥。续约机制(Lease Renewal / Watchdog)由持有者在锁即将过期时自动续期,表明"我还活着且在正常工作",保证业务执行期间锁不会过期。若持有者崩溃,停止续约,锁会在过期后自动释放。续约解决了"锁过期与业务执行时间不匹配"的矛盾。

续约是分布式锁可靠性的关键:让"锁的有效期"与"业务执行期"解耦,通过心跳续约表达存活。同时要处理持有者崩溃(停止续约 → 锁过期释放)与网络分区(续约失败 → 锁释放)等边界。

#
★★★

9. 热点行更新除行锁竞争外,还会争抢 Buffer Pool mutex、页闩锁与 redo log 写入带宽,为什么单纯靠索引或隔离级别无法解决?

热点行更新除行锁竞争外,还会争抢 Buffer Pool mutex、页闩锁与 redo log 写入带宽,为什么单纯靠索引或隔离级别无法解决?

  • 热点行的多层面瓶颈
  • Buffer Pool mutex
  • 页闩锁与 redo 带宽

热点行更新的瓶颈不止是行锁竞争,还包括:Buffer Pool 上保护该页内存数据的 mutex(多线程并发访问同一内存页时争抢)、页闩锁(page latch,保护页内页结构更新的锁)、redo log 写入带宽(每个变更都要写 redo,热点行频繁变更使 redo 写入成为瓶颈)。这些瓶颈属于存储引擎内部的内存与日志层面,与索引和隔离级别无关。索引优化(缩小扫描范围)只减少锁范围,隔离级别只影响可见性,都无法减少"同一行被反复修改"带来的内存页竞争与 redo 写入压力。因此要从"分散热点"(多子账户、Redis、队列/串行化)入手。

热点行是"结构性瓶颈":同一内存页被高频并发修改,触发 mutex/闩锁竞争,redo 写入带宽被占满。索引和隔离级别管不了这些内存与日志层面的竞争,只能通过分散热点或串行化热点来解决。

#
★★

10. 不一致分析(Inconsistent Analysis)的定义,聚合查询期间数据变更导致错误的合计?

什么是不一致分析(Inconsistent Analysis)?聚合查询期间数据变更为什么会导致错误的合计?

  • 不一致分析的定义
  • 聚合查询中的并发变更
  • 错误的合计

不一致分析指在聚合查询(如 SUM、AVG、MAX)执行期间,其他事务对相关数据进行了修改和提交,导致聚合结果基于"部分新值 + 部分旧值"计算,得到错误的合计。例如统计某账户余额总和时,中途另一事务把一笔余额从 A 转到 B,若查询还没读完 A 就读了 B,会把 B 的新值算进去而 A 还是旧值,合计错误。其本质是聚合查询没有在"一致快照"下执行,产生了读读不一致。

不一致分析是"读一致性"问题在聚合场景的体现。用快照隔离(RR 事务级快照)或 SERIALIZABLE 可让聚合查询在一致快照下执行,避免中途变更影响结果。原子更新也能从源头减少此类问题。

#
★★

11. 任务队列(Task Queue)模式,消息队列 + 异步消费?

任务队列(Task Queue)模式是什么?消息队列 + 异步消费如何运作?

  • 任务队列模式
  • 消息队列
  • 异步消费

任务队列模式是把需要处理的任务入队,由后台消费者异步处理,从而解耦任务的产生与消费、削峰填谷。消息队列(如 Kafka、RabbitMQ、Redis)作为中间层,生产者把任务写入队列,消费者按需拉取并处理。异步消费让任务处理不阻塞主流程,控制消费速率,并可水平扩展消费者。数据库也可用 SKIP LOCKED 实现任务队列(把任务行"领取"出来处理)。任务队列模式适合秒杀、异步邮件、后台结算等场景。

任务队列的核心是"解耦 + 削峰 + 异步"。消息队列提供可靠投递与缓冲,消费者异步处理。相比数据库行锁直接抢,任务队列把并发压力转移到队列,避免热点行竞争。这是热点控制与异步化的常用架构。

#
★★

12. 应用层幂等性(Idempotency)的实现,幂等键(Idempotency Key)?

应用层幂等性(Idempotency)是如何实现的?幂等键(Idempotency Key)的作用是什么?

  • 幂等性定义
  • 幂等键
  • 防重复处理

幂等性指同一操作重复执行多次与执行一次结果相同。应用层幂等通常通过幂等键实现:每个请求携带一个唯一幂等键(Idempotency Key,如 UUID),服务端在首次处理时把"幂等键 → 处理结果"记录下来(如存入数据库唯一键或 Redis),后续相同的幂等键请求直接返回已记录的结果,而不重复执行业务逻辑。配合数据库唯一约束可保证并发下只处理一次。幂等键用于支付、下单、重试等场景,防止网络重试或重复提交造成重复扣款、重复下单。

幂等键的核心是"用唯一标识去重"。数据库唯一约束是"数据库层面的幂等兜底",Redis 记录是"缓存层幂等加速"。幂等性是重试机制安全性的基础,与死锁重试、分布式锁超时密切相关。

#
★★

13. 不一致分析的解决,快照隔离、SERIALIZABLE、原子更新?

不一致分析可以通过哪些方式解决?快照隔离、SERIALIZABLE、原子更新分别如何起作用?

  • 快照隔离
  • SERIALIZABLE
  • 原子更新

不一致分析可通过:快照隔离(RR 事务级快照)让聚合查询在整个事务内基于一致快照执行,避免中途变更影响结果;SERIALIZABLE 由数据库保证可串行化,聚合查询期间其他事务无法产生可见的并发变更;原子更新(如把扣减写成一条 UPDATE 语句)从源头把"读-改-写"合并为原子操作,减少产生不一致的窗口。三者分别从"读快照、串行化、原子写"解决不一致分析。

不一致分析的根源是"聚合读与并发写交错"。快照隔离保证读一致,SERIALIZABLE 保证整体串行,原子更新减少读-改-写拆分。结合场景选择合适方案。

#
★★

14. SKIP LOCKED 的约束冲突?

SKIP LOCKED 有哪些约束或限制?在使用时需要注意什么?

  • SKIP LOCKED 的限制
  • 适用条件
  • 使用注意

SKIP LOCKED 的使用有约束:它只能用于当前读(SELECT ... FOR UPDATE/SHARE,以及 UPDATE/DELETE 等),不能用于普通快照读 SELECT;它适用于 InnoDB 行锁(MySQL 8.0+)和 PG 9.5+;跳过具体取决于锁状态,若跳过的行较多可能返回较少行甚至空集,需应用层处理(如循环领取);SKIP LOCKEDNOWAIT 语义不同(NOWAIT 遇到锁报错,SKIP LOCKED 跳过)。此外它不能与某些隔离级别/锁配置冲突,使用时需确认存储引擎支持。它并不保证"永远不冲突",只是跳过当前被锁的行。

SKIP LOCKED 的约束主要是"仅当前读、特定版本、可能返回空集"。理解它"跳过而非等待或报错"的语义,以及空集处理,是正确使用的前提。

#
★★

15. 为什么把热点行的并发更新在应用层串行化(单线程队列)有时比依赖数据库行锁吞吐更高?队列化的代价是什么?

为什么把热点行的并发更新在应用层串行化(单线程队列)有时比依赖数据库行锁吞吐更高?队列化的代价是什么?

  • 应用层串行化
  • 行锁瓶颈
  • 队列化的代价

热点行依赖数据库行锁时,瓶颈在数据库侧:行锁竞争、Buffer Pool mutex、页闩锁、redo 写入带宽都被集中争抢,且锁等待、死锁检测带来额外开销。把这些更新在应用层串行化(单线程队列)后,数据库只收到顺序的、无冲突的更新,锁竞争与内部争抢大幅减少,因此吞吐往往更高。队列化的代价是:增加延迟(任务排队)、单点瓶颈(队列本身可能成为瓶颈)、需要保证队列可靠性与幂等(消费失败重试)、以及引入额外组件(队列中间件)的运维复杂度。

应用层串行化把"数据库内部竞争"转化为"应用层顺序控制",绕开数据库锁与内存页竞争。代价是延迟、队列可靠性、幂等。这是"牺牲一点延迟换取吞吐与确定性"的经典取舍。

#
★★

16. 热点行为什么不适合乐观锁?冲突率接近百分之百时重试风暴会放大数据库负载,如何用失败降级与限流保护?

热点行为什么不适合乐观锁?冲突率接近百分之百时重试风暴如何放大数据库负载?如何用失败降级与限流保护?

  • 热点行与乐观锁
  • 重试风暴
  • 失败降级与限流

热点行(如秒杀同一商品库存)的并发写冲突率极高,接近百分之百。乐观锁在这种场景下,几乎所有事务都会因版本冲突而失败并重试,重试又再次争抢同一行,形成"重试风暴",把数据库负载放大数倍,甚至打垮数据库。因此热点行不适合乐观锁,而适合悲观锁串行化或应用层队列。保护手段:失败降级(冲突时直接返回失败或降级提示,而非无限重试)、限流(控制进入热点行的并发请求量,如令牌桶)、设置重试上限与退避,避免重试雪崩。

乐观锁的适用前提是"冲突率低",热点行冲突率接近 100%,重试会放大负载。热点行应"串行化 + 限流 + 降级",而非乐观重试。理解"冲突率决定锁策略"是关键。

#

17. 热点账户余额拆分(多子账户)为什么能降低行锁竞争?拆分与对账如何保证总额正确?

热点账户余额拆分(多子账户)为什么能降低行锁竞争?拆分与对账如何保证总额正确?

  • 多子账户拆分
  • 降低行锁竞争
  • 对账保证总额

热点账户余额拆分是把一个账户的余额分散到多个子账户(如余额池拆成多份),写入时随机或按规则选择其中一个子账户更新,从而把一个热点行分散成多个冷行,多个并发写落到不同子账户,降低行锁竞争。为保证总额正确,需要在逻辑上维护"总余额 = 各子账户余额之和",通过对账(定期汇总各子账户余额与总账比对)和记账(每次操作记录流水)来校验与纠错。拆分引入的问题是:查询总余额需聚合多个子账户,且需保证拆分/合并不丢失;对账用于发现并修正异常。

多子账户是"分散热点"的经典手段,把单行竞争分散到多行。代价是聚合查询与对账复杂度。对账(流水 + 汇总校验)是保证总额正确性的关键保障。

#

18. 分库分表后热点用户仍可能落在单一分片,如何通过二次分片或按用户维度冗余复制解决单分片压力?

分库分表后热点用户仍可能落在单一分片,如何通过二次分片或按用户维度冗余复制解决单分片压力?

  • 分库分表的热点问题
  • 二次分片
  • 按用户维度冗余复制

分库分表按用户 ID 哈希分片后,热点用户的所有数据仍集中在同一分片,该分片成为热点。解决方法:一是二次分片,对热点用户进一步按子维度(如订单号、时间)再分片,把热点用户的数据分散到多个分片;二是按用户维度冗余复制,把热点用户的只读数据复制到多个副本,读写分离,读请求分散到副本,写请求仍需处理。此外还可对热点用户做特殊标记,单独路由到独立集群或缓存。这些手段把"单分片压力"分散到多分片,缓解热点。

分库分表解决的是"整体数据量"问题,但热点用户是"局部访问集中"问题。二次分片和冗余复制分别从"细分数据"和"复制读请求"角度分散压力。理解热点在分片场景下的特殊处理是架构设计要点。