第七章 事务
事务是应用程序将多个读写操作组合成一个逻辑单元的一种方式
从概念上讲,事务中的所有读写操作被视作单个操作来执行:整个事务要么成功(提交(commit))要么失败(中止(abort),回滚(rollback))。如果失败,应用程序可以安全地重试。
并不是所有的应用都需要事务,有时候弱化事务保证、或完全放弃事务也是有好处的(例如,为了获得更高性能或更高可用性)。
事务的棘手概念
ACID含义
原子性
原子是指不能分解成小部分的东西
多线程编程
- 如果一个线程执行一个原子操作,这意味着另一个线程无法看到该操作的一半结果。
- 系统只能看到操作前或者操作后的状态,而不能看到中间状态
ACID 的原子性是 能够在错误时中止事务,丢弃该事务进行的所有写入变更的能力。 而不是关于并发的,隔离性才是关于并发的
一致性
ACID 的一致性是 对数据的一组特定约束必须始终成立。即不变量(invariants),例如在会计系统中,所有账户整体上必须借贷相抵
- 如果一个事务开始于一个满足这些不变量的有效数据库,且在事务处理期间的任何写入操作都保持这种有效性,那么可以确定,不变量总是满足的
- 原子性,隔离性和持久性是数据库的属性,而一致性(在ACID意义上)是应用程序的属性。应用可能依赖数据库的原子性和隔离属性来实现一致性,但这并不仅取决于数据库。因此,字母C不属于ACID
隔离性
ACID的隔离性是 同时执行的事务是相互隔离的,它们不能相互冒犯
持久性
ACID的持久性是 一个承诺,即一旦事务成功完成,即使发生硬件故障或数据库崩溃,写入的任何数据也不会丢失。
如何做到持久性?
- 为了提供持久性保证,数据库必须等到这些写入或复制完成后,才能报告事务成功提交。
- 完美的持久性是不存在的:万一所有硬盘和备份同时被销毁。
在带复制的数据库中
- 持久性可能意味着数据已成功复制到一些节点。
单对象和多对象操作
客户端在同一事务中执行多次写入时,数据库应该做的事:
原子性
不会部分失败--保证all-or-nothing
隔离性
- 同时运行的事务不应该互相干扰:事务如果多次写入,要么事务看到全部写入结果,要么什么都看不到
- 通常需要多对象事务来保持多个数据对象的同步
单对象写入
- 存储引擎的普遍目标:对单个节点上的单个对象(例如键值对)上提供原子性和隔离性
- 实现方法:缘溪行可以通过日志来实现崩溃恢复;可以使用每个对象上的锁来实现隔离(每次只允许一个线程访问对象)
- 更高级的原子操作,但只对单个对象操作有用,不是通常意义上的事务
- 自增操作
- CAS操作
- 事务通常被理解为:将多个对象上的多个操作合并为一个执行单元的机制
多对象事务的必要性
为什么现在分布式数据存储放弃了多对象事务
- 多对象事务很难跨分区实现
- 在需要高可用性或高性能的情况下,可能会碍事
需要协调写入几个不同对象的场景:
- 关系数据模型中,一个表中的行通常具有对另一个表中的行的外键的引用
- 在文档数据模型中,需要一起更新的字段通常在同一个文档中,这被视为单个对象--需要单个文档时不需要多对象事务。但是,当需要更新非规范化的信息时,需要一次更新多个文档
- 在具有二级索引的数据库中(处理纯粹的键值存储以外几乎都有),每次更改值都需要更新索引
没有原子性,错误处理就要复杂的多,缺乏隔离性,就会导致并发问题
处理中止和错误
重试一个中止的事务的问题:
- 如果事务实际成功了,但是在服务器试图向客户端确认提交成功时网络发生故障,那么重试事务导致事务执行了两次
- 如果是因为负载过大导致的事务失败,那重试会将问题变得更糟糕,需要限制重试次数
- 仅在临时性错误(如由于死锁、异常情况、临时性网络中断和故障切换)后才值得重试。发生永久性错误后重试无意义
- 如果事务在数据库之外也有副作用,即使事务被中止,也可能发生这些副作用。比如更新操作后附带发送电子邮件。(如果想让多个不同的系统同时提交或者放弃,需要两阶段提交。)
- 如果客户端在重试过程中也失败了,并且没有其他人负责重试,那么数据就会丢失
弱隔离级别
并发问题发生的条件:
- 如果两个事务不触及相同的数据,那么可以安全的并行执行
- 如果两个事务中一读一写或者同时写,才会出现并发问题
读已提交
没有脏读
- 脏读:一个事务可以看到另一个事务未提交的数据
- 为什么防止脏读:脏读会导致其他事务看到稍后需要回滚的数据
没有脏写
- 脏写:一个事务的写入覆盖了另一个事务未提交的写入
- 为什么防止脏写:如果事务更新多个对象,脏写会导致非预期的错误结果
○ Alice 和 Bob 同事购买一辆车,买车需要两次数据库写入:商品列表更新、开发票。
○ 下图中,Alice 先更新了商品列表,但是被 Bob 覆盖了商品列表;Bob 先更新了开发票,但是被 Alice 覆盖。
实现读已提交
- 如何实现:数据库通过使用行锁来防止脏写/脏读
- 当事务想要修改/读取特定对象(行或文档)时,它必须首先获得该对象的锁。然后必须持有该锁直到事务被提交或中止。
- 一次只有一个事务可持有任何给定对象的锁
- 如果另一个事务要写入同一个对象,则必须等到第一个事务提交或中止后,才能获取该锁并继续。
- 这种锁定是读已提交模式(或更强的隔离级别)的数据库自动完成的
- 读锁的缺点
- 一个长时间运行的写入事务会迫使很多只读事务等到这个慢写入事务完成
- 因为等待锁,应用某个部分的迟缓可能由于连锁效应,导致其他部分出现问题
- 实际方法
- 对于写入的每个对象,数据库都会记住旧的已提交值,和由当前持有写入锁的事务设置的新值。
- 当事务正在进行时,任何其他读取对象的事务都会拿到旧值。
- 只有当新值提交后,事务才会切换到读取新值。
快照隔离和可重复读
- 读已提交会存在不可重复读/读取偏差的异常情况
- Alice 有 1000 美元,分为两个账户
- 先查了账户 1,发现有 500 块
- 一个事务把账户 2 的钱转了 100 到账户 1
- 再查了账户 2,发现只有 400 块
- 导致 Alice 误以为总的只有 900 块
- 不能接受不可重复读的情况:
备份:大型数据库备份会几个小时才能完成,如果备份时数据库仍然接受写入操作,那么备份就可能有一些新的部分和旧的部分,从这样的备份中恢复,那么数据不一致会变成永久的
分析查询和完整性检查:一个分析需要查询数据库的大部分内容,如果不同时间点的查询结果不一样,那就没意义 - 解决方案:快照隔离
- 每个事务都从数据库的一致快照(consistent snapshot) 中读取,即事务可以看到事务开始时在数据库中提交的所有数据
- 优点: 快照隔离对长时间运行的只读查询(如备份和分析)非常有用。
- 实现快照隔离
- 思路
- 与读取提交的隔离类似,快照隔离的实现通常使用写锁来防止脏写
- 这意味着进行写入的事务会阻止另一个事务修改同一个对象
- 但是读取不需要任何锁定,即读不阻塞写,写不阻塞读
-这允许数据库在处理一致性快照上的长时间查询时,可以正常地同时处理写入操作。且两者间没有任何锁定争用
- 实现
- 数据库必须可能保留一个对象的几个不同的提交版本,因为各种正在进行的事务可能需要看到数据库在不同的时间点的状态,即同时维护着单个对象的多个版本,称为多版本并发控制
- 思路
- 保留几个版本的快照
- 如果一个数据库只需要提供读已提交的隔离级别,而不提供快照隔离,那么保留一个对象的两个版本就足够了:提交的版本和被覆盖但尚未提交的版本。
- 读已提交为每个查询使用单独的快照,而快照隔离对整个事务使用相同的快照
观察一致性快照的可见性规则
当一个事务从数据库中读取时,事务ID用于巨顶它可以看见哪些对象,看不见哪些对象
- 在每次事务开始时,数据库列出当时所有其他(尚未提交或中止)的事务清单,即使之后提交了,这些事务的写入也都被忽略
- 被中止事务锁执行的任何写入都将被忽略
- 由具有较晚事务ID(即, 在当前事务开始之后开始的)的事务所做的任何写入都被忽略,而不管这些事务是否已经提交
- 所有其他写入,对应用都是可见的
一个对象可见的两个条件:
- 读事务开始时,创建该对象的事务已经提交
- 对象为被标记为删除,或如果被标记为删除,请求删除的是事务在读事务开始时尚未提交
索引和快照隔离
索引在多版本数据库中:
- 索引指向对象的所有版本,再进行索引查询时,过滤掉当前事务不可见的对象版本
- 垃圾收集机制删除事务不再可见的旧对象版本时,对应的索引条目也能随之删除
- 在PostgreSQL中,如果同一对象的不同版本可放在同一个页面中,其优化措施能避免更新索引
CouchDB、Datomic和LMDB采用的方法
- 使用一种仅追加/写时拷贝的B树变体。在更新时,不会覆盖树的页面,为每个修改页面创建副本,并且从父页面到树根会进行级联更新,使其指向子页面的新版本。不受写入影响的页面无需复制,可保持原样
- 采用这种仅追加的B树方式,每个写入事务(或一批事务)都会生成一棵新的B树,在创建时,从该特定树根生长出来的树就构成了数据库的一个一致性快照。
- 后续写入不能修改现有的B树,只能创建新的树根,所以不需要依据事务ID过滤对象,但需要有负责压缩和垃圾收集的后台进程来配置运作。
可重复读与命名混淆
- PostgreSQL和MySQL称其快照隔离级别为可重复读
- Oracle中称为可串行化
防止丢失更新
什么情况下发生丢失更新
- 从数据库中读取一些值,修改它并写回修改的值(读取-修改-写入序列),可能发生丢失更新的问题
- 两个事务同时执行,其中一个的修改可能会丢失,因为第二个写入的内容并没有包括第一个事务的修改(有时说后面写入clobber(狠揍)了前面的写入
解决方案
原子写
原子写的实现方法
- 游标稳定性(cursor stability):在读取对象时,获取其上的排他锁实现,使得更新完成之前,没有其他事务可以读取它
- 简单地强制所有的原子操作在单一线程上执行
显示锁定
如果数据库不支持内置原子操作,可以是应用程序显式的锁定将要更新的对象
多人棋子游戏
BEGIN TRANSACTION;
SELECT * FROM figures
WHERE name = 'robot' AND game_id = 222
FOR UPDATE;
-- 检查玩家的操作是否有效,然后更新先前SELECT返回棋子的位置。
UPDATE figures SET position = 'c4' WHERE id = 1234;
COMMIT;
- FOR UPDATE 子句告诉数据库应该对该查询返回的所有行加锁
自动检索丢失的更新
- 原子操作和锁是通过强制读取-修改-写入序列按顺序发生,来防止丢失更新的方法
- 可以允许并发执行,如果事务管理器检测到丢失更新,则中止事务并强制它们重试其读取-修改-写入序列
优点
- 数据库可以结合快照隔离高效的执行此检查
- PostgreSQL的可重复读,Oracle的可串行化和SQL Server的快照隔离级别,都会自动检测到丢失更新,并中止该事务
- MySQL、InnoDB的可重复读不会检测丢失更新。一些作者认为,数据库必须能防止丢失更新才能算是提供了快照隔离,所以在这个定义下,MySQL不提供快照隔离
- 数据库自定检查,应用程序不需要任何操作就能使用丢失更新检测
比较并设置CAS
- 只有当前值从上次读取时一直未改变,才允许更新发生。
- 如果当前值与先前读取的值不匹配,则更新不起作用,且必须重试读取-修改-写入序列
- 如果更新失败,需要应用层重试
冲突解决和复制
锁和CAS操作的缺点:
- 锁和CAS操作假定只有一个最新的数据副本
- 多主和无主复制的数据库允许多个写入并发执行,并异步复制到副本上,无法保证只有一个最新数据的副本,因此锁或CAS操作的技术不适用此
复制数据库解决冲突的常用方法:允许并发写入创建多个冲突版本的值(也称为兄弟),并使用应用代码或特殊数据结构在事实发生之后解决和合并这些版本
原子操作的适用条件:可以在复制的上下文中很好地工作,尤其当它们具有可交换性时(即,可以在不同的副本上以不同的顺序应用它们,且仍然可以得到相同的结果)
最后写入胜利(LWW)缺点:容易对是更新,但LWW是许多数据库中的默认方案
写入偏斜与幻读
- 脏写或丢失更新:不同事务并发地尝试写入相同的对象
- 其他并发问题:修改不同的对象
例如:医院通常会同时要求几位医生待命,但底线是至少有一位医生在待命。医生可以放弃他们的班次(例如,如果他们自己生病了),只要至少有一个同事在这一班中继续工作。Alice和Bob是两位值班医生,同时请假:
应用首先检查是否有两个或以上的医生正在值班;
- 如果是的话,它就假定一名医生可以安全地休班。由于数据库使用快照隔离,两次检查都返回 2 ,所以两个事务都进入下一个阶段。
- Alice更新自己的记录休班了,而Bob也做了一样的事情。
- 两个事务都成功提交了,现在没有医生值班了。
- 违反了至少有一名医生在值班的要求
写偏差:如果两个事务读取相同的对象,然后更新其中一些对象(不同的事务可能更新不同的对象),可能会发生写偏差
解决方案较少的原因:
- 由于设计多个对象,单对象的原子操作不起作用
- 在一些快照隔离的实现中,自动检测丢失更新对此没有帮助。目前的可重复读、快照隔离级别中,都不会自动检测写入偏差。自动防止写入偏差需要真正的可串行化隔离
- 某些数据库允许配置约束,然后数据库强制执行(例如,唯一性,外键约束或特定值限制)
解决办法:
-
使用可串行化隔离级别
-
显示锁定事务所依赖的行
-
使用唯一约束
FOR UPDATE告诉数据库锁定返回的所有行以用于更新
BEGIN TRANSACTION; SELECT * FROM doctors WHERE on_call = TRUE AND shift_id = 1234 FOR UPDATE; UPDATE doctors SET on_call = FALSE WHERE name = 'Alice' AND shift_id = 1234; COMMIT;
写入偏差更多例子
会议室预定
防止会议室被同一个会议室多次预定,需要检查时间是否有重叠
BEGIN TRANSACTION;
-- 检查所有现存的与12:00~13:00重叠的预定
SELECT COUNT(*) FROM bookings
WHERE room_id = 123 AND
end_time > '2015-01-01 12:00' AND start_time '2015-01-01 13:00';
-- 如果之前的查询返回0
INSERT INTO bookings(room_id, start_time, end_time, user_id)
VALUES (123, '2015-01-01 12:00', '2015-01-01 13:00', 666);
COMMIT;
多人游戏
● 可以用锁防止丢失更新(也就是确保两个玩家不能同时移动同一个棋子)。
● 但是锁定并不妨碍玩家将两个不同的棋子移动到棋盘上的相同位置,或者采取其他违反游戏规则的行为。
● 按照您正在执行的规则类型,也许可以使用唯一约束(unique constraint),否则您很容易发生写入偏差
抢注用户名
● 在每个用户拥有唯一用户名的网站上,两个用户可能会尝试同时创建具有相同用户名的帐户。
● 快照隔离下这是不安全的。
● 幸运的是,唯一约束是一个简单的解决办法(第二个事务在提交时会因为违反用户名唯一约束而被中止)。
幻读
幻读:一个事务中的写入改变另一个事务的搜索查询的结果。快照隔离避免了只读查询中的幻读,但是会导致写入偏差的问题
物化冲突:将幻读变为数据库中一组具体行上的锁冲突
- 如果幻读的问题是没有对象可以加锁,也许可以人为的在数据库中引入一个锁对象
- 例如,在会议室预定的场景下,可以创建一个关于时间槽和房间的表。要创建预定的事务可以锁定(select for update)表中与所需房间和时间段对应的行。在获得锁定之后,可以检查重叠的预定并像以前一样插入新的预定
- 缺点:弄清楚如何物化冲突比较难,也容易出错,而让并发控制机制泄漏到应用数据模型是很丑陋的做法
可串行化隔离级别更可取
可串行化
真的串行执行
避免并发问题的最简单方法:在单个线程上按栓徐一次只执行一个事务
优点:设计用于单线程执行的系统有时可以比支持并发的系统更好,因为它可以避免锁的协调开销
缺点:其吞吐量仅限于单个CPU核的吞吐量,并且为了充分利用单一线程,需要与传统形式的事务不同的结构
在存储过程中封装事务
- 交互式的事务中,为了提高吞吐量,必须允许数据库并发处理
- 采用单线程串行事务处理的系统不允许交互式的多语句事务,就要求应用程序必须将整个事务代码作为存储过程提交给数据库
存储过程的优缺点
- 存储过程名声不好的原因
- 每个数据库厂商都有自己的存储过程语言,而不是通用语言如java
- 数据库代码管理困难,调试困难,版本控制困难
- 数据库通常比应用服务器对性能敏感的多,或数据库中一个写得不好的存储过程(例如,占用大量内存或CPU时间)会比在应用服务器中相同的代码造成更多的麻烦。
- 克服方法:现代的存储过程实现放弃了PL/SQL,而是使用现有的通用编程语言VoltDB使用Java或Groovy,Datomic使用Java或Clojure,而Redis使用Lua
- 单线程执行事务变得可行
- 存储过程与内存存储,使得在单个线程上执行所有事务变得可行。由于不需要等待I/O,且避免了并发控制机制的开销,他们可以在单个线程上实现相当好的吞吐量
- voltDB还是用存储过程进行复制
分区
- 问题:顺序写导致写入吞吐比较高的应用,单线程事务处理器可能成为一个严重的瓶颈
- 解决方法:分区,每个分区可以拥有自己独立运行的事务处理线程
- 缺点:
- 需要访问多个分区的任何事务,数据库必须在触及的所有分区之间协调事务
- 存储过程需要跨越所有分区锁定执行,以确保整个系统的可串行化
- 跨分区事务比单分区事务慢
- 事务能否分区取决于应用数据的结构:kv存储容易分区,但是多个二级索引的数据不方便分区
真的串行执行事务的条件
- 每个事务必须小而快,只要有一个缓慢的事务,就会拖慢所有事务处理
- 仅限于活跃数据集可以放入内存的情况。很少访问的数据可能会被移动到磁盘,但如果需要在单线程执行的事务中访问,系统会变得非常慢
- 写入吞吐量必须低到能在单个CPU核上处理,否则需要分区,但最好没有跨分区事务
- 跨分区事务也可以支持,但是占比必须小
两阶段锁定(2PL)
实现两阶段锁
- 使用场景:2PL用于MySQL(InnoDB)和SQL Server中的可串行化隔离级别,以及DB2中的可重复读隔离级别
- 实现方式:读与写的阻塞是通过为数据库中每个对象添加锁实现的,锁可以处于共享模式或独占模式
- 锁的使用
- 若事务要读取对象,则须先以共享模式获取锁。允许多个事务同时持有共享锁。但如果另一个事务已经在对象上持有独占锁,则这些事务必须等待
- 若事务要写入一个对象,它必须首先以独占模式获取该锁。不允许其他事务可以同时持有该锁(无论是共享模式还是独占模式)。即,如果对象上存在任何锁,则修改事务必须等待
- 如果事务先读取再写入对象,则它可能会将其共享锁升级为独占锁。升级锁的工作等价于直接获取独占锁
- 事务获取锁之后,必须等待持有锁直到事务结束(提交或中止)。两阶段锁:第一阶段(当事务正在执行时)获取锁,第二阶段(在事务结束时)释放所有的锁
- 可能会发生死锁:数据库会检测事务之间的死锁
- 两阶段锁定的性能差的表现
- 运行2PL的数据库可能具有相当不稳定的延迟,如果在工作负载中存在争用,那么可能高百分位点处的响应会非常慢
- 可能只需要一个缓慢的事务,或者一个访问大量数据并获取许多锁的事务,就能把系统的其他部分拖慢,甚至迫使系统停机
- 两阶段锁定的性能差的原因
- 获取和释放锁的开销,更重要的是并发行的降低
- 如果两个并发事务试图做任何可能导致竞争条件的事情,必须等待另一个完成
谓词锁
- 谓词锁:类似共享/排他锁,但不属于特定的对象,属于所有符合某些搜索条件的对象
- 谓词锁限制访问的方式
- 如果事务A想要读取匹配某些条件的对象,就像这个SELECT查询中那样,它必须获取查询条件上的共享谓词锁(shared-mode predicate lock)。如果另一个事务B持有任何满足这一查询条件对象的排他锁,那么A必须等到B释放它的锁之后才允许进行查询
- 如果事务A想要插入,更新或删除任何对象,则必须首先检查旧值或新值是否与任何现有的谓词锁匹配。如果事务B持有匹配的谓词锁,那么A必须等到B已经提交中止后才能继续
关键思想
- 谓词锁甚至适用于数据库中尚不存在,但将来可能会添加的对象(幻象)。
- 如果两阶段锁定包含谓词锁,则数据库将阻止所有形式的写入偏差和其他竞争条件,因此其隔离实现了可串行化。
索引范围锁/间隙锁
- 问题:谓词锁性能不佳,如果活跃事务持有很多锁,检查匹配的锁会非常耗时。多数运用2PL的数据库实际实现的是索引范围锁(间隙锁),属于谓词锁的简化近似版。
- 实现:让谓词匹配更大集合来简化谓词锁。
- 例如:若数据库基于 room_id 列有索引,查找 123 号房间现有预订时,可将共享锁附加到该索引项上,表示事务对该房间预订进行了搜索;若基于时间索引查找现有预订,就把共享锁附加到对应时间范围的一系列值上,意味着事务标记了该时段用于预订。如此一来,其他事务若要插入、更新或删除同一房间和 / 或重叠时间段预订,更新索引相同部分时会遇到共享锁,需等锁释放。
- 作用:能有效防止幻读和写入偏差
- 如果没有索引,会退化到整个表的共享锁。对性能不利,但是比较安全
可串行化快照隔离(SSL)
悲观与乐观的并发控制
- 两阶段锁是悲观并发控制:如果有事情可能出错(如另一个事务所持有的锁所表示的),最好等到情况安全后再做任何事情
- 串行化快照隔离是乐观并发控制:如果存在潜在的危险,也不阻止事务,而是继续执行事务。当一个事务想要提交时,数据库检查是否有什么不好的事情发生(即隔离是否被违反);如果是的话,事务将被中止,并且必须重试,只有可串行化的事务才被允许提交
适用场景
- 如果存在很多contention(很多事务试图访问相同的对象),则表现不佳,因为会导致很大一部分事务需要中止
- 如果有足够的备用容量,并且事务之间的争用不是很高,乐观的并发控制技术往往比悲观的要好
区别于乐观并发控制技术
- SSL基于快照,即事务中的所有读取都是来自数据库的一致性快照
- 在快照隔离的基础上,ssl通过算法来检测写入之间的串行化冲突,并确定要中止哪些事务
基于过时前提的决策
- 数据库判断结果是否可能已经已经改变
- 检测对旧MVCC对象版本的读取(读之前存在未提交的写入)
- 检测影响先前读取的写入(读之后发生写入)
检测旧MVCC读取
- 快照隔离出现写入偏差的原因:一个事务从MVCC数据库中的一致性快照读时,将忽略取快照时尚未提交的任何其他事务所做的写入
- 如何避免:数据库需要跟踪一个事务由于MVCC可见性规则而忽略另一个事务的写入。当事务想要提交时,数据库检查是否有任何被忽略的写入现在已经被提交,如果是这样,事务必须中止
- 为什么等到提交时才检查和中止:因为另一个事务可能是只读事务,或者本事务可能提交中止或者仍未提交。在提交时再判断,可以避免不必要的事务中止
检测影响之前读取的写入
- SSI 采用和索引范围锁类似的技术,除了SSI锁不会阻塞其他事务
- 数据库可以索引项来记录读取这个数据的事务
- 写入时的通知机制:事务写入数据库时,需在索引里查找曾读取受影响数据的其他事务,这类似获取写锁,但此锁不阻塞事务,只是起到通知作用,告知其他事务其之前读取的数据可能已过时。例如事务 43 和 42 都读取班次 1234 相关数据,事务 43 写操作会通知事务 42 其先前读已过时,反之亦然,最终依据提交顺序及冲突情况决定事务是否要中止(如事务 43 因事务 42 先提交且冲突写入已生效,所以事务 43 需中止)。
可串行化快照隔离的性能
- 与两阶段锁定相比,优势:
- 一个事务不需要阻塞等待另一事务所持有的锁。就像再快照隔离下一样,写不会阻塞读,反之亦然
- 使得查询延迟更可预测,变量更少。特别是,只读查询可以运行在一致性快照上,而不需要任何锁定,这对于读取繁重的工作负载非常有吸引力
与串行执行相比,可串行化快照隔离优点
- 并不局限与单个CPU核的吞吐量
- 即使数据可能跨很多台机器进行分区,事务也可以在保证可串行化隔离等级的同时读写多个分区中的数据
适用场景
- 中止率显著影响ssl的整体表现。例如,长时间读取和写入数据的事务很可能会发生冲突并中止,因此SSI要求同时读写的事务尽量短(只读的长事务可能没问题)
- ssl可能比两阶段锁定或串行执行更能容忍慢事务