SAP锁机制
http://blog.chinaunix.net/u2/66669/showart_1687418.html
一、SAP为什么要设置锁:
1,保持数据的一致性
如果几个用户要访问同样的资源,需要找到一种同步访问的方法去保持数据的一致性。比如说,在航班预订系统中,需要检查还有没有空座位,当检查的时候,你不想别人修改重要的数据(空座位的数量)。
2,仅仅用Database锁是不够的
数据库管理系统物理锁定了要修改的行记录,其他用户要等到数据库锁释放才能访问这个记录。
在SAP系统中,当一个新屏幕显示的时候会释放掉Database锁,因为屏幕的改变会触发一个隐式的DB COMMIT。如果数据是从好几个屏幕收集来的话,而且在这段时间内这些数据会分别被锁定,仅仅用Database锁就不够了。
SAP系统在应用服务器层面有一个全局的LOCK TABLE,可以用来设置逻辑锁来锁定相关的表条目,并有ENQUEUE工作进程来管理这些锁。SAP锁是一种逻辑意义上的锁,有可能你锁定的表条目在DATABASE上根本就不存在。
二、锁对象和其对应的FM
在SE11里创建锁对象,自定义的锁对象都必须以EZ或者EY开头来命名。一个锁对象里只包含一个PRIMARY TABLE,可以包含若干个SECONDARY TABLE,锁的模式有三种:E,S,X。LOCK PARAMETERS里填写你要根据哪些字段来锁定表条目。
模式E:当更改数据的时候设置为此模式。
模式S:本身不需要更改数据,但是希望显示的数据不被别人更改。
模式X:和E类似,但是不允许累加,完全独占。
如果你在一个程序里成功对一个锁对象加锁之后,如果模式为E,其他用户不能再对这个锁对象加E、X、S模式的任意一种锁;
如果你在一个程序里成功对一个锁对象加锁之后,如果模式为X,其他用户不能再对这个锁对象加E、X、S模式的任意一种锁;
如果你在一个程序里成功对一个锁对象加锁之后,如果模式为S,其他用户不能再对这个锁对象加E、X模式的锁,但是可以加S模式的锁;
如果你在一个程序里成功对一个锁对象加锁之后,如果模式为E,在这个程序,你还可以再对这个锁对象加E、S模式的锁,X模式的不可以。
如果你在一个程序里成功对一个锁对象加锁之后,如果模式为X,在这个程序,你不可以再对这个锁对象加E、X、S模式的锁。
如果你在一个程序里成功对一个锁对象加锁之后,如果模式为S,在这个程序,你还可以再对这个锁对象加S模式的锁,如果没有别的用户对其加S模式的锁,那么你还可以对其加E模式的锁。X模式的不可以。
当激活锁对象的时候,系统会自动创建两个FM,ENQUEUE_ <锁对象名> 和DEQUEUE_ <锁对象名> ,分别用来锁定和解锁。
三、锁定和解锁
当用逻辑锁来锁定表条目的时候,系统会自动向LOCK TABLE中写入记录。
当调用设置锁的FM时,LOCK PARAMETERS如果没有指明,系统会锁定整个表。当然,LOCK PARAMETER:CLIENT有点特殊,如果不指定,默认是SY-MANDT;如果指定相应的CLIENT,会锁定对应CLIENT上的相应的表记录;如果设置为SPACE,则锁定涉及所有的CLIENT。
当逻辑锁设置失败后,一般会有两种例外。一个是EXCEPTION:FOREIGN_LOCK,意思是已经被锁定了;另一个是EXCEPTION:SYSTEM_FAILURE。
有些情况下,程序中设置成功的逻辑锁会隐式的自己解锁。比如说程序结束发生的时候(MESSAGE TYPE为A或者X的时候),使用语句LEAVE PROGRAM,LEAVE TO TRANSACTION,或者在命令行输入/n回车以后。
在程序的结束可以用DEQUEUE FUNCTION MODULE来解锁(当然如果你不写这个,程序结束的时候也会自动的解锁),这个时候,系统会自动从LOCK TABLE把相应的记录删除。使用DEQUEUE FUNCTION MODULE来解锁的时候,不会产生EXCEPTION。要解开你在程序中创建的所有的逻辑锁,可以用FM:DEQUEUE_ALL.
四、上锁的一般步骤
先上锁,上锁成功之后,从数据库取数据,然后更改数据,接着更新到数据库,最后解锁。按照这个步骤,才能保证更改完全运行在锁的保护机制下。
****************************************************************
http://blog.youkuaiyun.com/chuangxin/archive/2010/04/11/5473494.aspx
呵呵 想加你做好友啊
类SAP锁机制设计
1.锁的分类:
1)函数锁(FunctionLock):目的是为了限制函数的并发调用次数,如限制为2,则当该函数已有2人在调用时(尚未调用结束),第3人再去调用时就会报错并且阻断函数的调用,从而减轻服务器负荷;
2)表锁(TableLock)
2.锁机制
1)关键字:注册中心(RegisterCenter)、锁队列(LockQueue)、锁控制单元(LockEntry)、锁工厂(LockFactory)、锁(AbstractLock)
·注册中心:登记、注销锁的样本特征,主要是锁的应用程序名称和锁名称;
·锁队列:锁控制单元入队和出队操作,入队的前提条件是注册中心须有该控制单元的注册记录,出队的扫尾工作是当队列为空时,须在注册中心注销该锁;
·锁控制单元:锁控制的最小单元,相当于细胞;
·锁工厂:创建锁
·锁:执行加锁、解锁、是否已经锁定判断操作
2)核心类解析:
3)加锁、解锁原理
由于函数锁和表锁在加锁和解锁操作上执行的具体内容差异很大,因此此处细分为函数锁的加锁和解锁、表锁的加锁和解锁。
a) 函数锁加锁和解锁
加锁:
·根据锁工厂LockFactory,传入函数锁类型获取函数锁;
·设置锁控制单元LockEntry,主要是application, lockName, maxCount;
·加锁
伪代码:
LockEntry inEntry = 传入的锁控制单元
LockEntry entry = 从注册队列获取Entry(inEntry);
If(entry is null){
LockEntry le = 从注册中心获取Entry(inEntry);
If(le is null){
inEntry.MaxCount = 默认最大值;
注册中心注册(inEntry);
}else{
inEntry.MaxCount = le.MaxCount
}
inEntry.AccumlativeCount = inEntry.AccumlativeCount + 1;
注册队列注册(inEntry);
}else{
If(entry.AccumlativeCount> =entry. MaxCount){
报错,加锁失败,返回错误结果
}else{
entry.AccumlativeCount ++;
替换注册队列(entry);
}
}
·解锁
伪代码:
LockEntry inEntry = 传入的锁控制单元
LockEntry entry = 从注册队列获取Entry(inEntry);
If(entry is null){
不需解锁,返回结果
}else{
If(entry.AccumlativeCount>1){
entry.AccumlativeCount --;
替换注册队列(entry);
}else{
移除注册队列(entry);
}
}
b) 表锁加锁和解锁
加锁:
伪代码:
LockEntry inEntry = 传入的锁控制单元
if(inEngty 没有在注册中心注册){
注册中心注册(inEntry);
}
list = 获取注册队列();
if(list!=null){
if( inEntry in list){
报错,返回结果
}else{
inEntry 入队 list;
MC中 更新 list
}
}else{
新建队列list, inEntry入队;
MC中 更新 list
}
解锁:
伪代码:
LockEntry inEntry = 传入的锁控制单元
list = 获取注册队列();
if(list==null){
注册中心注销(inEntry), 返回结果;
}else{
If(inEntry in list){
inEntry 出队 list;
if(list.size()<1){
MC中移除list;
}else{
MC中更新list
}
}
}
4)加锁、解锁存在的问题:函数锁存在的问题不大,主要是表锁存在如下问题。
a)加锁存在的问题:什么时候加锁、锁ID号何时分配
b)解锁存在的问题:什么时候解锁、该解哪些锁
补充:目前常用的数据并发、一致性方案
1. 所有字段值方法
该方法的核心就是在做Update操作的时候,Where条件匹配所有字段的前后值,该方法的典型运用就是PB。
2. 时间戳(timestamp)法
该方法是目前采用最广发的一种乐观锁定方法,即利用数据库机制,给每张表定义一个timestamp类型字段,然后用户作表的insert、update操作时,数据库会自动更新timestamp字段的值。那么只要用户在更新记录的时候,加上时间戳匹配条件,即可实现乐观锁定。如表timestamp_example(id int pk, code char(3), ts timestamp)
User A: insert(1, ‘001’), 数据库会自动更新timestamp的值,假设为0x000000000001;
User B, User C 同时retrieve得到这条数据(1,’101’, 0x000000000001);
然后,User B更新该数据set code = ‘002’,此时系统会自动更新timestamp,假设更新后为0x000000000002;
此时,User C也想更新这条数据 … set code = ‘003’ where id=1 and tsequal(ts, 0x000000000001),由于ts字段的值已经在User B做更新操作的时候更新掉了,因此User C更新失败,从而实现乐观锁定。
由于,timestamp类型本质上来说是varbinary类型,映射到java数据类型和C#数据类型上是byte[]类型,该类型在数据处理上教int、string等基本类型麻烦得多,而且timestamp受数据库约束较大,因此新平台如欲采用该法来实现数据并发控制的话,将变通如下:
1) 所有数据表增加soaversion int not null字段;
2) Insert操作时,soaversion赋值1;
3) Update操作时,set soaversion = (soaversion + 1) % 999999, 如欲控制数据一致性的话,就where 条件加 soaversion = @soaversion
本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/chuangxin/archive/2010/04/11/5473494.aspx
*************************************************************************
简单实现思路:(感谢阿红)
首先在SE11下面建立一个锁对象
CALL FUNCTION 'ENQUEUE_EZ_ZTABLENAME'
EXPORTING
mode_ZTABLENAME = 'E'
mandt = sy-mandt
KEYFIELD1 = "Value
KEYFIELD2 = "Value
KEYFIELD3 = "Value
...
* X_KEYFIELD1 = ' '
* X_KEYFIELD2 = ' '
* X_KEYFIELD3 = ' '
...
* _SCOPE = '2'
* _WAIT = ' '
* _COLLECT = ' '
* If exceptions are not used, message is displayed within FM
EXCEPTIONS
FOREIGN_LOCK = 1
SYSTEM_FAILURE = 2
OTHERS = 3.
IF sy-subrc <> 0.
* Retrieve message displayed within Function Module
message id sy-msgid
type 'I'
number sy-msgno
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
EXIT.
ENDIF.
下面这个函数是解锁
CALL FUNCTION 'DEQUEUE_EZ_ZTABLENAME'
EXPORTING
MODE_ZTABLENAME = 'E'
MANDT = SY-MANDT
mandt = sy-mandt
KEYFIELD1 = "Value
KEYFIELD2 = "Value
KEYFIELD3 = "Value
...
* X_KEYFIELD1 = ' '
* X_KEYFIELD2 = ' '
* X_KEYFIELD3 = ' '
...
* _SCOPE = '3'
* _SYNCHRON = ' '
* _COLLECT = ' '
.
那么这个FUNCTION MODUEL是不是要自己写的啊?
完LOCK OBJECT后,自己写FUNCTION MODULE吗?
不用的,就会生成了
到SE37了看
DEQUEUE_*
**************************************