Smart Kitchen 智能厨房今天开业了——这是一间数字化的后厨,几位厨师(事务)在同一张操作台上同时备菜。操作台上摆着系统里的一份份“食材档案”(数据),每位厨师的每一步操作都会被实时记录。很快,问题就来了:有人按着别人做到一半的“半成品”菜谱备菜,结果那份菜被推翻重做,白忙一场;有人两次查看同一份食材档案,内容却对不上;还有人清点灶台上的锅,数着数着竟多出一口。这三种混乱,正是数据库并发世界里的三大经典异常:脏读、不可重复读和幻读。为了治理这些混乱,厨房出台了四套“协作守则”——也就是事务隔离级别。这篇文章,就用这间厨房的故事,把三兄弟、四级防护网、以及背后的锁机制和 MVCC 一次讲透,从此面试和开发都不再犯怵。

岗位关联

对 AI 从业者而言,事务隔离级别是数据库并发编程的“安全底线”。无论是训练数据管道、特征存储写入,还是在线推理服务的状态管理,理解脏读、幻读与 MVCC,能让你在设计存储方案时避开数据不一致的深坑。

目录
  1. 可视化演示
  2. 并发异常总览
  3. 隔离级别体系
  4. 脏读详解
  5. 不可重复读
  6. 幻读详解
  7. 锁机制基础
  8. MVCC原理
  9. 隔离级别选择
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

动态演示数据库脏读幻读不可重复读的核心机制,通过入门/进阶/实战/优化四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

数据库脏读幻读不可重复读:脏读问题 脏读问题与读未提交隔离级别 不可重复读与读已提交级别 核心机制可视化 数据库脏读幻读不可重复读整体架构与关键组件

并发异常总览

Smart Kitchen 开业第一天就乱了套。三位厨师——事务A、B、C——在同一张操作台上忙碌,系统里存着每道菜的状态档案。厨师A在腌鱼,腌到一半去接电话;厨师B路过,看着这条“半成品”还挺像样,顺手拿来做配菜;厨师C则在一旁清点锅具。结果A回来觉得盐放多了,把鱼整个扔掉重做——B 手里那盘配菜瞬间成了“无源之水”。类似的混乱接二连三:B第一次看食材档案是草鱼,转头再看变成了鲈鱼;C数锅数到一半,锅里又凭空多了一口。这三种乱象,就是数据库并发世界里的三大经典异常:脏读、不可重复读、幻读。

它们有一个共同的根源:多个事务并发访问同一份数据时,读写顺序产生了交错。 如果没有规则约束,事务之间就会互相看到“不该看”的中间状态,导致基于错误数据的决策。

厨房管理员意识到,必须出台一套“协作守则”,约束厨师们什么时候能看什么、能碰什么。这套守则,就是事务隔离级别。

并发异常∈{脏读, 不可重复读, 幻读}\text{并发异常} \in \{\text{脏读},\ \text{不可重复读},\ \text{幻读}\}
这个公式表示:并发环境下可能出现的异常,一共归为三类——脏读、不可重复读和幻读。
  1. 并发异常多个事务同时操作数据库时产生的一类错误统称。
    并发异常\text{并发异常}
  2. ∈(属于)表示左侧的元素是右侧集合中的一员,即三类问题的总称。
    ∈\in
  3. 三元素集合花括号内列出三类异常的名称,它们是后续所有讨论的主角。
    {脏读, 不可重复读, 幻读}\{\text{脏读},\ \text{不可重复读},\ \text{幻读}\}

隔离级别体系

管理员很快贴出了四套协作守则,供不同场景选用。第一套守则“完全透明”:所有厨师可以看一切半成品,哪怕是别人正在腌的鱼——这叫“读未提交”。第二套守则“只看成品”:半成品一律不许碰,只有做完装盘的菜才能看——这叫“读已提交”。第三套守则“开工拍照”:每位厨师开工时给厨房拍一张全景快照,之后全程只对照照片干活——这叫“可重复读”。第四套守则“交替进场”:同一时间只允许一位厨师在厨房操作,做完下一位才能进——这叫“串行化”。

