数据系统与分布式(三) 分布式数据系统(事务,事务隔离级别)
数据 和 分布式
数据系统基础
第一章. 可靠 可拓展 可维护的应用系统
-
可靠性
- 出现意外情况, 硬软件故障,人为失误, 系统应该正常运转, 虽然性能降低, 但是功能正确
-
可拓展性
-
随着系统规模的增长 , 系统应该合理的匹配增长
-
比如Twitter的例子P19
- 描述性能我们关心中位数, 百分位数
- 比如P50代表至少一半用户查询等待时间是在这个时间之内的
- 同样的还有99.99%这种
- 实际上为了提高性能, 我们常常在垂直拓展和水平拓展之间做取舍
-
-
可维护性
- 许多时间的推移, 新加入的运维人员应该比较容易适应
- 可运维性: 运维更轻松
- 简单性: 简化系统复杂度
- 可演化性: 易于改变
-
常见术语
- 现在的应用很多都有数据系统的很多模块
- 数据库: 用来存储数据
- 高速缓存: 缓存那些复杂或操作代价昂贵的 结果 加快下一次访问
- 索引: 按关键字来检索数据 并支持各种过滤
- 流式处理: 持续发送数据到另外一个进程 , 采用异步方式
- 批处理: 定期处理大量的累计数据
第二章 数据模型 和 查询语言
-
计算机常常的分层结构, 在数据系统中也不例外. 在某个具体的层次上 , 我们关注的问题是 如何用下一层来表示当前层
- 用数据结构对现实建模 -> 用json或者xml或者关系数据库的表进行存储-> 用内存, 磁盘或者网络的字节格式来表示 ->硬件用电流, 磁场来表示数据
-
关系模型和文档模型
- 关系模型最出名的就是SQL
- 子主题 2
第三章 数据存储 和 检索
-
从基本的层面: DB = 插入时存储数据, 查询时返回数据
-
哈希索引
-
B树索引
-
事务分析和处理
-
事务不一定有ACID属性
-
OLTP
- 在线事务处理
- OnLine Transition Process
- 使用索引的某些键查找少量积累, 根据用户的输入 插入或更新记录 并且常常是交互式的
- 比如博客的评论 ,游戏的动作
- 基本访问模式和处理业务交易类似
-
OLAP
- 在线分析处理
- OnLine Analytic Process
- 分析查询需要扫描大量的记录 ,并汇总一个信息
- 比如 一个月店铺总收入 促销期间比平时多麦了多少香蕉
-
数据规模 OLTP GB~TB OLAP TB到PB
-
命名 常常OLAP单独在数据仓库上 因为不愿意让业务分析人员在OLTP上执行代价很高的操作 数据仓库有OLTP的只读副本
-
采用数据仓库的目的是可以针对不同的分析访问模式进行优化
-
-
子主题 5
- 子主题 1
第四章 数据编码和 演化
-
在内存里 ,数据保存在对象, 结构体, 数组, 哈希表 ,树. 把数据写入文件或者通过网络发送时, 由于指针对其他进程没有意义,所以字节序列和内存的大不一样
-
Json
-
XML
-
二进制
-
Thrift
-
Facebook开发的 07 - 08开源
-
有两种编码方式
-
Binary Protocol
- 对一个3字段的59byte
-
CompactProtocol
- 只要34字节
-
-
-
-
Protocol Buffers
-
Google开发的 07 - 08开源
-
类似CompactProtocol
- 只要33Byte
-
-
-
特征是 编码时不存储具体的字段名, 比如name, 而是用一个Tag来标识字段, 带来了更好的模式演化, 为以后拓展字段等
-
旧代码读新代码写入的数据
- 可以通过简单的忽略解决
- 实现了向前兼容性
-
新代码读入旧代码的数据
- 字段必须为optional或者具有默认值
- 实现了向后兼容性
-
-
而且required 和 optional是在运行时检查, 在编码时不编码进去
-
Avro
- 与前两个比, 优点在于没有标签号
-
-
数据流模式
-
基于数据库的流模式
-
基于服务的数据流
-
REST
-
RPC 远程过程调用 Remote Procedure Call
-
RPC存在一些问题
- 本地函数可预测(只和参数有关), 网络请求是不可预测的, 网络问题很常见 ,请求或者响应可能因为网络丢失, 那是不是要有重试机制? 幂等性怎么解决
- 本地要么return一个结果 要么抛出异常 要么死循环. 远程可能因为超时没有结果. 根本不知道发生了什么
- 如果重试失败的请求, 可能请求已经完成, 只是响应丢失, 可能会导致执行多次, 除非建立( 重复数据)消除(幂等性)机制
- 由于网络延迟, 函数执行时间很难说
- 本地可以把引用或者指针传给本地内存的对象 RPC一定要编码成网络字节序列
- 子主题 6
-
子主题 2
-
-
面向微服务的一个关键的设计目标是, 通过使服务可独立部署和演化, 让应用程序易于更改和维护
-
-
基于消息传递的数据流
-
也就是消息队列
- Rabbit MQ,Active MQ, Apache Kafka
-
优点:
- 1.如果接受方过载, 或者不可用 .它可以作为缓冲区, 提高系统可靠性
- 2.自动把消息重新发到崩溃的进程 , 防止消息丢失
-
- 解耦
- 4.避免了发送方需要知道接收方的IP地址, 端口号( 在频繁变更的虚拟机中)很有用
- 5.支持一条消息多个接收方
- 6.逻辑上的分离, 发送方只管发送消息,不关心谁使用它们
- 7.异步的
-
-
分布式Actor框架
-
分布式数据系统
基础知识
-
为什么要引入分布式
-
拓展性
- 负载过大,超出了单台机器的处理上限
-
容错与高可用性
- 单台机器出现故障, 其他机器可以正常工作
- 组件失效, 冗余组件可以继续接管
-
延迟考虑
- 服务遍布全球各地, 希望就近服务
-
-
系统拓展
-
每个机器称为结点
-
垂直拓展
- 加强单台机器
-
水平拓展
- 加机器数量, 加小机器
-
-
复制与分区
-
当把数据分布在多节点的时候有两种常见的方式
-
复制
- 在多个节点上保存冗余的副本
-
分区
- 将一个大的数据库拆分成多个较小的子集
-
数据复制
-
目的
- 1.数据在地理位置上更接近用户,降低延迟
- 2.高可用, 系统出现故障, 仍然可以继续工作
- 3.拓展多台机器, 提高读吞吐量
-
主节点与从节点
-
基本原理
- 1.选一个作为主节点, client的所有写请求都发给主节点
- 2.其他节点都是从节点, 主节点把更改写入本地后, 把更改通过复制的日志或者更改流发送给所有从副本, 从副本应用这些更改
- 3.client可以向主节点发read or write, 对从节点只能read
-
同步复制和异步复制
-
同步复制的优点
- 一旦向用户确认, 从节点可以保证数据的最新版本
- 主节点发生故障时, 从节点有最新的备份, 可以迅速恢复
-
同步复制的缺点
- 一旦有某个响应特别慢, 就要一直阻塞到同步副本确认完成
-
异步复制的优点
- 性能特别好, 在处理写请求时, 不管从节点数据多么滞后, 主节点总是可以继续响应写请求, 系统的吞吐性能更好
-
异步复制的缺点
- 主节点发生失败,还未同步的写请求会丢失
- 无法保证数据的持久化
-
trade-off也就是半同步
- 一个节点是同步模式, 其他节点是异步模式.
- 万一同步的从节点性能下降或者不可用了, 把另外一个节点提升成同步模式
- 这样做的好处是保证至少有两个节点拥有最新的数据副本
-
-
配置新的从节点
-
如何在不停机 , 数据服务不中断的前提下完成从节点的设置
- 1.在某个时间点对主节点的数据副本产生一个一致性快照
- 2.将此快照拷贝到新的从节点
- 3.从节点连接到主节点, 并请求 快照之后的数据更改日志
- 4.获得日志后, 应用这些变更. 也就是 "追赶"
-
-
处理节点失效
-
从节点失效
- 追赶式恢复
-
主节点失效
-
切换的具体表现
- 选择某个从节点提升为主节点, 客户端也要把write发送给新的主节点, 从节点也要接受来自新的主节点的变更数据
-
手动切换
- 管理员手动切换
-
自动切换
-
1.确认主节点失效
- 基于超时的机制, 如果30s都没有回复, 则认为失效
-
2.选举新的主节点
- 选举的方式, 最好数据差异最小
-
3.重新配置系统让 新主节点生效
-
很多小问题
- 脑裂
- 原主节点仍然认为自己是主节点
-
-
-
-
-
复制日志的实现
-
基于语句的复制
-
把诸如insert和update这类语句发送给从节点
-
问题就是很多非确定性的语句
-
比如now()获取时间
-
多个并发执行的事务
- 等等
-
MySQL会自动切换到基于行d
-
-
-
基于预写日志(WAL)传输
- PostgreSQL和Oracle支持
- 缺点是此方案过于底层, 与存储引擎紧密耦合, 和软件版本有关
-
基于行的逻辑日志复制
- 复制和存储引擎采用不一样的日志格式, 解耦合
- MYSQL的binlog
-
基于触发器的复制
- 高度可定制, 复杂度高
-
-
-
复制滞后问题
-
主从复制所有写都要经过主节点, 可以通过水平拓展来增强 系统的读能力
-
"最终一致性"问题
- 一个应用从一个异步的从节点读取数据, 该副本落后于主节点
- 如果同时对主节点和从节点发送相同的查询, 可能会得到不一样的效果
- 但是这种不一致只是暂时的, 一段时间后, 从节点会赶上主节点
- 这种效应称为最终一致性
-
"写后读一致性"问题
-
读自己的写
-
用户在写入不久后查看数据
- 但是还没同步,看起来就像数据丢失了一样
- 用户会不高兴不高兴
-
我们需要"写后读一致性"
-
方案一
- 如果用户读可能被修改的内容, 从主节点读
- 否则 ,在从节点读
- 比如社交网站, 读自己的朋友圈, 从主节点读, 读别人的朋友圈, 在从节点读
-
方案二
-
跟踪最近更新的时间,
- 如果在1min之内, 从master读
-
同时监控从节点的复制滞后程度
- 避免从滞后超过1min的节点读取
-
-
方案三
- 通过client端发送读请求时的时间戳, 确保提供读服务时 时间上的合理
-
Tips
- 如果副本分布在多数据中心, 必须先把请求路由到主节点在的数据中心
-
-
-
-
"单调读"
-
由于两次路由的从节点不同的原因, 用户第一次看到了数据, 第二次却看不到了
-
我们需要"单调读"一致性
-
方案一
- 根据用户ID哈希到固定的一台机器
-
-
是比"强一致性"弱, , 比"最终一致性"强的
-
保证绝对不会看到回滚
-
-
"前缀一致读"
-
对于一系列按照某个顺序发生的写请求 ,读这些内容时也会按照当时写入的顺序
-
方案
- Happened-before
-
-
复制滞后的解决方案
-
如果采用最终一致性系统, 用户的体验能接受的话,
- 那就采用"最终一致性"吧
-
如果用户体验不好
-
提供更强的一致性
- 比如写后读
-
-
-
-
多主节点复制
-
单主节点的明显缺点
- 如果主节点网络中断了,就会影响所有的 写入操作
-
适用场景
-
在单数据中心引入 多节点 只是徒增复杂度
-
适用于多数据中心, 离线客户端, 协作编辑 这三个场景
-
多数据中心场景
-
在每个数据中心内, 采用常规的主从复制方案
-
在数据中心之间, 有每个的主节点来负责 数据交换和更新
-
与单主节点的对比
-
性能方面
- 肯定是多主节点性能更好,
- 因为就近访问
-
容忍数据中心失效
- 也是多主节点更好
- 每个数据中心可以独立于其他数据中心继续运行
-
容忍网络问题
- 也是多主更好
-
-
缺点
- 冲突问题
-
-
协作编辑
- 石墨文档, 腾讯文档
- 如果解决冲突
-
离线客户端操作
- 比如手机上的日历工作, 每部手机都是一个主节点
- 这种时候采用 多主节点场景比较合适
-
-
处理写冲突
-
场景
- A修改标题为20201005是美好的一天, B修改标题为 20201006是美好的一天
-
同步与异步冲突检测
-
避免冲突
- 最理想的策略就是避免冲突
- 不同用户对应到不同的主数据中心
- 但是还是有可能冲突,
-
收敛于一致性状态
-
通过分配唯一的ID
- 比如基于时间戳
- 最后写入者获胜
-
拼接这些值
- 20201005是美好的一天20201006是美好的一天
-
靠应用层的逻辑, 事后解决, 比如告诉用户
-
-
自定义冲突解决逻辑
- 最合适的方式还是得应用层来
- 在写入时执行
- 在读取时执行
-
什么是冲突
-
刚才的那种对同一个字段写
-
订票时把唯一的票给了两个人
-
自动冲突解决的可能方案
-
1.无冲突的数据类型(CRDT)
- conflict-free Replicated DataType
- 可以由多个用户编辑的数据结构
-
2.可合并的持久数据结构
- 类似git
- 二向合并 三向合并
-
3.操作转换
-
-
没现成的答案
-
-
-
拓扑结构
-
指的是多个主节点之间传播路径
-
环形拓扑
-
星形拓扑
- 拓展到树形结构
-
all to all拓扑
- 容错性更好
- 问题: 可能由于网络 原因, 出现类似"前缀一致读"的问题
- 使用版本向量的技术来
-
-
-
无主节点复制
-
思路清奇, 放弃主节点
- 亚马孙Dynamo系统 不开放, DynamoDB不是Dynamo
-
节点失效时写入数据库
-
读修复与反熵
-
读修复
- 并行读多个副本时, 可以检测到过期的返回值
-
反熵
- 有后台进程不断查找副本之间的数据差异, 有明显的同步滞后
-
-
读写quorum
-
quorum是法定人数的意思
-
3个副本的例子里, 成功的写操作至少2个, 那么失败的至多一个, 我们读的时候至少读两个, 就可以保证至少有一个是最新的副本
-
W+R>n
-
常见的设计
- n为奇数
- w = r = (n+1)/2
-
问题
- 即使满足W+R>n , 我们还是不能保证读取最新值
-
所以我们最好把w和r看成灵活可调的读取新值的概率
-
-
-
Quorum一致性的局限性
-
可以把w和r<=n
- 这样虽然可能读的是旧值, 但是提高了性能
-
监控旧值
-
我们监控复制的运行状态
-
如果差距过大, 可能就是网络问题或者节点超负荷
-
原理大概是
- 由于主节点和从节点上写入遵循相同的顺序, 每个节点维护了复制日志执行的当前偏移量. 通过比较偏移量的差值, 来衡量从节点落后于主节点的程度
-
-
宽松的quorum与回传
-
配置适当的quorum是有好处的
- 1.可以容忍一些节点慢, 因为请求不用等所有节点的响应, 只需要w或者r个
-
sloppy quorum
-
写入和读取仍然需要w和r个成功的响应, 但包含了不在先前指定的n个节点
-
原先指定的n个节点暂时不可用
-
把写请求写入了一些暂时可访问的节点中, 一旦网络问题解决, 这些临时节点会把这些写入全部发送到原始主节点上,
- 这就是数据回传,(或者说暗示移交)
-
-
-
多数据中心操作
- 无主节点复制 aim at 更好容忍并发写入冲突, 网络中断 和 延迟尖峰
-
-
检测并发写
-
Dynamo风格的数据库在并发写入时缺乏顺序保证
-
最后写入者获胜( 丢弃并发写入)
-
每个写请求附加一个时间戳
-
Last Write Wins(LWW)
-
可以实现最终收敛的目标, 但是牺牲了数据的持久性
-
如果覆盖, 丢失数据可以接受,就用
-
确保LWW安全无副作用的唯一办法
- 只写入一次然后写入值视为不可变
- 比如UUID作为主键
-
-
Happens-before关系和并发
- 并发性, 时间, 相对性
- 确定前后关系
- 合并同时写入的值
- 版本矢量
-
-
数据分区
-
面对海量数据集或者非常高的查询压力, 复制技术还不够, 需要引入分区
-
分区的定义
- 每条数据, 或者说每条记录,只属于某个特定分区
- 每个分区可以看成一个完整的小型数据库
-
-
数据分区与数据复制
-
分区通常和复制结合使用, 每个分区在多个节点上保存有副本.
- 意思是某条记录属于特定的分区, 而这个分区里有许多节点保存着这记录的拷贝
- 每个分区有自己的主副本
-
-
键-值数据的分区
-
基于关键字区间分区
- 分区边界要适配数据本身的分布特征
-
基于关键字哈希值分区
- MD5, Fowler-Noll-Vo
- 缺点, 丧失了区间查询特性
-
组合索引
-
user_id, update_timestamp
- 可以有效检索某用户在一段时间内的所有更新
-
-
负载倾斜与热点
-
所有的读/写都是针对同一个关键字
- 比如明星的微博
- 方法,如果某key是热点, 在这个key的前或后加一个随机数,比如两位的十进制,, 这样可以负载到100台机上
-
-
-
分区与二级索引
-
二级索引带来的主要挑战是它们不能规整的映射到分区中
-
基于文档分区的二级索引
- 每个分区完全独立, 如果我们想查询"红色的汽车"
- 需要把查询发送到所有的分区, 然后合并所有返回的结果
- 别名:"分散/聚集"
-
基于词条的二级索引分区
-
我们要对所有的数据构建全局索引, 但是不能把所有的全局索引存在一个节点上, 所以全局索引页必须分区
-
全局索引的缺点:
- 写入速度较慢而且比较复杂
- 所以往往是异步的 刚写入之后读可能 不太行
-
优点:
- 读取更高效
-
-
-
分区再平衡
-
随着时间的变化, 数据库可能会变化, 我们需要再平衡
-
再平衡需要满足
- 1.平衡之后, 负载, 数据存储, 读写请求等应该均匀分布
- 2.再平衡过程中, 数据库应该可以正常提供读写服务
- 3.避免不必要的负载迁移
-
动态再平衡的策略
-
为什么不用取模
-
hash (A) = 123456
-
10台机器时
- 123456%10 = 6
-
11台机器时
- 123456%10 = 3
-
12台机器时
- 123456%10 = 0
-
-
因为如果节点数N发送了变化, 会导致许多关键字需要迁移
-
-
固定数量的分区
-
比较好
- 创建远超实际节点数的分区数, 然后为每个节点分配多个分区
-
比如10个机器, 一开始就创建了1000个分区
- 每个机子负责100个分区
-
只需要做好映射关系
-
-
动态分区
- 分区数量大到一定程度, 比如10GB, 就分裂
- 分区小到一定程度, 就合并
- 类似B树
- 一个节点上可能有多个分区
- 适用于关键字区间分区, 也适用于基于哈希的分区
-
按节点比例分区
-
-
-
请求路由
-
我们已经把数据集分布到多个节点上, 但是客户端有一个 悬而未决的问题 应该连接到哪个IP地址 和哪个端口号
- 也就是服务发现的问题
-
主要有三种策略
- 1.客户端随机选, 然后节点看看是不是自己, 不是就转发
-
- 引入一个路由层
- 3.客户端就知道
-
ZooKeeper是一个独立的协调服务, 它跟踪集群范围内的元数据
- 每个节点向ZooKeeper注册自己, 路由层或者客户端订阅ZooKeeper
-
当然还可以通过gossip协议来同步集群状态变化, 虽然增加了节点的复杂性, 但是避免了对ZooKeeper的依赖
-
事务
-
ACID的含义
-
隔离性各不相同
-
BASE
- Basically Available
- Soft state
- Eventual consistency
-
ACID原子性
- 在出错时中止事务, 并将部分完成的写入全部丢弃
- 或者说, 可终止性
-
ACID一致性
-
不同场景含义不同
- 在副本一致性和异步复制模型时, 这里指的是最终一致性问题
- 一致性哈希指的是系统动态分区再平衡的办法
-
ACID一致性指的是 任何数据更改必须满足这些状态约束
-
状态机 从一致状态 经过一些操作 跳转到另外一个状态
-
-
ACID隔离性
-
并发执行的多个事务 相互隔离, 不能互相交叉
-
经典定义是 可串行化
- means 可以假装它是 数据库运行的唯一 事务
-
-
ACID持久性
-
对于单点数据库
- 持久性意味着 数据已经被写入 非易失性 存储设备
-
对于支持远程复制的数据库,
- 持久性意味着 数据已经成功复制到多个节点
-
-
-
单对象与多对象事务操作
-
单对象写入
-
compare-and-set
-
单对象操作
- 称为轻量级事务
-
-
多对象事务的必要性
- = =
-
处理错误与中止
-
-
弱隔离级别
-
从理论上讲 ,隔离是假装没有并发
-
可串行化的隔离会严重影响性能
- 所有常常用弱级别的隔离
-
-
读-提交
-
提供两个保证
-
1.读数据, 只能看见已经成功提交的数据(防止"脏读")
-
如果事务A已经完成部分数据写入, 但是事务没有提交(或者中止了), 那事务B能不能看到 未提交的数据呢?
-
如果能, 就是脏读
-
适用场景
- 1.如果事务更新多个对象, 比如更新电子邮件 和 更新 未读邮件计数器
-
-
2.写数据库,只会覆盖已经成功提交的数据(防止"脏写")
- 推迟第二个写请求, 直到前面的事务完成提交
-
-
读-提交的实现
-
数据库通常用行级锁来防止脏写
-
但是防止脏读的话, 也加锁就太慢了
-
一般来说会同时维护旧值和新值
- 事务提交之前,都读旧值, 提交之后, 读新值
-
-
-
-
快照级别隔离和可重复读
-
每个事务都从数据库的一致性快照中读取, 事务一开始看到的是最近提交的数据 ,即使数据
-
快照级别隔离的实现
- 考虑到多个正在执行的事务可能会在不同的时间点 查看数据库状态, 所以数据库保留了对象多个 不同的提交版本(MVCC) Multi-Version Concurrency Control
- 当事务开始时, 赋予一个唯一的, 单调递增的 事务ID(txid)
- 会引入一个快照 一个created_by + 事务ID字段 一个deleted_by + 事务ID 字段
-
一致性快照的实现
-
当且仅当这两个条件成立, 数据对象对事务可见
- 1.事务开始的时刻, 创建该对象的事务已经完成了提交
- 2.对象没有标记为删除; 即使标记了, 删除的事务还没提交
-
-
索引与快照级别隔离
-
可重复读 与 命名混淆
- 快照级别隔离 在Oracle里叫可串行化
- 在PostgreSQL和MySQL里叫可重复读
-
-
防止更新丢失
-
写事务并发 最著名的问题就是 更新丢失问题
-
解决方案
-
1.原子写操作
-
如果原子操作可行, 那么就是最佳方案
-
实现方法
- 加独占锁
- 强制所有原子操作都在单线程上执行
-
-
2.显式加锁
-
3.自动检测更新丢失
- 先让他们并发执行, 如果事务检测器 检测到了更新丢失风险, 那么中止当前事务, 回退到安全的模式
- MySQL的可重复读 不支持 检测更新丢失
-
4.原子比较和设置
-
5.冲突解决与复制
- 多副本的数据库
-
-
-
写倾斜和幻读
-
医院至少一个医生值班, 如果两个人同时 请假
-
幻读
- 在一个事务的写入中 改变了 另一个事务查询结果的现象 叫幻读
-
为何产生写倾斜
-
实体化冲突
- 大多数情况下, 可串行化隔离更好
-
-
-
串行化
-
前面一大堆问题
- 我们可以用可串行化隔离来解决
-
被认为是最强的隔离级别
-
实际串行执行
-
背景
- 1.内存偏移, 可以把数据集加载到内存里
- 2.OLTP事务通常很快 而OLAP通常是只读的, 可以在一致性快照上运行, 不用在串行主循环里
-
Redis采用串行方式执行事务
- 单线程执行优势可能会比支持并发的系统效率更高, 尤其是避免锁开销
-
吞吐量上限是单核的吞吐量
- 事务需要作出调整
-
采用存储过程封装事务
-
应用程序必须提交整个事务代码作为存储过程 打包发送 给数据库, 不需要等待网络或磁盘I/O
-
而不是交互式
-
优点
- Redis 采用lua
- VoltDB使用JAVA或者Groovy
-
缺点
-
-
分区
-
如果可以让事务在一个分区里
- 那么每个分区都可以有自己的事务处理线程且独立运行
- 每个CPU核 分配一个分区
-
key-value可以试试
-
带有许多二级索引的 需要跨区协调, 不合适
-
-
场景
- 事务简短高效
- 适合数据可以完全加载到内存
- 跨分区少
-
-
两阶段锁定
-
2PL(two-phrase locking)
- 2PC是两阶段加锁, 二者区别很大
-
多个事务可以同时读取同一对象, 但只要出现两个事务同时尝试修改同一个对象时, 以加锁的方式来确保第二个写入等待前面事务完成
- 1.如果事务A已经读取了某个对象, 事务B想写入, 事务B必须要等A提交或终止
- 2.如果事务A已经修改了 对象, 事务B想读 , 那么必须等到A提交或终止
-
反高速缓存
- 如果访问不在内存的数据, 先中止事务,然后异步地加载到内存, 此期间处理其他事务, 后面再重启事务
-
实现两阶段加锁
-
已经实现的数据库
- Mysql(innoDB), SQL Server"可串行化隔离"
-
用法
-
1.如果要读 ,则要获取共享锁
-
2.如果要写, 要获取排他锁
-
3.如果要读, 然后写, 等价于获取排他锁
-
4.事务获得锁后, 一致持有锁到事务结束.
- 在第一阶段(事务执行之前)获得锁
- 在第二阶段(事务结束)释放锁
-
-
-
两阶段加锁的性能
-
主要缺点就是 相比于弱隔离性
-
性能下降非常多
-
原因
-
1,获取和释放锁本身的开销
-
2,降低了事务的并发性
- 按2PL的设计, 两个并发事务如果试图做任何困难导致竞争条件的事情, 其中一个必须等对方完成
-
如果竞争严重,会非常慢
- 而且还有死锁的问题
-
-
-
-
谓词锁
-
为了防止"幻读"问题
- 可以防止 写倾斜
-
如果事务A想读取匹配某些条件的对象,比如select查询, 那它必须以共享模式 获得查询条件的谓词锁, 如果事务B持有任何一个匹配对象的互斥锁, 则A必须等
-
如果事务A想要 更改任何对象, 必须检查所有旧值和新值是否与现有的任何谓词锁匹配, 如果B持有, A要等B
-
-
索引区间锁
-
next key locking
- 因为谓词锁性能不佳
-
简化谓词锁的方式是 将其保护的对象 扩大化
- 索引区间锁就是扩大了
-
但是开销低的多, 是一种很好的折中方案
-
如果无合适的索引 可以施加区间锁, 可以锁整个表
-
-
-
可串行化的快照隔离
-
概览
-
是一种乐观并发控制技术
-
基于数据库的一致性快照, 在之前的基础上, 新增了 算法 来检测写入之间的串行化冲突 从而决定 中止 哪些事务
-
是2008年提出的算法
- SSI
- Serializable Snapshot Isolation
-
-
悲观和乐观的并发控制
-
两阶段加锁是一种典型的悲观并发控制机制
- 如果某些操作可能出错, 那么就等到足够安全为止
-
可串行化的快照隔离 是一种 乐观并发控制
- 如果可能潜在冲突, 会继续执行而不是中止, 寄希望于平安无事, 冲突了 会重试
-
-
基于过期的条件做决定
- 检测是否读取了过期的MVCC对象
- 检测写是否影响了之前的读
-
可串行化快照隔离的性能
- 关于跟踪事务读, 写的粒度会影响性能
- 与"两阶段加锁"比, 事务不需要等待其他事务所持有的锁. 读写不会互相阻塞. 在一致性快照上执行只读查询不需要任何 锁
- 与"串行执行"相比, 可以突破单CPU核的限制,
- 可串行化隔离希望事务要简短
-
-
派生数据
XMind: ZEN - Trial Version