OceanBase 高并发场景技术解读

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: OceanBase 高并发场景技术解读

OceanBase 高并发关键技术

数据库系统是属于既要又要的系统,既要保证数据库的正确性,又要高并发。在高并发的场景下保证数据库的正确性,关键在于保证事务的 ACID。以 ACID 的 I(Isolation) 为例,I 表示的是在并发事务的场景下,事务并发执行的效果与事务串行执行的效果完全相同,这种隔离级别就是所说的可串行化隔离级别,但是可串行化隔离级别的代价比较大,往往伴随着大量的冲突等待或者冲突失败。


为了提供更好的并发执行性能,数据库不得不放宽调度的验证,允许更多非可串行化的调度被执行,多个并发的事务执行结果可能会不再等价于任何一种串行执行的结果,为了规范用户使用,数据库需要给用户做出保证:什么样的错误会发生,而什么样的错误不会发生,这些不同的保证就是数据库的隔离级别。


SQL-92 标准中基于事务并发执行过程中可能出现的三种导致数据错误的现象定义了一套隔离级别,按照这三种现象的容忍程度不同定义出了4个不同的隔离级别。1995年发表的一篇论文《A critique of ANSI SQL Isolation Levels》,论文指出了 SQL-92 标准中关于隔离级别的定义存在的一些问题:


  • SQL-92 里关于异常现象的定义过于狭隘,即使排除了3个异常现象也没有办法做到可串行化;
  • 新增了几种新的异常现象,脏写、丢失更新、读倾斜、写倾斜;

分布式事务一致性

并发事务的场景下,分布式数据库还要面临比单机数据库更多的挑战。

OceanBase 数据库 MySQL 模式的事务控制语句与 MySQL 数据库兼容,OceanBase 数据库的 MySQL 模式开启事务可以通过以下方式来完成:

执行 BEGIN 命令

obclient [test]> BEGIN;      //开启事务
obclient [test]> INSERT INTO table1 VALUES(1,1);  
obclient [test]> COMMIT;

执行 START TRANSACTION 命令

obclient [test]> START TRANSACTION; //开启事务
obclient [test]> INSERT INTO table1 VALUES(1,1);  
obclient [test]> COMMIT;

配置 autocommit = 0(关闭自动提交)后,执行 INSERT、UPDATE、DELETE、SELECT FOR UPDATE 语句时,系统会默认开启一个新事务。

obclient [test]> SET AUTOCOMMIT=0;
obclient [test]> INSERT INTO table1 VALUES(1,1);  //开启事务
obclient [test]> COMMIT;

obclient [test]> SET AUTOCOMMIT=0;
obclient [test]> UPDATE table1 SET id = 2 WHERE id = 1;  //开启事务
obclient [test]> COMMIT;

obclient [test]> SET AUTOCOMMIT=0;
obclient [test]> DELETE FROM table1 WHERE id = 2;  //开启事务
obclient [test]> COMMIT;

obclient [test]> SET AUTOCOMMIT=0;
obclient [test]> SELECT id FROM table1 WHERE id = 1 FOR UPDATE;  //开启事务
obclient [test]> COMMIT;

当事务开启时,OceanBase 数据库会为事务分配一个事务 ID,用于唯一的标识一个事务。

首先是读写并发问题

在分布式数据库系统里面,通常会采用两阶段提交协议或者事务回滚补偿机制来保证分布式事务提交的原子性,不管是采用哪种机制都会面临读写并发问题。以两阶段提交为例,两阶段提交分为 prepare 阶段和 commit 阶段,当所有的参与者都 prepare 成功之后,协调者会向所有的参与者发起 commit 请求,协议上没有办法保证所有的参与者都在同一时刻 commit 成功。

假设事务 T1 发起 A 向 B 转账50块钱,事务 T1 在提交过程中,A 已经提交成功,B 正在提交,并发事务 T2 读到的 A 和 B 分别是多少呢?

外部一致性问题

假设用户在淘宝上下了一个订单,并且支付成功了。