从第一套到第四套,规则的严格程度逐步升级,能挡住的异常也越来越多。

有了这四套守则,我们再回头细看每一种异常的具体场景、业务危害和避免手段。

RU<RC<RR<SR\text{RU} < \text{RC} < \text{RR} < \text{SR}
四个隔离级别的防护强度从低到高:读未提交(RU)、读已提交(RC)、可重复读(RR)、串行化(SR)。
  1. RU(读未提交)Read Uncommitted,能看到任何未提交的更改,防护最弱。
    RU\text{RU}
  2. RC(读已提交)Read Committed,只能看到已提交的数据,挡住脏读。
    RC\text{RC}
  3. RR(可重复读)Repeatable Read,事务内看到一致的快照,挡住不可重复读。
    RR\text{RR}
  4. SR(串行化)Serializable,事务完全串行执行,三类问题全部挡下。
    SR\text{SR}
  5. 强度序号小于号表示防护能力逐级递增,代价是并发性能逐级下降。
    <<

脏读详解

厨房里最让人窝火的,就是“半成品陷阱”。厨师B想给套餐配一份葱油鱼,他在系统里看到厨师A的食材档案上写着“鱼:已腌制”,于是放心地按这个状态准备配料。可 A 这边接到电话回来,觉得盐放多了,一个回滚把鱼重新洗过、换料重腌。B 辛辛苦苦备好的配料,全废了。

技术世界里,这就是典型的脏读:事务 T2 读到了事务 T1 尚未提交的修改,而 T1 随后回滚,导致 T2 读取的数据变成了“不存在过的脏数据”。

业务危害很直观:如果库存管理系统 A 事务扣减了库存但未提交,B 事务按这个虚假库存对外承诺“有货”,等 A 回滚后,库存其实没变,但订单已经多签了。避免脏读的办法很简单——把隔离级别至少设为读已提交,让事务只能看到已提交的数据。

T1:write(x) (未提交)  ⇒  T2:read(x)=未提交的 x  ⇒  T1:rollbackT_1: \text{write}(x) \ (\text{未提交}) \;\Rightarrow\; T_2: \text{read}(x) = \text{未提交的} \ x \;\Rightarrow\; T_1: \text{rollback}
这个流程说明:T1 写入 x 但没提交,T2 读到了这个未提交的 x,随后 T1 回滚,T2 读到的是脏数据。
  1. T1 写入未提交事务 T1 修改了数据 x,但还没有执行 COMMIT。
    write(x) (未提交)\text{write}(x) \ (\text{未提交})
  2. T2 读取事务 T2 在 T1 未提交时读 x,拿到的是 T1 改过的新值。
    read(x)=未提交的 x\text{read}(x) = \text{未提交的} \ x
  3. T1 回滚T1 最终撤销了自己的修改,x 恢复原值,T2 读到的值成了“空中楼阁”。
    rollback\text{rollback}

不可重复读

第二个场景更隐蔽。厨师B准备做“菜名对照表”,他先查看冰箱第二层的档案:“草鱼”。记录完毕,他去调料架拿了一瓶生抽,回来再看同一份档案——竟然变成了“鲈鱼”。原来厨师C在中间把鱼换了,而且已经提交入库。B 前后两次读的是同一条记录,结果却不同。

这就是不可重复读:同一个事务内,两次读取同一条记录,由于其他事务在中间提交了 UPDATE,两次结果不一致。

业务危害同样惊人:比如在转账场景,T1 先读到账户余额 1000 元用于计算利息,中间 T2 转入 500 元并提交,T1 再结算时余额变成了 1500,导致利息计算错误。避免不可重复读,需要将隔离级别提升到可重复读,或用行锁/共享锁保证读取期间记录不被他人修改。

