关于快照隔离和幻影读的一些深入思考

本文通过一个具体的消费场景案例,详细对比了MySQL中Repeatable Read与Serializable两种隔离级别的区别,特别是针对范围读锁的问题进行了深入探讨。

       mysql的隔离级别有四种:read uncommitted,read committed,repeatable read ,serializable

       前面三种的区别 比较容易理解 。关键是第三种个第四种的区别 。

       前段时间微信公众号上看到的一个例子,大致是这么说的:

    • table credit上面记录了每个用户的消费记录,record1(小明,date1,消费5000元),record2(小明,data2,消费2000元)
    • 有一条隐式约束,即小明的消费记录加载一期不能超过小明的信用额度1万元:5000+2000<10000
    • 事务1,小明想消费了2000元,先去select sum(消费) from credit where user=小明;若sum(消费)< 10000,则允许小明此次消费2000元,向credit表中插入一条新的记录,否则交易失败;
    • 事务2跟事务1相同,也是消费2000元;
  • 如果事务1跟事务2完全串行,那么事务2不会交易成功
  • 如果事务1未提交之前,事务2开始,那么
    • 在read repeatable的隔离级别下,事务2也是可以执行的,因为事务2开始时,计算小明消费总额时,读取到的是credit表的快照,此时仍然是可以消费的;
    • 如果级别是serializable的隔离级别,事务1在计算消费总额度时,会针对查询的范围都加上范围读锁,事务2执行的时候,发现事务1已经自己要读取的记录,加了范围锁,那么事务2会阻塞等待事务1提交或回滚。
        对于范围读不加锁导致的不一致性现象,SQL标准中叫幻影读(   phantom read)。mysql的前三种隔离级别,采用的是基于MVCC的快照隔离技术。而对于顺序读,则按照严格的2PC协议实现。对于顺序读,是否就无法采用快照隔离的技术呢。答案是否定的,可以增加一些额外的技巧做workaround。例如读取一个范围数据时,将这些范围数据映射为一个写操作,这个在第二个事务也做同样的范围读时,就就可以检测到写冲突。
        这个问题另外一个规避手段,应用层规避,用一条单独的记录来存储用户的剩余信用额度,每次只读取一条记录做判断。在做数据库设计时,为了性能,采用Repeatable Read的隔离级别(数据库的全局配置参数);为了一致性,尽量规避范围查询后做的写操作,而是把范围查询的聚合运算结果提前存到一个记录中。补充一点, mysql的RR隔离级别的MVCC实现是有些问题的,会导致write skew。简单的例子就是下面的语句update set count = count+1;如果是RR级别,并发高了,会出现lost update。oracle和pg不存在这个问题,pg的mvcc的rr实现,第二个事务提交前会检查写冲突。当然mysql可以通过自己的一个sql关键字规避这个问题,但是对于不熟悉的人,是个很大的坑,笔者就遇到过。
后记,实际上PG的serializable级别可能会存在write skew,PG的serializable实现了一种很特殊的serializable snapshot isolation,也会有些write skew。比如这么做:
  1. R两条记录A.count和B.count,if A.count+ B.count >1 然后W A=A-1。
  2. R两条记录A.count和B.count,if A.count+ B.count >1 然后W B=B-1。
出事条件是A.count=1且B.count=1;如果这两个事物同时执行,PG的serializable会允许两者同时提交,PG的SSI机制会认为事物1修改A和事务2修改B没有冲突。显然这违背了业务的约束,产生了write skew。看来,只有严格的2PL协议,才能保证不会出现write skew和read skew的现象。

参考
https://en.wikipedia.org/wiki/Snapshot_isolation

https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html

http://www.evanjones.ca/db-isolation-semantics.html

补充,一些容易混淆的技术概念:

  • 对于快照隔离,一般认为是一种隔离的级别,笔者更倾向于理解为隔离的技术,在此基础上可以实现不同的隔离级别,例如PG中对于serializable的实现,就是对SI隔离的升级为serializable snapshot isolation;
  • SQL92事务隔离级别的定义是基于悲观锁的思想,例如教科书上的三级封锁协议的定义;商用数据库的实现过程中,多采用基于乐观锁的MVCC技术;商用DBMS事务隔离级别的定义,跟SQL92的标准是有所差别的。
  • 不同的DBMS对于事务隔离级别的定义和实现相差非常大;例如DB2中最高隔离级别repeatable read,相当于mysql的serializable;DB2的cursor stability(锁住select查询游标指向的当前记录)在mysql并没有定义;