假设订单事务为 T1,支付事务为 T2,T1 和 T2 位于不同的服务器上,事务 T1 和事务 T2 分别各自提交,事务 T1 的提交版本号是 1000,事务 T2 的提交版本号是 800,假设有一个读事务 T3,他的快照版本号是 900,T3 能够读到支付成功的信息,但是读不到订单信息,显然这个违背了业务的语义。

并发事务调度算法

以上我们介绍了并发事务面临的挑战,并发事务的行为取决于并发事务的调度,常见的并发事务调度方法有以下几种:

  • 首先是两阶段锁:两阶段锁是一种悲观并发控制的方法,保证了并行事务的可串行化调度,通过调整读锁和加锁的策略可以实现不同的隔离级别;
  • 其次是乐观的并发控制,主要有时间序和 OCC;
  • 最后是目前主流数据库库经常会用到的多版本并发控制;

这里我们主要介绍两阶段锁和多版本并发控制。

两阶段锁

所谓的两阶段锁协议,顾名思义就是协议分为两个阶段,加锁阶段和解锁阶段,在加锁阶段不允许解锁,在加锁阶段向锁管理器申请加锁请求,锁可以是读锁也可以是写锁,当加锁不成功的时候需要等待,一旦进入解锁阶段就不允许再加锁,数据库实践中通常采用严格的两阶段锁,在事务 commit 成功之后再解锁。


看一个严格两阶段锁调度的例子,假设事务 T1,A 向 B 转账10块钱,转账过程中分别在 A 和 B 上加互斥锁,事务 T2 读取 A 和 B 的账户余额,需要加读锁,由于事务 T1 持有互斥锁,因此事务 T2 的读请求需要等待,等到事务 T1 提交成功并解锁,最后事务 T2 读到 A=90、B=110,满足两个账户加起来的和等于 200 这个约束。

两阶段锁的协议实现起来比较简单,但是当事务发现锁冲突的时候事务需要等待,可能会降低数据库的并发能力,其次多个并发事务由于彼此抢锁,有可能出现死锁的可能性。

多版本并发控制

为了解决读写冲突的问题,很多数据库实现的时候采用了多版本并发控制的机制,该机制最大的好处是读不阻塞写,写不会阻塞读,大大提高了系统的并发能力。

以 MySQL innodb 多版本并发控制的实现为例。Innodb 数据块记录的是最新版本的数据,通过 undo log 记录了多个旧版本的数据,innodb 每一行上保存了两个隐藏的字段,事务 ID 字段和回滚指针,分别用于记录修改当前行的事务 ID 以及指向旧版本数据的指针,沿着回滚可以找到多个旧版本的数据。事务在对 innodb 修改之前首先会用互斥锁锁定该行,避免其他事务并发修改,然后写 undo log 和 redo log,最后修改当前行的数据并且将行上隐藏的事务 ID 修改为本事务 ID,将回滚指针指向 undo log,以上是事务修改的简要过程。

下面我们看一下事务的读取过程,事务在读取数据之前首先生成一个 Read view,代表当前事务的可见范围。Read view 包含几个信息,首先是本事务的事务 ID,其次是当前活跃事务列表以及当前活跃事务的上限和下限。确定了 Read view 之后就相当于确定了事务的快照,然后读取行记录,根据 Read view 对行记录进行检查,如果当前行不可见,则通过回滚指针继续查找旧版本的数据。首先检查行上的事务 ID,如果行上的事务 ID 在当前的活跃事务列表里或者行上的事务 ID 大于 Read view 的最大事务 ID,则行上的数据不可见,需要找旧版本的数据,否则数据可见。

多版本管理

OceanBase 采用基于互斥锁的多版本并发控制,其中 OceanBase 存储引擎采用的是 LSM tree 架构,将数据拆分成静态数据和动态数据,动态数据保存在 Memtable 中并定期 dump 到磁盘中,Memtable 采用 B+tree 以及Hash 双索引结构对数据进行存储,其中 B+tree 用于范围查询,Hash 用于单行查询。