T1:read(x)=a  ⇒  T2:update(x,b) 已提交  ⇒  T1:read(x)=b≠aT_1: \text{read}(x) = a \;\Rightarrow\; T_2: \text{update}(x, b) \ \text{已提交} \;\Rightarrow\; T_1: \text{read}(x) = b \neq a
这个式子表示:T1 第一次读到 a,T2 把 x 改成了 b 并提交,T1 第二次读到 b,两次对不上。
  1. 第一次读取T1 读到 x 的值为 a,这是它的“第一印象”。
    read(x)=a\text{read}(x) = a
  2. T2 修改并提交T2 把 x 从 a 改为 b,并完成了提交,新值对所有人可见。
    update(x,b)\text{update}(x, b)
  3. 第二次读取T1 再读 x 时得到 b,与第一次的 a 不一致。
    read(x)=b≠a\text{read}(x) = b \neq a

幻读详解

第三个场景,是最容易和不可重复读混淆的“罗生门”。厨师B负责清点灶台上的锅数,第一次数:5 口,他认真地记在台账本上。等他回头看一眼菜谱,再数一遍——6 口!厨师D在这中间新放了一口锅,并且已经记录在案。B 两次查询的是同一个“范围”(灶台所有锅),但行数变了。

这就是幻读:同一个事务内,两次执行相同的范围查询,由于其他事务在中间插入了新行或删除了旧行,结果集的行数不同。幻读和不可重复读的区别在于:不可重复读是针对某一条已存在的记录,幻读是针对整个记录集合的数量。

避免幻读是三个问题里最麻烦的。可重复读的快照读能挡住一部分,但要对“当前读”(UPDATE/DELETE 这类需要看到最新数据的操作)完全防幻读,需要串行化级别,或者 MySQL InnoDB 在可重复读下使用间隙锁(Gap Lock),把“还未出现的空位”也锁起来,让新数据插不进来。

T1:count(S)=n  ⇒  T2:insert into S 已提交  ⇒  T1:count(S)=n+1T_1: \text{count}(S) = n \;\Rightarrow\; T_2: \text{insert into } S \ \text{已提交} \;\Rightarrow\; T_1: \text{count}(S) = n+1
这个式子表示:T1 统计集合 S 有 n 行,T2 插入了新行并提交,T1 再统计变成了 n+1 行。
  1. 第一次统计T1 对集合 S 执行 COUNT 查询,得到 n 行。
    count(S)=n\text{count}(S) = n
  2. T2 插入T2 向 S 中插入一条新记录并提交,集合的物理行数增大了。
    insert into S\text{insert into } S
  3. 第二次统计T1 再次执行相同查询,得到 n+1 行,多出来的行像“幻影”一样出现。
    count(S)=n+1\text{count}(S) = n+1

锁机制基础

厨房为了保证秩序,引入了一套“挂牌制度”。每道菜出锅前可以挂一块牌子:共享锁(S锁) 挂着“试吃”牌——多个厨师可以同时凑过来尝一口,但谁也不能把菜端走;排他锁(X锁) 挂着“制作中”牌——只有持有者能碰这道菜,其他厨师连看都不许看。共享锁之间可以共存(多人试吃不冲突),但共享锁和排他锁、排他锁和排他锁之间水火不容。

锁的粒度也各有讲究:行级锁精细到一盘菜,并发度高;表级锁把整个灶台锁死,简单但性能差;间隙锁则是幻读的克星——它锁住的是“记录之间的空隙”,让新的记录无法插进这个范围。理解了锁,就理解了数据库如何用“排斥”换“一致”。

S∩S=兼容,S∩X=∅,X∩X=∅S \cap S = \text{兼容}, \quad S \cap X = \emptyset, \quad X \cap X = \emptyset
锁兼容性规则:两个共享锁可以同时存在;共享锁与排他锁、排他锁与排他锁互相冲突。
  1. 共享锁共存S 锁与 S 锁可以同时加在同一数据上,就像多位厨师一起试吃。
    S∩S=兼容S \cap S = \text{兼容}
  2. 共享与排他冲突S 锁与 X 锁不能共存,试吃的人在场时,厨师不能独占这道菜。
    S∩X=∅S \cap X = \emptyset
  3. 排他与排他冲突X 锁与 X 锁互斥,两个厨师不能同时“制作中”同一道菜。
    X∩X=∅X \cap X = \emptyset