https://www.ibm.com/support/knowledgecenter/SSEPGG_9.7.0/com.ibm.db2.luw.admin.perf.doc/doc/c0004121.html

https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html

http://it.dataguru.cn/article-8406-1.html?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

<think>好的,用户想了解快照随机的概念及区别。首先,我需要回忆一下这两个术语的出处。快照在之前的引用里提到过,比如引用[1]中说到MVVC解决的是普通快照)的幻问题。而随机可能HBase或者数据库的访问方式有关,引用[4]提到HBase适合随机实时写,可能涉及到随机的概念。 首先,快照(Snapshot Read)应该是指数据库在某个时间点生成数据快照取这个快照的数据,保证一致性,比如在RR隔离级别下使用MVCC机制。而随机(Random Read)可能是指直接访问存储系统中的任意位置数据,比如通过索引或主键快速定位,不需要顺序扫描。 接下来需要明确两者的定义应用场景。快照通常用于数据库的隔离级别中,确保取的数据版本一致,避免幻等问题。而随机更多是指数据访问方式,比如在HBase中通过RowKey直接获取数据,或者关系型数据库中使用索引查找。 然后要对比它们的区别:数据一致性、底层实现、性能应用场景。快照依赖多版本控制,可能消耗更多存储;随机需要高效的索引或数据结构,比如B+树或LSM树。性能方面,快照可能更快,因为不需要实时锁,而随机的延迟取决于索引效率。 还需要引用提供的资料。引用[1][2]涉及快照事务隔离,引用[3][4]提到HBase的存储结构随机写,可以结合起来说明随机的应用场景。 最后生成相关问题,确保覆盖概念、区别、应用场景技术实现。比如问快照如何实现数据一致性,或者HBase如何优化随机性能。 需要检查是否有遗漏点,比如快照是否还有其他应用,随机在传统数据库NoSQL中的不同表现。确保回答结构清晰,分点明确,符合用户要求的中文回答引用标注。</think>### 快照与随机的概念及区别 #### 1. **快照(Snapshot Read)** - **定义**:快照是基于数据库多版本并发控制(MVCC)机制实现的取方式。它会在事务开始时生成一个数据快照取过程中始终基于该快照版本,避免与其他事务的写入操作冲突,保证一致性[^1]。 - **特点**: - 数据一致性:取的是历史版本数据,不会受后续写入影响。 - 无锁操作:通过版本链直接访问快照,无需加锁。 - 适用场景:适用于需要事务隔离的场景(如`REPEATABLE READ`隔离级别)[^2]。 - **示例**: ```sql SELECT * FROM table; -- 普通查询语句(快照) ``` #### 2. **随机(Random Read)** - **定义**:随机是直接通过索引、主键或唯一标识快速定位并取数据的访问方式,常见于需要低延迟查询的场景(如HBase、Redis等)。 - **特点**: - 直接定位:通过索引或RowKey快速找到数据存储位置。 - 实时性:取最新写入的数据(除非显式指定版本)。 - 适用场景:适用于高频点查、实时分析等[^4]。 - **示例**: ```java Get get = new Get(Bytes.toBytes("rowkey1")); // HBase通过RowKey随机取 ``` #### 3. **核心区别** | **维度** | **快照** | **随机** | |----------------|-------------------------------|-------------------------------| | **数据一致性** | 基于历史快照,一致性视图 | 取最新数据,实时性优先 | | **底层实现** | 依赖MVCC版本链(可能占用存储) | 依赖索引或RowKey定位(如B+树、LSM树) | | **性能** | 无锁,但需维护多版本 | 延迟低,但索引维护可能影响写入 | | **典型应用** | 事务隔离(如MySQL RR级别) | 实时查询(如HBase点查) | #### 4. **技术实现对比** - **快照**: 通过MVCC记录数据版本链,事务取时选择可见的版本。例如InnoDB通过`ReadView`判断哪些版本对当前事务可见[^1]。 - **随机**: 在HBase中,通过RowKey直接定位RegionStoreFile,结合MemStoreBlockCache加速取[^3][^4]。 --- ### 相关问题 1. **快照如何解决幻问题?** (提示:结合MVCCNext-Key Locks机制[^1]) 2. **HBase如何优化随机性能?** (提示:BlockCache、布隆过滤器、RowKey设计[^4]) 3. **哪些场景适合混合使用快照随机?** (提示:OLTP系统中的历史数据查询与实时交易结合) 4. **MVCC对数据库存储有何影响?** (提示:版本链存储开销与清理机制[^2])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值