B+tree 的叶子节点保存了行数据的元信息,元信息里面有很多字段,这里只介绍3个字段,主键信息、锁信息以及链表指针:


锁信息表示是否有事务持有行锁,事务在修改数据之前需要先加行锁;

链表信息指向多个版本的数据,每个版本只保存增量信息,比如某一次修改只修改一个字段,增量信息只会记录该字段的变化情况;

从下图可以看出,主键等于1这一行记录发生过3次变更,第1次变更是一个插入操作,事务版本号是1000,第2次变更修改了b字段,事务版本号是1005,第3次变更修改了b字段,事务版本号是无穷大,表示当前事务还没有提交。

多版本并发控制

OceanBase 分布式数据库系统对事务进行调度,确保并发事务不会出现一致性问题。假设有3个并发事务,分别为读写事务 T1, 只读事务 T2,读写事务 T3,其中事务 T1 还没有提交,持有行的行锁。

事务 T3 在对行1进行修改之前需要先持有行1的行锁,由于行1的行锁被事务 T1 持有,因此 T3 需要等待,直到事务T1提交成功并且将行锁解开。事务 T2 是一个只读事务,事务的快照版本号是1008,在读取之前首先通过索引结构找到行的元数据,然后根据快照版本号找到比快照版本号小的最大提交版本号的数据。从下图可以看出事务 T2 能够读到提交版本号是1005的数据。以上就是 OceanBase 内部的读写并发控制机制,通过行上的互斥锁来解决写写冲突的问题,通过多版本机制保证写不阻塞读,读不阻塞写。


OceanBase 的多版本并发控制实现非常的简单,快照版本就是一个时间戳,通过比较时间戳的大小就可以确定事务的可见性,不需要维护活跃事务。在有些分布式数据库系统里,维护了全局事务管理器,这个全局事务管理器本质上就是用来确定事务的快照的,当并发的事务比较多的时候,全局事务管理器容易成为集群的瓶颈,OceanBase 不需要维护全局事务管理器。还有一点就是 OceanBase 行的元数据上保存了锁信息,不需要额外的锁管理器。

回到开头讲到的读写并发问题,事务 T1 在提交过程中,A 已经提交成功,B 正在提交,为了解决读写并发问题有些分布式系统采用两阶段锁方案,在读的时候需要加读锁确保在事务 T1 提交成功之后事务 T2 可以读到。

在 OceanBase 分布式数据库系统里,读请求首先会根据快照版本号找到对应版本的数据,假设事务 T1 的提交版本号小于事务 T2 的快照版本号,那么事务 T2 可以读到 T1 的修改,事务 T2 在读 B 这一行数据的时候,如果发现 B 上处于 committing 状态,那么事务 T2 需要等待,直到 B 这一行数据提交成功。这样就可以保证事务 T2 既能够读到 A 上的数据还能读到 B 上的数据。在这种场景下事务 T2 需要等待的时间窗口,就是两阶段提交过程中从 prepare 阶段到 commit 阶段这个时间窗口,这个时间窗口要远远小于两阶段锁的等锁时间。

外部一致性是分布式数据库系统要解决的另外一个问题。

假设用户在淘宝上要买一个手机,首先用户先下单,下单成功之后发起订单支付。下单和支付是两个不同的事务,我们假设这两个事务分别是 T1 和 T2,由于这两个事务分别在两个不同的机器上执行,各自生成提交版本号,事务 T1 的提交版本号是1000,事务 T2 的提交版本号是800,假设事务 T3 的快照版本号是900,事务 T3 会出现能够读到订单的支付信息,但是读不到订单信息,显然不符合事务的先后顺序,这个就是外部一致性。

全局时间戳服务

为了解决外部一致性的问题,OceanBase 引入了全局时间戳服务,通过全局时间戳为每个事务分配快照版本及提交版本号。