MVCC原理

锁虽好,但代价太大——读的人多了,写的人就得排队。于是厨房管理员想了个聪明办法:给每道菜装上“时光相机”。系统为每份食材档案维护一串历史版本(版本链),每个版本都用 undo 日志记录着“改之前长什么样”。厨师开工时,系统给他拍一张当时的快照,之后不管别人怎么改这道菜,他看到的始终是开工那一刻的版本——这叫快照读。

但要注意,UPDATE、DELETE、INSERT 这类“当前读”必须看到最新已提交的版本,所以需要加锁。MVCC 的巨大价值在于:普通读操作不加锁、不被写阻塞,在可重复读级别下还能保证一致性快照,这就是为什么现代数据库(MySQL InnoDB、PostgreSQL)都把它作为并发控制的核心底座。

快照读(x)=版本事务开始时刻(x)\text{快照读}(x) = \text{版本}_{\text{事务开始时刻}}(x)
快照读返回的是事务开始那一刻的版本数据,而不是最新已提交的数据。
  1. 快照读事务执行 SELECT 时,不读取最新数据,而是读取快照版本。
    快照读(x)\text{快照读}(x)
  2. 版本下标表示取版本链中“事务开始时刻”对应的那个版本。
    版本事务开始时刻\text{版本}_{\text{事务开始时刻}}
  3. 整体含义读写分离——读走快照,写走当前读,互不阻塞。
    (x)(x)

隔离级别选择

不同餐厅有不同经营策略。MySQL InnoDB 默认使用可重复读(RR),一个原因是 MySQL 主从复制在 RR 下更容易保证复制日志的一致性;而 Oracle 和 PostgreSQL 默认使用读已提交(RC),更追求吞吐量和低延迟。选择隔离级别,本质上是在一致性和并发性能之间做权衡。

实际选型时,大部分互联网业务用 RC + 应用层兜底(乐观锁、悲观锁)就足够:比如秒杀系统在 RC 下用 UPDATE 行锁防超卖,不需要 RR 的重型保障。而银行对账、用户余额清算这类强一致场景,才需要升级到 RR 甚至串行化。守住一致性底线,又不牺牲不必要的性能,才是成熟的架构决策。

