实现并发新高度:23ai的无锁列值保留
Oracle Database 23ai支持Lock-Free Reservation,中文通常译为“无锁列值保留”。

本文将通过3个部分来阐述Lock-Free Reservation的这个特性:
- 1.应用场景
- 2.实现原理
- 3.使用限制
1.应用场景
Lock-Free Reservation这项特性可用于实现更细粒度的并发控制。
它的本质是相对于传统的行锁,能以更细的粒度(即列值级别)进行锁定,从而减少锁争用,提高并发性能。
例如,当库存充足时,数据仅在提交时锁定,并有可能改善最终用户体验以及事务的吞吐量。
为了避免重复造轮子,本文演示的测试用例部分,直接参考了官方博客中给出的测试用例,原文链接为:
下面我们就依据此测试用例来测试并理解下Lock-Free Reservation的具体功效吧。
1.1 创建测试表
首先,创建测试表inventory:
create table inventory
( item_id NUMBER CONSTRAINT inv_pk PRIMARY KEY,
item_display_name VARCHAR2(100) NOT NULL,
item_desc VARCHAR2(2000),
qty_on_hand NUMBER RESERVABLE CONSTRAINT qty_ck CHECK (qty_on_hand >= 0) NOT NULL,
shelf_capacity NUMBER NOT NULL,
CONSTRAINT shelf_ck CHECK (qty_on_hand <= shelf_capacity)
);
在开始下一步前,我们先解读下这张测试表的创建语句,以便后面测试能更好的理解:
- CHECK (qty_on_hand >= 0) 这个约束确保了库存数量不能为负数。
- shelf_capacity,这是货架容量,表示每种物品在货架上的最大存放量。
- CONSTRAINT shelf_ck CHECK (qty_on_hand <= shelf_capacity),这个约束确保 qty_on_hand(当前库存数量)不能超过 shelf_capacity(货架容量)。
1.2 查看测试表信息
这里测试表的RESERVABLE属性列,还可以通过如下SQL来查看,确认表中是否存在RESERVABLE属性的列,以及确定具体是哪一列:
col table_name format a30
col has_reservable_column format a30
col reservable_column format a30
select table_name, has_reservable_column
from user_tables
where table_name = 'INVENTORY';
select column_name, reservable_column
from user_tab_cols
where table_name = 'INVENTORY' and reservable_column = 'YES';
查看表中约束信息:
col search_condition format a40
col constraint_name format a20
select constraint_name, search_condition
from user_constraints
where table_name='INVENTORY';
上面查询结果就是我们创建的表的基础信息:
07:35:47 PRIMARY @ORCL -> JINGYU @PDB1>
TABLE_NAME HAS_RESERVABLE_COLUMN
------------------------------ ------------------------------
INVENTORY YES
COLUMN_NAME RESERVABLE_COLUMN
------------------------------ ------------------------------
QTY_ON_HAND YES
CONSTRAINT_NAME SEARCH_CONDITION
-------------------- ----------------------------------------
SYS_C008423 "ITEM_DISPLAY_NAME" IS NOT NULL
SYS_C008424 "QTY_ON_HAND" IS NOT NULL
SYS_C008425 "SHELF_CAPACITY" IS NOT NULL
QTY_CK qty_on_hand >= 0
SHELF_CK qty_on_hand <= shelf_capacity
INV_PK
1.3 插入测试数据
在测试表中插入3条测试数据,并提交更改:
insert into inventory values (123, 'Milk', 'Lowfat 2%', 100, 120);
insert into inventory values (456, 'Bread', 'Multigrain', 50, 100);
insert into inventory values (789, 'Eggs', 'Organic', 50, 75);
commit;
2.实现原理
首先设计测试场景,然后从测试表现理解实现原理。
2.1 测试无锁列值保留
目前测试表中数据:
07:37:39 PRIMARY @ORCL -> JINGYU @PDB1> select ITEM_ID, QTY_ON_HAND, SHELF_CAPACITY from inventory;
ITEM_ID QTY_ON_HAND SHELF_CAPACITY
---------- ----------- --------------
123 100 120
456 50 100
789 50 75
Elapsed: 00:00:00.00
--更新item_id=123的qty_on_hand列,原值减10 @session1:
update inventory
set qty_on_hand = qty_on_hand - 10
where item_id = 123;
--更新item_id=123的qty_on_hand列,原值减2 @session2:
update inventory
set qty_on_hand = qty_on_hand - 2
where item_id = 123;
--更新item_id=123的qty_on_hand列,原值减9 @session3:
update inventory
set qty_on_hand = qty_on_hand - 9
where item_id = 123;
--更新item_id=123的qty_on_hand列,原值加20 @session4:
update inventory
set qty_on_hand = qty_on_hand + 20
where item_id = 123;
传统情况下,不同会话同时更新表的同一行数据,会阻塞,但这里上面4个会话都可以正常执行成功。
结果如下:
--@session1:
07:38:03 PRIMARY @ORCL -> JINGYU @PDB1> update inventory
set qty_on_hand = qty_on_hand - 10
where item_id = 123;
07:39:13 2 07:39:13 3
1 row updated.
Elapsed: 00:00:00.01
07:39:13 PRIMARY @ORCL -> JINGYU @PDB1>
--@session2:
07:39:01 PRIMARY @ORCL -> JINGYU @PDB1> update inventory
set qty_on_hand = qty_on_hand - 2
where item_id = 123;
07:39:29 2 07:39:29 3
1 row updated.
Elapsed: 00:00:00.01
07:39:29 PRIMARY @ORCL -> JINGYU @PDB1>
--@session3:
07:39:03 PRIMARY @ORCL -> JINGYU @PDB1> update inventory
set qty_on_hand = qty_on_hand - 9
where item_id = 123;07:39:38 2 07:39:38 3
1 row updated.
Elapsed: 00:00:00.01
--@session4:
07:39:07 PRIMARY @ORCL -> JINGYU @PDB1> update inventory
set qty_on_hand = qty_on_hand + 20
where item_id = 123;07:39:45 2 07:39:45 3
1 row updated.
Elapsed: 00:00:00.01
4个会话更新同一行数据,完全不受影响,实现了并发的新高度,即比行锁更细的颗粒度。
2.2 查看journal table
查看journal table,这个是核心,也是Lock-Free Reservation的底层实现机制:
select object_name, object_type, created
from user_objects order by 3 desc;
结果如下:
OBJECT_NAME OBJECT_TYPE CREATED
------------------------------ ----------------------- ---------
INV_PK INDEX 12-JUN-24
SYS_RESERVJRNL_76171 TABLE 12-JUN-24
INVENTORY TABLE 12-JUN-24
查看这个journal table的表结构:
07:42:53 PRIMARY @ORCL -> JINGYU @PDB1> desc SYS_RESERVJRNL_76171
Name Null? Type
----------------------------------------------------------------------------------------------------- -------- --------------------------------------------------------------------
ORA_SAGA_ID$ RAW(16)
ORA_TXN_ID$ RAW(8)
ORA_STATUS$ VARCHAR2(11)
ORA_STMT_TYPE$ VARCHAR2(6)
ITEM_ID NOT NULL NUMBER
QTY_ON_HAND_OP VARCHAR2(1)
QTY_ON_HAND_RESERVED NUMBER
07:42:58 PRIMARY @ORCL -> JINGYU @PDB1>
select * from SYS_RESERVJRNL_76171;
分别在上面4个会话查询,结果如下:
--@session1:
07:42:58 PRIMARY @ORCL -> JINGYU @PDB1> select * from SYS_RESERVJRNL_76171;
ORA_SAGA_ID$ ORA_TXN_ID$ ORA_STATUS$ ORA_ST ITEM_ID Q QTY_ON_HAND_RESERVED
-------------------------------- ---------------- ----------- ------ ---------- - --------------------
05001B0068040000 ACTIVE UPDATE 123 - 10
Elapsed: 00:00:00.00
--@session2:
07:39:30 PRIMARY @ORCL -> JINGYU @PDB1> select * from SYS_RESERVJRNL_76171;
ORA_SAGA_ID$ ORA_TXN_ID$ ORA_STATUS$ ORA_ST ITEM_ID Q QTY_ON_HAND_RESERVED
-------------------------------- ---------------- ----------- ------ ---------- - --------------------
02000B0050040000 ACTIVE UPDATE 123 - 2
Elapsed: 00:00:00.00
--@session3:
07:39:39 PRIMARY @ORCL -> JINGYU @PDB1> select * from SYS_RESERVJRNL_76171;
ORA_SAGA_ID$ ORA_TXN_ID$ ORA_STATUS$ ORA_ST ITEM_ID Q QTY_ON_HAND_RESERVED
-------------------------------- ---------------- ----------- ------ ---------- - --------------------
0A000A00B3030000 ACTIVE UPDATE 123 - 9
Elapsed: 00:00:00.00
--@session4:
07:39:46 PRIMARY @ORCL -> JINGYU @PDB1> select * from SYS_RESERVJRNL_76171;
ORA_SAGA_ID$ ORA_TXN_ID$ ORA_STATUS$ ORA_ST ITEM_ID Q QTY_ON_HAND_RESERVED
-------------------------------- ---------------- ----------- ------ ---------- - --------------------
04000E00B2030000 ACTIVE UPDATE 123 + 20
Elapsed: 00:00:00.00
可以看到这个journal table的表SYS_RESERVJRNL_76171详细记录了每个会话对这列的操作。
一旦提交,这个记录就会被清空。有点儿像Oracle的临时表?顺手获取下这个表的创建语句:
select dbms_metadata.get_ddl('TABLE','SYS_RESERVJRNL_76171','JINGYU') from dual;
07:48:49 PRIMARY @ORCL -> JINGYU @PDB1> select dbms_metadata.get_ddl('TABLE','SYS_RESERVJRNL_76171','JINGYU') from dual;
DBMS_METADATA.GET_DDL('TABLE','SYS_RESERVJRNL_76171','JINGYU')
--------------------------------------------------------------------------------
CREATE TABLE "JINGYU"."SYS_RESERVJRNL_76171"
( "ORA_SAGA_ID$" RAW(16),
"ORA_TXN_ID$" RAW(8),
"ORA_STATUS$" VARCHAR2(11),
"ORA_STMT_TYPE$" VARCHAR2(6),
"ITEM_ID" NUMBER NOT NULL ENABLE,
"QTY_ON_HAND_OP" VARCHAR2(1),
"QTY_ON_HAND_RESERVED" NUMBER
) SEGMENT CREATION IMMEDIATE
PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
NOCOMPRESS LOGGING
STORAGE(INITIAL 16384 NEXT 16384 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "USERS"
从创建语句来看倒没有指定临时表创建语句,但感觉上实现机制上有些类似,以后空了再研究,不是很重要。
总之这个日志表是建立无锁列值保留时,Oracle自动创建的,当然也由Oracle自己维护,用户肯定不能对其直接进行操作,否则会报错:
ORA-55727: DML, ALTER, RENAME, and CREATE INDEX operations are not allowed on the reservation journal table "JINGYU"."SYS_RESERVJRNL_76171".
Help: https://docs.oracle.com/error-help/db/ora-55727/
3.使用限制
最后聊下关于Lock-Free Reservation的目前使用限制:
- 该特性仅限于特定场景,主要是经常需要更新特定列而非整行的场景。
- 更新特定列也不能随便,只能使用原值增加或减少的方式。
- 支持的数据类型有限:仅支持数值型数据列,不适用于所有数据类型。
- 只在23ai数据库版本中提供支持。
3.1 修改非特定列肯定不行
这好像是废话。。。但还是提一句,可不要傻傻的认为表中的所有列都可以。
--@session1:
update inventory
set item_display_name = 'XXBK'
where item_id = 123;
--@session2:
update inventory
set item_display_name = 'ABCD'
where item_id = 123;
比如上面更新这个表的其他列时,session2肯定会等待session1提交或回滚之后才能操作成功。
3.2 修改特定列也有特定限制
特定列就可以随便更新了吗?目前也不是的,只能支持特定的场景,比如使用原值增加或减少的方式。
--@session1:
update inventory
set qty_on_hand = 30
where item_id = 123;
--@session2:
update inventory
set qty_on_hand = 40
where item_id = 123;
上面这种直接更新这个特定列的值,也是会报错:
ORA-55746: Reservable column update statement only supports + or - operations on a reservable column.
Help: https://docs.oracle.com/error-help/db/ora-55746/
3.3 不支持非数值型
如果你定义了非数值型的保留列,建表就会直接报错,明确提醒你只支持NUMBER, INTEGER, FLOAT这些数据类型:
ORA-55748: Reservable column property specified on column "QTY_ON_HAND" is supported only on columns of data types Oracle NUMBER, INTEGER, and FLOAT.
Help: https://docs.oracle.com/error-help/db/ora-55748/
3.4 修改表中列的RESERVABLE属性
如果你最终发现,你的业务根本不需要列的RESERVABLE属性,那么就可以修改列属性,去掉RESERVABLE属性:
alter table inventory modify (qty_on_hand NOT RESERVABLE);
这样,这个列就会支持常规更新操作,但同时,就会和普通列一样持有锁:
--@session1:
update inventory
set qty_on_hand = 30
where item_id = 123;
--@session2:
update inventory
set qty_on_hand = 40
where item_id = 123;
3.5 测试环境清理
最后测试回退相关操作,删除测试表:
drop table inventory;
好了,有关Oracle Database 23ai支持Lock-Free Reservation特性实现并发新高度的测试就到这里了。大家可以想想自己公司的业务中是否有合适的场景可以通过这个特性大幅增加并发能力,欢迎一起讨论。
实现并发新高度:23ai的无锁列值保留的更多相关文章
- 【实战Java高并发程序设计6】挑战无锁算法:无锁的Vector实现
[实战Java高并发程序设计 1]Java中的指针:Unsafe类 [实战Java高并发程序设计 2]无锁的对象引用:AtomicReference [实战Java高并发程序设计 3]带有时间戳的对象 ...
- 如何在高并发环境下设计出无锁的数据库操作(Java版本)
一个在线2k的游戏,每秒钟并发都吓死人.传统的hibernate直接插库基本上是不可行的.我就一步步推导出一个无锁的数据库操作. 1. 并发中如何无锁. 一个很简单的思路,把并发转化成为单线程.Jav ...
- java并发:AtomicInteger 以及CAS无锁算法【转载】
1 AtomicInteger解析 众所周知,在多线程并发的情况下,对于成员变量,可能是线程不安全的: 一个很简单的例子,假设我存在两个线程,让一个整数自增1000次,那么最终的值应该是1000:但是 ...
- 【Java并发编程】2、无锁编程:lock-free原理;CAS;ABA问题
转自:http://blog.csdn.net/kangroger/article/details/47867269 定义 无锁编程是指在不使用锁的情况下,在多线程环境下实现多变量的同步.即在没有线程 ...
- Erlang运行时中的无锁队列及其在异步线程中的应用
本文首先介绍 Erlang 运行时中需要使用无锁队列的场合,然后介绍无锁队列的基本原理及会遇到的问题,接下来介绍 Erlang 运行时中如何通过“线程进度”机制解决无锁队列的问题,并介绍 Erlang ...
- CAS原子操作实现无锁及性能分析
CAS原子操作实现无锁及性能分析 Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn.net/chen19870707 ...
- 【Java并发编程】9、非阻塞同步算法与CAS(Compare and Swap)无锁算法
转自:http://www.cnblogs.com/Mainz/p/3546347.html?utm_source=tuicool&utm_medium=referral 锁(lock)的代价 ...
- Java CAS同步机制 原理详解(为什么并发环境下的COUNT自增操作不安全): Atomic原子类底层用的不是传统意义的锁机制,而是无锁化的CAS机制,通过CAS机制保证多线程修改一个数值的安全性。
精彩理解: https://www.jianshu.com/p/21be831e851e ; https://blog.csdn.net/heyutao007/article/details/19 ...
- Java并发程序设计(七)乐天派:无锁
无锁 一.概述 无锁是处理并发的一种乐观策略,它会假设对资源的访问是没有冲突的.既然没有冲突自然不需要等待,所以所有的线程都可以在不停顿的状态下执行.那遇到冲突怎么办?接下来请看,无锁绝招“CAS”即 ...
- 加锁并发算法 vs 无锁并发算法
Heinz Kabutz 在上周举办了一次成功 JCrete研讨会,我在会上参加了对一种新的 StampedLock(于JSR166中 引入) 进行的评审.StampedLock (邮戳锁) 旨在解决 ...
随机推荐
- IT人才能嗑到的这对CP,甜!
简介: 提到文件存储,相信大家都不陌生,在浩瀚的存储发展史中,文件存储无疑是璀璨的,耀眼的.那么,在性能已经成为刚需,自动驾驶行业风起云涌的当下,文件存储与GPU这对CP又有怎样的含糖量呢?今天,我们 ...
- 在阿里巴巴,我们如何先于用户发现和定位 Kubernetes 集群问题?
简介:本文整理自阿里云高级研发工程师彭南光(光南) 在 KubeCon China 2021 大会的演讲实录,分享了阿里巴巴是如何通过自研通用链路探测+定向巡检工具 KubeProbe 应对大规模集 ...
- 一文了解EPaxos核心协议流程
简介: EPaxos(Egalitarian Paxos)作为工业界备受瞩目的下一代分布式一致性算法,具有广阔的应用前景.但纵观业内,至今仍未出现一个EPaxos的工程实现,甚至都没看到一篇能把EPa ...
- 局域网内一部分网络设备无法ping通,icmp_seq=1 目标主机不可达
问题: 来自 192.168.2.99 icmp_seq=1 目标主机不可达. 最近想在局域网内搭建一台服务器,打开SSH服务后发现局域网内的一部分设备无法使用,尝试了各种办法都没能解决,重装系统 ...
- .NET Emit 入门教程:第七部分:实战项目1:将 DbDataReader 转实体
前言: 经过前面几个部分学习,相信学过的同学已经能够掌握 .NET Emit 这种中间语言,并能使得它来编写一些应用,以提高程序的性能. 随着 IL 指令篇的结束,本系列也已经接近尾声,在这接近结束的 ...
- 【git】建立分支
1.git clone现有的项目 git clone git@github.com:zhangshengdong/Zflask.git 2.建立关联 git remote add origin git ...
- 模型微调-书生浦语大模型实战营学习笔记4&大语言模型7
大语言模型-7.模型微调 书生浦语大模型实战营学习笔记-4.模型微调 本节对应的视频教程为B站链接.笔记对视频的理论部分进行了整理.部分内容参考李宏毅2024春<生成式人工智能导论>和三分 ...
- v-for比v-if优先级更高
在V2当中,v-for的优先级更高,而在V3当中,则是v-if的优先级更高. 在V3当中,做了v-if的提升优化,去除了没有必要的计算, 但同时也会带来一个无法取到v-for当中遍历的item问题, ...
- Python基础篇(流程控制)
流程控制是程序运行的基础,流程控制决定了程序按照什么样的方式执行. 条件语句 条件语句一般用来判断给定的条件是否成立,根据结果来执行不同的代码,也就是说,有了条件语句,才可以根据不同的情况做不同的事, ...
- turltle模块详解
引言:turtle(海龟)模块,我们是用它来进行画图的,基本上就是画简单的直线,点,和曲线. 你可以把它想成一个小海龟,在沙滩上行走,然后留下的各种痕迹,使用Turtle模块可以绘制很多精美的图形. ...