从图上我们可以看出事务 T1 和事务 T2 在提交过程中分别向全局时间戳服务申请一个时间作为事务的提交版本号,事务 T3 也从全局时间戳申请一个时间戳做为事务的快照,显然全局时间戳服务可以保证 TS1 < TS2 < TS3,假如事务 T3 能够读到 T2 修改的数据,那么 T3 肯定也能够读到 T1 修改的数据,解决了外部一致性的问题。

提前解行锁

在单机数据库里面会碰到热点行更新的问题,但是在分布式数据库系统里热点行更新的问题会更加的明显,热点行更新的性能取决于行锁持锁的时间,行锁持锁的时间越长热点行更新的性能会越差。


在分布式系统里事务的延迟会比单机事务的延迟要大一些,事务持锁的时间会更长,另外在三地五中心的部署方式里,日志同步的延迟可能会超过几十ms,大大影响热点行更新的性能。为了缓解热点行更新的问题,OceanBase 采用提前解行锁的方案来缓解热点行更新的问题。


传统数据库在执行过程中加行锁,在最后事务提交的时候等待事务持久化成功之后再解锁,这个是普通事务的执行过程。在 OceanBase 热点行更新的方案里,事务在执行过程中也需要加行锁,但是在收到用户 commit 请求之后,在事务持久化成功之前就可以解锁。从下图可以看出在优化之前事务是串行执行的,采用提前解行锁方案之后,事务在日志持久化之前后续的事务就可以加锁成功,大大降低事务持锁的时间。


我们做了一些测试,测试了 oltp update 事务执行过程中各个阶段的时间消耗,其中生成执行计划大概需要 60us,dml 操作大概需要 50us,填充事务日志大概需要 33us,日志同步大概需要 170us,可以算出事务执行过程中持锁的总时间大概是 270us。提前解行锁优化之后,事务的持锁时间降低了 65%,相应的热点行更新的性能可以达到原来性能的3倍。

在上面介绍提前解行锁这个方案的时候,可能有同学已经想到,假如前面一个事务持久化失败,那后续的事务应该怎么处理呢?

如果一个事务失败了,那么后续的事务都需要回滚。这个就是通常所说的级联回滚,在数据库论文里经常会提到我们应该避免级联回滚,但是在实际的应用场景里,事务提交失败的比例是非常低的,因此出现级联回滚的可能性非常低,在实际的测试来看也能够证明这一点。