默认隔离级别={RRMySQL InnoDBRCOracle / PostgreSQL\text{默认隔离级别} = \begin{cases} \text{RR} & \text{MySQL InnoDB} \\ \text{RC} & \text{Oracle / PostgreSQL} \end{cases}
主流数据库的默认隔离级别不同:MySQL 用可重复读,Oracle 和 PostgreSQL 用读已提交。
  1. 分段函数按数据库类型给出各自的默认级别选择。
    {\begin{cases} \end{cases}
  2. MySQL 选 RR为了配合主从复制与 binlog 的格式要求。
    RRMySQL InnoDB\text{RR} \quad \text{MySQL InnoDB}
  3. Oracle 选 RCRC 下只锁必要的行,吞吐量更高。
    RCOracle / PostgreSQL\text{RC} \quad \text{Oracle / PostgreSQL}

AI 应用场景

电商库存超卖与秒杀系统

阶段:特征存储与在线推理服务的数据一致性保障

解决的问题:库存系统中,多个并发用户在秒杀瞬间同时扣减库存。传统方式若在低隔离级别下操作,会出现脏读——用户看到“有货”并下单,但库存在事务回滚后其实已无货,导致超卖。AI 推荐系统若基于未提交的库存特征做决策,同样会推荐出已无货的商品。

具体案例:某 AI 驱动的电商平台在秒杀场景中用“读已提交 + 行级排他锁”:UPDATE inventory SET stock = stock - 1 WHERE sku_id = ? AND stock > 0,让扣减操作原子化,同时避免脏读带来的虚假库存展示。AI 助手推荐商品时,只读取已提交的库存快照。

金融对账与异常交易检测

阶段:离线批处理与在线特征计算的一致性读取

解决的问题:银行日终对账需要统计一个时间段内的所有流水。若在低隔离级别下运行,其他事务并发插入新流水,会导致对账任务第一次统计和第二次统计的行数不一致——幻读。AI 反欺诈模型读取交易特征时,也可能因为不可重复读而看到前后矛盾的账户余额特征,导致误判。

具体案例:某支付风控系统的 AI 模型在评估一笔交易时,需要在事务内多次读取该账户近 30 天的交易汇总。团队将查询事务设为“可重复读”,确保整个评估过程中读到一致的快照;日终对账报表则使用串行化级别,彻底杜绝幻读导致的总账不平。

高并发特征库的一致性快照

阶段:实时特征平台的数据读取与写入

解决的问题:在线推理服务需要读取多个特征源并组装成一条完整的特征向量。如果不同特征在不同时刻被更新,多次读取可能拼出“半新半旧”的错误向量。用 MVCC 的快照读,可以在服务启动时获取一个一致性快照,保证组装特征向量时所有字段来自同一时间点。

具体案例:某广告 CTR 预估系统,特征平台每天多次更新用户画像特征。推理服务启动一个长事务,用可重复读的快照一次性读取用户全部特征,避免因更新带来的特征错配,让 AI 模型的输入始终保持一致性。

Python 演示

python70 行
"""模拟数据库脏读 / 不可重复读 / 幻读三类并发问题"""  class MiniDB:    """极简内存数据库:一张余额表和一张订单表"""     def __init__(self):        self.balance = 100          # 余额(已提交值)        self.pending = None         # 未提交的新余额        self.pending_owner = None   # 未提交的事务名        self.orders = [1, 2, 3]     # 订单表,用于幻读演示     def set_balance(self, txn, value):        self.pending = value        self.pending_owner = txn     def commit(self):        if self.pending is not None:            self.balance = self.pending        self.pending = None        self.pending_owner = None     def rollback(self):        self.pending = None        self.pending_owner = None     def read_balance(self, use_pending=False):        if use_pending and self.pending is not None:            return self.pending        return self.balance  def demo_dirty_read():    db = MiniDB()    print("=== 场景1:脏读(读未提交)===")    db.set_balance("T1", 500)    print("T1 把余额改成 500(未提交)")    got = db.read_balance(use_pending=True)    print("T2 读到余额 =", got, "<-- 脏读!")    db.rollback()    print("T1 回滚,余额恢复 =", db.balance)  def demo_unrepeatable_read():    db = MiniDB()    print("\n=== 场景2:不可重复读(读已提交)===")    first = db.read_balance()    db.set_balance("T2", 900)    db.commit()    second = db.read_balance()    print("T1 第一次读 =", first)    print("T2 提交修改后,T1 第二次读 =", second)    print("两次结果不一致 -> 不可重复读")  def demo_phantom():    db = MiniDB()    print("\n=== 场景3:幻读(范围行数变化)===")    n1 = len(db.orders)    db.orders.append(4)    n2 = len(db.orders)    print("T1 第一次数订单数 =", n1)    print("T2 插入一条订单后,T1 再次统计 =", n2)    print("行数变化 -> 幻读")  if __name__ == "__main__":    demo_dirty_read()    demo_unrepeatable_read()    demo_phantom()
目的:用 60 行 Python 模拟 MiniDB 的提交/回滚语义,把脏读、不可重复读、幻读三个场景完整复现一遍,帮助读者在“没有数据库”的情况下直观理解并发问题。
  1. self.balance = 100保存已提交的正式值,是事务之间“公认的事实”。
  2. self.pending = None记录未提交的临时值,代表“半成品”状态。
  3. def set_balance(self, txn, value)模拟一个事务修改数据,只写 pending 不写正式值。
  4. db.read_balance(use_pending=True)模拟读未提交级别,直接读半成品。
  5. db.rollback()模拟回滚,把 pending 清空,正式值保持不变。
  6. db.commit()模拟提交,把 pending 正式写入 balance。
  7. db.orders.append(4)模拟其他事务插入新行,造成范围结果集变大。
  8. if __name__ == "__main__":程序入口,依次执行三个演示函数。

常见误区

误区感觉脏读和不可重复读都是“读到不该读的数据”,面试时常常混为一谈,分不清区别。

正解两者有本质区别。脏读是读到了未提交的数据,而这个数据后来被回滚了,等于读到了一个“从不存在的值”;不可重复读是读到了已提交的数据,但同一条记录在两次读取之间被别的 UPDATE 改写了。打个比方:脏读是吃了别人做到一半被倒掉的菜,不可重复读是菜已端上桌却被换成了另一道。

为何常见:因为两者都表现为“两次读取结果不一致”,初学者容易只看到表象,却忽略了“数据是否已提交”这个关键差异。

误区既然串行化能防所有问题,那所有业务都设置串行化不就好了?

正解串行化的代价是事务完全排队执行,并发度急剧下降。对于高吞吐的互联网系统,全部串行化会让接口响应时间成倍增加,甚至引发连接池耗尽。正确的做法是按业务场景分级:核心资金操作才用高隔离级别,普通读写用 RC 配合 MVCC。

为何常见:只看“安全性”不看“性能成本”,忽略了数据库隔离级别本质上是安全与并发之间的权衡。

误区以为 MVCC 的快照读解决了一切并发问题,数据库里就不存在锁了。

正解MVCC 主要优化的是“快照读”——普通 SELECT 不加锁、不被阻塞。但 UPDATE、DELETE、INSERT 这类“当前读”必须读取最新数据,仍然需要加锁。另外 RR 级别下防幻读还要靠间隙锁,写写冲突也要靠排他锁。MVCC 和锁是配合使用的,不是替代关系。

为何常见:把 MVCC 的“读优化”误解为“全场景优化”,忽略了写操作和当前读的数据结构支撑。

误区认为设置了 RR(可重复读)之后,幻读就彻底消失了。

正解MySQL InnoDB 的 RR 级别下,普通快照读确实看不到新插入的行,所以常规 SELECT 不会幻读;但如果事务中执行了 UPDATE/当前读,由于当前读要基于最新数据加锁,其他事务新插入的行仍可能影响范围结果。因此严格来说,RR 并没有在标准意义上彻底杜绝幻读,而是靠间隙锁在多数场景下规避了它。真正彻底防止幻读的是串行化级别。

为何常见:混淆了“快照读体验一致”和“范围查询并发安全”两个概念,把 InnoDB 的工程优化当成 SQL 标准的严格定义。

误区以为数据库性能会随隔离级别提升而变好,毕竟“数据更准了”。

正解隔离级别越高,数据库需要做的约束越多:加锁范围更大、阻塞更频繁、MVCC 版本链更长,整体吞吐量通常越低。RC 与 RR 相比,RC 只对需要的行加锁,范围查询不加间隙锁,并发度更高。提升隔离级别相当于用性能换一致性,绝不等于性能提升。

为何常见:把“数据更准确”与“系统更快”划等号,没有意识到隔离级别的本质是资源约束与权衡。

面试题

Q1. 脏读是指事务读到了什么数据?

单选题 · easy

  • A.另一个事务已经提交的数据
  • B.另一个事务尚未提交的数据← 正确答案
  • C.自己事务里尚未提交的数据
  • D.数据库里已经过期的旧数据

答案:B。脏读的核心是读到了“未提交”的数据,而且这个数据源事务之后可能回滚。A 是正常读取已提交数据;C 是自己修改自己当然能看;D 是旧版本而非“脏”数据。

Q2. 不可重复读与幻读的核心区别是什么?

单选题 · mid

  • A.前者针对单行 UPDATE,后者针对集合 INSERT/DELETE← 正确答案
  • B.前者针对INSERT,后者针对UPDATE
  • C.两者在本质上没有任何区别
  • D.前者发生在读未提交,后者发生在串行化

答案:A。不可重复读是同一条记录被 UPDATE 导致两次读值不同;幻读是范围查询因为 INSERT/DELETE 导致行数变化。B 弄反了两者的操作类型;C 忽略了关键差异;D 的隔离级别归属也不对。

Q3. MySQL InnoDB 引擎的默认隔离级别是?

单选题 · easy

  • A.读未提交(RU)
  • B.读已提交(RC)
  • C.可重复读(RR)← 正确答案
  • D.串行化(SR)

答案:C。MySQL InnoDB 默认使用可重复读(RR),这是为了配合主从复制与 binlog 的一致性需求。Oracle 和 PostgreSQL 才默认使用读已提交。

Q4. 哪一个隔离级别能够完全避免脏读、不可重复读和幻读?

单选题 · easy

  • A.读未提交
  • B.读已提交
  • C.可重复读
  • D.串行化← 正确答案

答案:D。串行化让事务完全排队执行,任何并发异常都不可能发生。可重复读在标准定义下仍可能出现幻读(InnoDB 靠间隙锁规避但不彻底)。

Q5. 读已提交(RC)级别主要能防止哪一类问题?

单选题 · mid

  • A.脏读← 正确答案
  • B.不可重复读
  • C.幻读
  • D.所有并发问题

答案:A。RC 保证只能读到已提交的数据,所以避免了脏读。但它无法保证同一事务内两次读取同一行值不变,也无法阻止范围结果集数量变化,因此不可重复读和幻读仍可能发生。

Q6. 关于 MVCC 快照读的优势,以下说法正确的是?

多选题 · mid

  • A.普通读操作不需要加锁← 正确答案
  • B.读操作不会被写操作阻塞← 正确答案
  • C.可重复读下能读到一致快照← 正确答案
  • D.能够彻底替代所有锁机制

答案:ABC。MVCC 快照读让 SELECT 不加锁且不被写阻塞,还能在 RR 下提供一致的开始快照。但 D 错误:UPDATE/DELETE 等当前读仍需加锁,MVCC 与锁是配合关系。

Q7. 关于幻读,下列说法正确的是?

多选题 · hard

  • A.表现为两次范围查询行数不同← 正确答案
  • B.可能由其他事务插入新行引发← 正确答案
  • C.可能由其他事务更新行值引发
  • D.可重复读下绝对不可能出现

答案:AB。幻读的本质是范围集合的大小变化,由 INSERT/DELETE 引发,所以 A、B 正确。C 是 UPDATE 引发的不可重复读。D 错误:RR 标准下幻读仍可能发生,InnoDB 靠间隙锁规避但不绝对。

Q8. InnoDB 在可重复读级别下使用哪种锁机制来规避幻读?

单选题 · mid

  • A.表级共享锁
  • B.间隙锁与 next-key 锁← 正确答案
  • C.全表扫描加读锁
  • D.不做任何特殊处理

答案:B。InnoDB 通过间隙锁(Gap Lock)锁住索引记录之间的“空位”,再配合 next-key 锁(记录锁+间隙锁),让其他事务无法在范围内插入新记录,从而规避幻读。

Q9. 排他锁(X 锁)具有以下哪个特点?

单选题 · easy

  • A.多个事务可以同时持有
  • B.与共享锁可以共存
  • C.持有期间其他事务不可读写该数据← 正确答案
  • D.只阻塞写入但不阻塞读取

答案:C。排他锁意味着独占:持有者可以读写,其他事务既不能读也不能写。A 是共享锁的特征;B 与兼容性规则矛盾;D 与排他锁的定义不符。

Q10. 在真实业务中选择隔离级别时,应该主要考虑哪些因素?

多选题 · hard

  • A.业务对数据一致性的要求← 正确答案
  • B.系统的并发性能与吞吐量← 正确答案
  • C.开发调试的复杂度← 正确答案
  • D.网络带宽的大小

答案:ABC。选择隔离级别是在一致性、性能、可维护性之间做权衡:资金类业务倾向更高隔离级别,高并发互联网系统倾向 RC+应用层兜底。D 的网络带宽与隔离级别选择没有直接关系。