精通Java事务编程(3)-弱隔离级别之快照隔离和可重复读
ztj100 2025-04-26 22:40 19 浏览 0 评论
表面看,RC已满足事务所需的一切特征:支持中止(原子性),防止读取不完整的事务结果,并防止并发写的混乱。这点很关键!为我们的开发省去一大堆麻烦。
但此隔离级别仍有很多地方可能产生并发错误。如图-6说明RC可能发生的问题。
Alice在银行有1000存款,分为两个账户,每个500。现有一笔转账交易从账户1转移100到账户2。若她在提交转账请求后、银行DB系统执行转账的过程中间,查看两个账户的余额,她可能看到账号2在收到转账前的余额(500),和账户1在完成转账之后的余额(400)。对Alice,貌似她的账户总共只有900,100消失!
这种异常就是不可重复读(nonrepeatable read)或读倾斜(read skew):若Alice在交易结束时再读取账户1的余额,将看到和她之前的查询看到的不同的值(600)。RC下,不可重复读被认为是可接受的:Alice 看到的帐户余额的确都是账户当时的最新值。
术语 倾斜(skew) 这词有些滥用:以前使用它是因为热点的不平衡工作量,而在此意味着异常时序。
Alice案例不是长期持续的问题,几s后当她刷新银行页面,可能就看到一致的帐户余额。但有的场景不能容忍这种暂时的不一致:
- 备份
- 备份需复制整个DB,大型DB可能需数h。备份进程运行时,DB仍会接受写。因此镜像备份里可能包含一些旧版本数据和一些新版本数据。从这样的备份中恢复,最终就会导致永久性的不一致(如那些消失的存款)
- 分析查询和完整性检查
- 有时查询会扫描几乎大半个DB。这类查询在分析中很常见,也可能是定期的数据完整性检查(监视数据损坏情况)。若这些查询在不同时间点观察DB,则可能会返回无意义的结果
【快照隔离】是这类问题最常见解决方案。每个事务都从DB的一致性快照(consistent snapshot)中读取,即事务一开始所看到是最近提交的数据。即使这些数据随后被另一个事务更改,每个事务也只能看到该特定时间点的旧数据。
快照隔离对长时间运行的只读查询(如备份和分析)很有用。若数据在查询执行的同时变化,则很难理解查询结果的物理含义。而若查询的是DB在某特定时间点冻结时的一致性快照,则查询结果含义明确。
快照隔离很流行:PostgreSQL、InnoDB引擎的MySQL、Oracle、SQL Server 等都支持。
实现快照隔离
类似RC,快照隔离的实现通常使用写锁防止脏写,正在进行写入的事务会阻止另一个事务修改同一个对象。但读取则不无需加锁。性能角度,快照隔离的关键点:读不会阻塞写,写不会阻塞读。这允许DB可在正常处理写入的同时,在一致性快照上执行长时间的只读查询,且两者之间没有任何锁竞争。
为实现快照隔离,DB用类似图-4防脏读但却更通用的机制。考虑到多个正在进行的事务可能在不同时间点查看数据库状态,所以DB保留对象的多个不同的提交版本,所以这种技术也称为多版本并发控制(MVCC, multi-version concurrency control)。
若只是为提供RC,而非完整的快照隔离,则只保留对象的两个版本即可:
- 已提交的旧版本
- 尚未提交的新版本
所以,支持快照隔离的存储引擎一般也直接使用MVCC实现RC。典型做法:
- 在RC下,为每个不同的查询单独创建一个快照
- 而快照隔离则是对整个事务使用相同的一个快照。
图-7说明如何在 PostgreSQL 中实现基于 MVCC 的快照隔离(其他实现基本类似)。当事务开始时,首先赋予一个唯一、单调递增 1 的事务ID(txid)。每当事务向DB写入新内容,所写入的数据都会被标记写入者的事务ID。
表中的每行都有个 created_by 字段,其中包含将该行插入到表中的的事务ID。都有个 deleted_by 字段,最初是空的。如某事务删除了一行,那么该行实际上并未从数据库中删除,而是通过将 deleted_by 字段设置为请求删除的事务的 ID 来标记为删除。稍后时间,当确定没有事务可以再访问已删除的数据时,数据库中的gc过程会将所有带有删除标记的行移除,并释放其空间。
这样的一笔UPDATE 操作在内部会被转换为一个 DELETE 和一个 INSERT 。图-7中,事务13从账户2扣100,将余额从 500改为400。account 表会出现两条账户2的记录:
- 余额为500的行被标记为被事务13删除
- 余额为400的行由事务13创建
一致性快照的可见性规则
当事务读DB时,通过事务ID可决定哪些对象可见,哪些不可见。要想对上层应用维护好快照的一致性,需仔细定义可见性规则:
- 每个事务开始时,DB列出当时所有当时还在进行中(即尚未提交或中止)的其它事务,然后忽略这些事务完成的部分写入(尽管之后可能会被提交),即不可见
- 所有中止事务所做的任何修改全部不可见
- 较晚事务ID(即晚于当前事务开始)所做的任何修改不可见,而不管这些事务是否已完成提交
- 此外的所有其他写入都对应用查询可见
以上规则适用于创建、删除操作。图-7中,当事务12从账户2读时,会看到500余额,因为500余额的删除是由事务13完成的(根据规则 3,事务12看不到事务13执行的删除),同理400美元记录的创建也不可见。
即若如下两个条件都成立,则该数据对象对事务可见:
- 读事务开始的时刻,创建该对象的事务已完成提交
- 对象未被标记为删除或即使被标记为删除了,但删除事务在当前读事务开始时还没有完成提交
长时间运行的事务可能会使用快照很长时间,其他事务角度,它可能在持续访问正在被覆盖或删除的内容。由于没有就地更新,而是每次修改总创建一个新版本,因此DB可以以较小运行代价来维护一致性快照。
索引和快照隔离
多版本DB如何支持索引?一种方案是索引直接指向对象所有版本,并且需要索引查询过滤掉对当前事务不可见的对象版本。当后台的GC进程决定删除某个事务不可见的旧对象版本时,相应索引条目也随之删除。
实践中,许多细节决定了多版本并发控制的性能,如:
- 可将同一对象的不同版本放入同一内存页,PostgreSQL如此优化可避免更新索引
- CouchDB、Datomic 和 LMDB使用另一种方案。虽然也使用B树,但采用追加/写时复制(append-only/copy-on-write),当需要更新时,不会修改现有的页,而总是创建一个新的修改副本,拷贝必要的内容,然后让父结点或递归向上直到树root都指向新创建的结点。那些不受更新影响的页面都无需复制,保持不变并被父结点所指向。
这种使用追加的B树,每个写入事务(或一批事务)都会创建一个新的B 树,当创建时,从该特定树根生长的树就是该时刻DB的一致性快照。这时就没必要根据事务ID再去过滤对象,每个写入都会修改现有的B树,因为之后的 询可以直接作用于特定快照B-tree(有利于查询性能)。采用这种方案依然需要后台进程来执行压缩和GC。
可重复读与命名混淆
快照隔离对只读事务特别有效。但DB实现用不同名字来称呼:
- Oracle 中称为可串行化(Serializable)
- PostgreSQL 和 MySQL 中称为可重复读(repeatable read)
命名混淆原因是SQL标准未定义快照隔离,而仍是基于System R 1975年定义的隔离级别,那时还没快照隔离。而定义了 可重复读,表面看起来接近快照隔离。 所以PostgreSQL 和 MySQL 称快照隔离级别为可重复读(repeatable read),这符合标准要求。
但SQL标准对隔离级别的定义存在缺陷的,模糊,不精确,做不到独立于实现。有几个DB实现了可重复读,但它们实际提供的保证差异很大。IBM DB2 使用 “可重复读” 实现可串行化级别的隔离。
所以导致结果,无人真正知道可重复读到底啥意思。
Footnotes
- 事务ID是32位整数,所以大约在40亿次事务后溢出。 PostgreSQL 的 Vacuum 过程会清理老旧的事务 ID,确保事务 ID 溢出(回卷)不会影响到数据。
作者:JavaEdge在掘金
链接:
https://juejin.cn/post/7125794799461859335
- 上一篇:唇唇欲动,一个唇膏控的不完全自白
- 下一篇:14个品牌!64支唇膏使用心得报告
相关推荐
- 30天学会Python编程:16. Python常用标准库使用教程
-
16.1collections模块16.1.1高级数据结构16.1.2示例...
- 强烈推荐!Python 这个宝藏库 re 正则匹配
-
Python的re模块(RegularExpression正则表达式)提供各种正则表达式的匹配操作。...
- Python爬虫中正则表达式的用法,只讲如何应用,不讲原理
-
Python爬虫:正则的用法(非原理)。大家好,这节课给大家讲正则的实际用法,不讲原理,通俗易懂的讲如何用正则抓取内容。·导入re库,这里是需要从html这段字符串中提取出中间的那几个文字。实例一个对...
- Python数据分析实战-正则提取文本的URL网址和邮箱(源码和效果)
-
实现功能:Python数据分析实战-利用正则表达式提取文本中的URL网址和邮箱...
- python爬虫教程之爬取当当网 Top 500 本五星好评书籍
-
我们使用requests和re来写一个爬虫作为一个爱看书的你(说的跟真的似的)怎么能发现好书呢?所以我们爬取当当网的前500本好五星评书籍怎么样?ok接下来就是学习python的正确姿...
- 深入理解re模块:Python中的正则表达式神器解析
-
在Python中,"re"是一个强大的模块,用于处理正则表达式(regularexpressions)。正则表达式是一种强大的文本模式匹配工具,用于在字符串中查找、替换或提取特定模式...
- 如何使用正则表达式和 Python 匹配不以模式开头的字符串
-
需要在Python中使用正则表达式来匹配不以给定模式开头的字符串吗?如果是这样,你可以使用下面的语法来查找所有的字符串,除了那些不以https开始的字符串。r"^(?!https).*&...
- 先Mark后用!8分钟读懂 Python 性能优化
-
从本文总结了Python开发时,遇到的性能优化问题的定位和解决。概述:性能优化的原则——优化需要优化的部分。性能优化的一般步骤:首先,让你的程序跑起来结果一切正常。然后,运行这个结果正常的代码,看看它...
- Python“三步”即可爬取,毋庸置疑
-
声明:本实例仅供学习,切忌遵守robots协议,请不要使用多线程等方式频繁访问网站。#第一步导入模块importreimportrequests#第二步获取你想爬取的网页地址,发送请求,获取网页内...
- 简单学Python——re库(正则表达式)2(split、findall、和sub)
-
1、split():分割字符串,返回列表语法:re.split('分隔符','目标字符串')例如:importrere.split(',','...
- Lavazza拉瓦萨再度牵手上海大师赛
-
阅读此文前,麻烦您点击一下“关注”,方便您进行讨论和分享。Lavazza拉瓦萨再度牵手上海大师赛标题:2024上海大师赛:网球与咖啡的浪漫邂逅在2024年的上海劳力士大师赛上,拉瓦萨咖啡再次成为官...
- ArkUI-X构建Android平台AAR及使用
-
本教程主要讲述如何利用ArkUI-XSDK完成AndroidAAR开发,实现基于ArkTS的声明式开发范式在android平台显示。包括:1.跨平台Library工程开发介绍...
- Deepseek写歌详细教程(怎样用deepseek写歌功能)
-
以下为结合DeepSeek及相关工具实现AI写歌的详细教程,涵盖作词、作曲、演唱全流程:一、核心流程三步法1.AI生成歌词-打开DeepSeek(网页/APP/API),使用结构化提示词生成歌词:...
- “AI说唱解说影视”走红,“零基础入行”靠谱吗?本报记者实测
-
“手里翻找冻鱼,精心的布局;老漠却不言语,脸上带笑意……”《狂飙》剧情被写成歌词,再配上“科目三”背景音乐的演唱,这段1分钟30秒的视频受到了无数网友的点赞。最近一段时间随着AI技术的发展,说唱解说影...
- AI音乐制作神器揭秘!3款工具让你秒变高手
-
在音乐创作的领域里,每个人都有一颗想要成为大师的心。但是面对复杂的乐理知识和繁复的制作过程,许多人的热情被一点点消磨。...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 30天学会Python编程:16. Python常用标准库使用教程
- 强烈推荐!Python 这个宝藏库 re 正则匹配
- Python爬虫中正则表达式的用法,只讲如何应用,不讲原理
- Python数据分析实战-正则提取文本的URL网址和邮箱(源码和效果)
- python爬虫教程之爬取当当网 Top 500 本五星好评书籍
- 深入理解re模块:Python中的正则表达式神器解析
- 如何使用正则表达式和 Python 匹配不以模式开头的字符串
- 先Mark后用!8分钟读懂 Python 性能优化
- Python“三步”即可爬取,毋庸置疑
- 简单学Python——re库(正则表达式)2(split、findall、和sub)
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)