为了支持事务级联回滚,需要为每个热点行维护事务间的依赖关系,将修改同一行的多个事务串成一个链表,确保在某个事务失败的时候能够将后继的事务都回滚掉。比如事务 T1、T2、T3,对同一热点行数据做了修改,事务 T1 在持久化成功之前提前把行锁解开,事务 T2 就可以加锁成功,相应的 T2 提前解行锁,T3 也可以加锁成功。因此事务 T1、T2、T3 构成了一个依赖关系,事务 T3 依赖事务 T2,事务 T2 依赖事务 T1,这些依赖关系形成一个链表,当事务 T1 最终持久化失败的事务,事务 T2 和事务 T3 需要回滚,否则会出现正确性问题。


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
15天前
|
消息中间件 存储 负载均衡
高并发流量杀手锏:揭秘秒杀系统背后的削峰技术!
本文介绍了秒杀场景下的“削峰填谷”策略,通过消息队列缓冲用户请求,避免高并发对系统造成冲击。文中详细解释了消息队列的工作原理及如何通过预扣减库存和分布式锁确保数据一致性,同时还提出了合理的消息队列配置、高可用性及数据库负载均衡等最佳实践。通过这些技术手段,可有效提升系统的稳定性和用户体验。
37 8
高并发流量杀手锏:揭秘秒杀系统背后的削峰技术!
|
2月前
|
NoSQL 关系型数据库 MySQL
排行榜系统设计:高并发场景下的最佳实践
本文由技术分享者小米带来,详细介绍了如何设计一个高效、稳定且易扩展的排行榜系统。内容涵盖项目背景、技术选型、数据结构设计、基本操作实现、分页显示、持久化与数据恢复,以及高并发下的性能优化策略。通过Redis与MySQL的结合,确保了排行榜的实时性和可靠性。适合对排行榜设计感兴趣的技术人员参考学习。
57 7
排行榜系统设计:高并发场景下的最佳实践
|
16天前
|
缓存 分布式计算 Hadoop
HBase在高并发场景下的性能分析
HBase在高并发场景下的性能受到多方面因素的影响,包括数据模型设计、集群配置、读写策略及性能调优等。合理的设计和配置可以显著提高HBase在高并发环境下的性能。不过,需要注意的是,由于项目和业务需求的不同,性能优化并没有一劳永逸的解决方案,需要根据实际情况进行针对性的调整和优化。
47 8
|
2月前
|
存储 缓存 监控
函数计算产品使用问题之调用sd生图时,怎么保证高并发场景正常运行
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
2月前
|
弹性计算 监控 Serverless
函数计算产品使用问题之如何处理银行转账场景遇到的高并发问题
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
2月前
|
缓存 监控 安全
揭秘高并发神话背后:打造坚不可摧的秒杀系统,技术大牛必修课!
【8月更文挑战第29天】在设计高并发、高可用的分布式秒杀系统时,需关注系统架构、数据库设计、缓存策略、并发控制、降级限流及安全防护。采用微服务架构并通过API网关和负载均衡器通信;数据库设计需考虑分库分表与读写分离;利用Redis缓存热点数据;采用限流算法和排队机制控制并发;实施IP限流和验证码验证保障安全。以下为简化代码示例,展示如何在秒杀服务中实现预扣减库存和订单创建逻辑。此外,还需进行性能测试与优化,并设置监控和日志记录机制,确保系统稳定可靠。
40 1
|
2月前
|
关系型数据库 OLAP 分布式数据库
揭秘Polardb与OceanBase:从OLTP到OLAP,你的业务选对数据库了吗?热点技术对比,激发你的选择好奇心!
【8月更文挑战第22天】在数据库领域,阿里巴巴的Polardb与OceanBase各具特色。Polardb采用共享存储架构,分离计算与存储,适配高并发OLTP场景,如电商交易;OceanBase利用灵活的分布式架构,优化数据分布与处理,擅长OLAP分析及大规模数据管理。选择时需考量业务特性——Polardb适合事务密集型应用,而OceanBase则为数据分析提供强大支持。
197 2
|
2月前
|
Java Spring 开发者
Spring 框架配置属性绑定大比拼:@Value 与 @ConfigurationProperties,谁才是真正的王者?
【8月更文挑战第31天】Spring 框架提供 `@Value` 和 `@ConfigurationProperties` 两种配置属性绑定方式。`@Value` 简单直接,适用于简单场景,但处理复杂配置时略显不足。`@ConfigurationProperties` 则以类级别绑定配置,简化代码并更好组织配置信息。本文通过示例对比两者特点,帮助开发者根据具体需求选择合适的绑定方式,实现高效且易维护的配置管理。
35 0
|
2月前
|
安全 Java Go
探索Go语言在高并发场景中的优势
Go语言,作为一种现代编程语言,凭借其并发模型和高效的性能,正在成为处理高并发任务的首选。本文深入探讨Go语言的并发特性,特别是goroutines和channels如何在实际应用中提供高效的解决方案。我们将通过对比其他语言的处理方式,展示Go语言在性能和可维护性上的优势。
|
3月前
|
SQL 关系型数据库 MySQL
(八)MySQL锁机制:高并发场景下该如何保证数据读写的安全性?
锁!这个词汇在编程中出现的次数尤为频繁,几乎主流的编程语言都会具备完善的锁机制,在数据库中也并不例外,为什么呢?这里牵扯到一个关键词:高并发,由于现在的计算机领域几乎都是多核机器,因此再编写单线程的应用自然无法将机器性能发挥到最大,想要让程序的并发性越高,多线程技术自然就呼之欲出,多线程技术一方面能充分压榨CPU资源,另一方面也能提升程序的并发支持性。
216 3

热门文章

最新文章

下一篇
无影云桌面